瀏覽代碼

Merge remote-tracking branch 'origin/feature/v2.4.46' into feature/v2.4.46

zhangyuhan 3 月之前
父節點
當前提交
59eb5c3d37

+ 2 - 2
src/store/order.js

@@ -315,10 +315,10 @@ export default {
       state.pageForm = payload
     },
     resetLoading(state, payload = cloneDeep(defaultLoading)) {
-      state.pageForm = payload
+      state.loading = payload
     },
     resetOrderInfo(state, payload = cloneDeep(defaultOrderInfo)) {
-      state.pageForm = payload
+      state.orderInfo = payload
     },
     // 修改数组对象中某个值的 mutation
     updatePageFormArrayObjectValue(state, { arrayName, index, key, newValue }) {

+ 1 - 0
src/utils/mixins/selector-v-model.js

@@ -11,6 +11,7 @@ export const selectorVModelMixin = {
   watch: {
     value: {
       immediate: true,
+      deep: true,
       handler(n) {
         this.setState(n)
       }

+ 134 - 45
src/views/create-order/components/order-detail-submodule/OrderDetailCard.vue

@@ -14,14 +14,19 @@
         </div>
       </div>
       <div class="order-detail-product-list">
-        <ProductCard v-for="(product, index) in productData" :key="product.id" :title="setProductTitle(product, index)" subtitle="该产品暂不支持系统自动开通权限,请联系运维开通">
+        <ProductCard v-for="(product, index) in productData" :key="product.id" :title="setProductTitle(product, index)" :subtitle="product.auto !== 1 ? '该产品暂不支持系统自动开通权限,请联系运维开通': ''">
           <div class="order-detail-product-content">
-            <div class="order-detail-card-item" v-for="(item, index) in productInfoItems" :key="index">
-              <div v-if="item.key === 'linkedOrder'" class="linkedOrder">
-                {{ item.label }}
-                <TableCard :columns="linkOrderColumns"></TableCard>
+            <div class="grouped-items">
+              <div class="order-detail-card-item" :class="`item-span-${item.span}`" v-for="(item, index) in productInfoItems" :key="index">
+                <div v-if="item.key === 'linkedOrder'" class="linkedOrder">
+                  {{ item.label }}:
+                  <TableCard :columns="linkOrderColumns"></TableCard>
+                </div>
+                <div v-else-if="item.key === 'validity_period'">
+                  <span v-html="getValidityPeriodHtml(product, item)"></span>
+                </div>
+                <div v-else>{{ item.label }}:{{ getFilteredValue(product[item.key], item.filter) || '-' }}</div>
               </div>
-              <div v-else>{{ item.label }}:{{ getFilteredValue(product[item.key], item.filter) || '-' }}</div>
             </div>
           </div>
         </ProductCard>
@@ -68,21 +73,21 @@ export default {
         { label: '0元订单类型', key: 'product_type'}
       ],
       productInfoItems: [
-        { label: '活动产品', key: 'activity_code' },
-        { label: '付费类型', key: 'service_type', filter: 'orderServiceType' },
-        { label: '升级内容', key: 'supServicelds' },
-        { label: '产品规格', key: 'combold'},
-        { label: '服务列表', key: 'product_type'},
-        { label: '有效周期', key: 'product_type'},
-        { label: '合同金额', key: 'final_price'},
-        { label: '标准售价', key: 'original_price'},
-        { label: '折扣率', key: 'rate'},
-        { label: '子账号数量', key: 'product_type'},
-        { label: '主账号数量', key: 'product_type'},
-        { label: '关联订单', key: 'linkedOrder'},
-        { label: '开通权益手机号', key: 'product_type'},
-        { label: '服务开始时间', key: 'product_type'},
-        { label: '服务结束时间', key: 'product_type'}
+        { label: '活动产品', key: 'activity_code', span: 1 },
+        { label: '付费类型', key: 'service_type', filter: 'orderServiceType', span: 3},
+        { label: '升级内容', key: 'supServicelds', span: 3 },
+        { label: '产品规格', key: 'productName', span: 3},
+        { label: '服务列表', key: 'bigServiceNames', span: 1},
+        { label: '有效周期', key: 'validity_period', span: 1},
+        { label: '合同金额', key: 'final_price', span: 3},
+        { label: '标准售价', key: 'original_price', span: 3},
+        { label: '折扣率', key: 'rate', span: 3},
+        { label: '子账号数量', key: 'product_type', span: 1},
+        { label: '主账号数量', key: 'product_type', span: 1},
+        { label: '关联订单', key: 'linkedOrder', span: 1},
+        { label: '开通权益手机号', key: 'phone', span: 3},
+        { label: '服务开始时间', key: 'service_starttime', span: 3},
+        { label: '服务结束时间', key: 'service_endtime', span: 3}
       ],
       orderData: {},
       productData: [],
@@ -108,48 +113,115 @@ export default {
           width: 80
         },
         {
-          prop: 'buySubject',
-          label: '购买主体',
-          width: 80
+          prop: 'order_code',
+          label: '订单编号',
+          width: 154
         },
         {
-          prop: 'buySubject',
-          label: '购买主体',
+          prop: 'service_type',
+          label: '付费类型',
           width: 80
         },
         {
-          prop: 'buySubject',
-          label: '购买主体',
-          width: 80
+          prop: 'create_time',
+          label: '创建时间',
+          width: 102
         },
-      ] 
+      ]
     }
   },
   mounted() {
-    this.orderData = this.orderDetail?.orderData || {}
-    if(this.orderDetail?.productData && this.orderDetail.productData.length > 0) {
-      this.productData = this.orderDetail.productData.map(product => { 
-        try {
-          const parsedFilter = JSON.parse(product.filter || '{}')
-          const rate = (div(product.final_price, product.original_price) * 100).toFixed(2) + '%'
-          return Object.assign(product, { filter: parsedFilter,  rate});
-        } catch (error) {
-          console.error('JSON 解析失败:', error);
-          return product;
-        }
-      })
-    }
-    this.productData = this.orderDetail?.productData || []
+    this.init()
   },
   computed: {},
   methods: {
+    init() {
+      this.orderData = this.orderDetail?.orderData || {}
+      if(this.orderDetail?.productData && this.orderDetail.productData.length > 0) {
+        this.productData = this.orderDetail.productData.map(product => { 
+          try {
+            const parsedFilter = JSON.parse(product.filter || '{}')
+            // 计算折扣率
+            const rate = (div(product.final_price, product.original_price) * 100).toFixed(2) + '%'
+            // 设置有效周期
+            product.validity_period = this.calculateValidityPeriod(
+              parsedFilter.buy_cycle,
+              parsedFilter.buy_type,
+              parsedFilter.give_cycle,
+              parsedFilter.give_type
+            );
+            // 设置合同金额和标准售价
+            product.final_price = '¥' + (product.final_price / 100).toFixed(2)
+            product.original_price = '¥' + (product.original_price / 100).toFixed(2)
+            return Object.assign(product, { filter: parsedFilter,  rate});
+          } catch (error) {
+            console.error('JSON 解析失败:', error);
+            return product;
+          }
+        })
+      }
+      this.productData = this.orderDetail?.productData || []
+    },
+    calculateValidityPeriod(buyCycle, buyType, giveCycle, giveType) {
+      let totalMonths = 0;
+      let buyText = '';
+      let giveText = '';
+      const TIME_MAP = {
+        '1': '天',
+        '2': '月',
+        '3': '年',
+        '4': '季度'
+      }
+      // 安全地将字符串转为整数,默认为 0
+      const parseCycle = (cycle) => {
+        const num = parseInt(cycle);
+        return isNaN(num) ? 0 : num;
+      };
+      // 计算周期并返回文本和月份数
+      const processCycle = (cycle, type) => {
+        const textValue = TIME_MAP[type] || '';
+        const value = parseCycle(cycle);
+
+        let months = 0;
+        let text = '';
+
+        if (textValue.includes('月')) {
+          months += value;
+          text = `${value}个月`;
+        } else if (textValue.includes('年')) {
+          months += value * 12;
+          text = `${value}年`;
+        } else if (textValue.includes('季度')) {
+          months += value * 3;
+          text = `${value}季度`;
+        } else if (textValue.includes('天')) {
+          months += value / 30; // 注意:此处为近似值
+          text = `${value}天`;
+        }
+
+        return { months, text };
+      };
+      const buyResult = processCycle(buyCycle, buyType);
+      const giveResult = processCycle(giveCycle, giveType);
+      totalMonths = buyResult.months + giveResult.months;
+      buyText = buyResult.text;
+      giveText = giveResult.text;
+
+      return `付费${buyText},赠送${giveText},合计<span>${totalMonths.toFixed(0)}</span>个月(全额回款当日开通)`;
+    },
+    getValidityPeriodHtml(product, item) {
+      const label = item.label;
+      const value = product[item.key] || '-';
+      return `${label}:${value}`;
+    },
     // 替代过滤器的通用方法
     getFilteredValue(value, filterName) {
       if (!filterName || !value) return value || '-';
       return this[filterName](value);
     },
     setProductTitle(product, index) {
-      return `${index + 1}.【售卖】${product.product_type}`;
+      const tactics = product.tactics === '2' ? '赠送' : '【购买】';
+      return `${index + 1}.${tactics}${product.product_type}`;
     },
     orderCoursed(val) {
         if (val == 0) {
@@ -204,11 +276,24 @@ export default {
     align-items: center;
     margin-bottom: 6px;
   }
+  .grouped-items {
+    display: flex;
+    flex-wrap: wrap;
+    margin-bottom: 10px;
+    .item-span-1 {
+      width: 100%;
+    }
+
+    .item-span-3 {
+      min-width: 255px;
+    }
+  }
   .order-detail-product-content {
     display: flex;
     flex-wrap: wrap;
     align-items: center;
     margin-bottom: 6px;
+    padding: 0 24px;
   }
   .order-detail-card-item {
     min-width: 255px;
@@ -217,6 +302,10 @@ export default {
     font-size: 14px;
     line-height: 22px;
     color: $gray_10;
+    .linkedOrder {
+      display: flex;
+      align-items: flex-start;
+    }
   }
 }
 </style>

+ 17 - 1
src/views/create-order/components/product-info-submodule/CheckboxGroup.vue

@@ -14,6 +14,10 @@
 export default {
   name: 'CheckboxGroup',
   props: {
+    multiple: {
+      type: Boolean,
+      default: true
+    },
     value: {
       type: Array,
       default() {
@@ -42,7 +46,19 @@ export default {
   },
   methods: {
     onCheckboxInput(e) {
-      this.$emit('input', e)
+      if (this.multiple) {
+        this.$emit('input', e)
+      } else {
+        if (this.value.length >= 1) {
+          const clicked = this.getDifference(e, this.value)
+          this.$emit('input', clicked)
+        } else {
+          this.$emit('input', e)
+        }
+      }
+    },
+    getDifference(arr1, arr2) {
+      return arr1.filter(item =>!arr2.includes(item));
     },
     getState() {
       return this.value

+ 34 - 13
src/views/create-order/components/product-info-submodule/ServiceList.vue

@@ -55,7 +55,7 @@
       <div class="already-buy-text" v-if="alreadyBuyIds.length > 0">
         <span class="color-orange">注:服务到期时间:{{ serviceEndTime }},</span>
         <span class="font-bold">该客户已购买:</span>
-        <span>{{ nameArr.join('、') }}</span>
+        <span>{{ alreadyBuyNameArr.join('、') }}</span>
       </div>
     </template>
   </div>
@@ -64,6 +64,7 @@
 <script>
 import { selectorVModelMixin } from '@/utils/mixins/selector-v-model'
 import { serveNamesMap } from '@/views/create-order/data/index.js'
+import { cloneDeep } from 'lodash'
 import { mapState } from 'vuex'
 
 export default {
@@ -100,6 +101,7 @@ export default {
       customServiceList: [],
       idArr: [],
       nameArr: [],
+      alreadyBuyNameArr: [],
       serviceList: [
         // {
         //   label: '基础服务',
@@ -321,12 +323,10 @@ export default {
       immediate: true,
       handler(n) {
         this.setDisabledHideService(n)
+        this.alreadyBuyNameArr = this.calcAlreadyBuyServiceNamesArr(this.alreadyBuyIds)
       }
     }
   },
-  created() {
-    window.t = this
-  },
   methods: {
     refreshState() {
       this.setState(this.value)
@@ -411,6 +411,7 @@ export default {
             }
             obj.label = obj.name
             obj.value = obj.id
+            obj.list.forEach(i => i.value = i.id)
             obj.options = obj.list.map(item => {
               return {
                 ...item,
@@ -419,7 +420,8 @@ export default {
                 disabled: item.disabled,
               }
             }).filter(item => !item.hide).sort((a, b) => a.s_count_year - b.s_count_year)
-            obj.optionsValueList = obj.options.map(r => r.value)
+            // obj.optionsValueList = obj.options.map(r => r.value)
+            obj.optionsValueList = obj.list.map(r => r.value)
 
             // 3. 数据获取和赋值问题
 
@@ -439,6 +441,7 @@ export default {
           const baseService = first.ColumnName === '基础服务' // 基础服务默认勾选且不可修改
           children.forEach(e => {
             e.selected = e.id // 默认选中第一个
+            e.hide = false
             if (e.id === 1) {
               e.checked = true
               e.disabled = true
@@ -454,6 +457,7 @@ export default {
             value: first.ColumnName,
             disabled: baseService,
             checked: baseService,
+            hide: false,
             indeterminate: false,
             childrenValue: [],
             children,
@@ -509,13 +513,13 @@ export default {
       this.$emit('change', payload)
     },
     // 根据组件数据结构获取当前被选中的项
-    getSelected() {
+    getSelected(serviceList = this.serviceList) {
       const map = {
         selectedInfoList: [], // 包含disabled的被选中子项
         selectedNoDisabledList: [], // 不包含disabled的被选中子项
         selectedDisabledList: [], // 仅包含被禁用且未被选中的子项
       }
-      this.serviceList.forEach(service => {
+      serviceList.forEach(service => {
         service.children.forEach(serve => {
           if (serve.checked) {
             // 被选中
@@ -531,14 +535,22 @@ export default {
       })
       return map
     },
+    calcAlreadyBuyServiceNamesArr(ids = []) {
+      if (ids.length <= 0) return []
+      const shadowServiceList = cloneDeep(this.serviceList)
+      this.setState(ids, shadowServiceList)
+      const { selectedInfoList } = this.getSelected(shadowServiceList)
+      const { nameArr } = this.calcSelectedArrIdNames(selectedInfoList)
+      return nameArr
+    },
     // 计算选中列表的id数组(用来传参)和name数组(用来展示)
     calcSelectedArrIdNames(selectedInfoList) {
       const selectedInfoArr = []
       // 默认选中 [1,3]
       selectedInfoList.forEach(s => {
         const selectedId = s.selected
-        if (Array.isArray(s.options)) {
-          s.options.forEach(item => {
+        if (Array.isArray(s.list)) {
+          s.list.forEach(item => {
             if (item.id === selectedId) {
               selectedInfoArr.push(item)
             }
@@ -631,17 +643,26 @@ export default {
       })
       return findIt
     },
-    setState(selectedIds = this.defaultSelectedIds) {
+    setState(selectedIds = this.defaultSelectedIds, serviceList = this.serviceList) {
       const target = selectedIds
       if (!Array.isArray(target)) return
-      this.serviceList.forEach(service => {
+      serviceList.forEach(service => {
         const childrenValue = []
         service.children.forEach(serve => {
-          const t = serve.options.find(item => target.includes(item.value))
+          // 倒着找,同时有2种options中的权限时候,优先选中(展示)比较大的那个
+          const t = serve.options.findLast(item => target.includes(item.id))
           if (t) {
             childrenValue.push(serve.value)
             serve.checked = true
             serve.selected = t.value
+          } else {
+            // 自定义备选项找不到,找原始list列表。因为有可能是套餐特有的id需要选中回显
+            const t2 = serve.list.findLast(item => target.includes(item.id))
+            if (t2) {
+              childrenValue.push(serve.value)
+              serve.checked = true
+              serve.selected = t2.value
+            }
           }
         })
         service.childrenValue = [...new Set(childrenValue)]
@@ -655,7 +676,7 @@ export default {
       if (target.length === 0) return
       this.serviceList.forEach(service => {
         service.children.forEach(serve => {
-          const t = serve.options.find(item => target.includes(item.value))
+          const t = serve.list.find(item => target.includes(item.value))
           if (t) {
             serve.disabled = true
             serve.hide = true

+ 29 - 7
src/views/create-order/components/schema-form/schema-form.vue

@@ -20,6 +20,8 @@ import { schemaKeyMap, productKeyMap, productGroupKeyMap, productTypeMap } from
 import { cloneDeep, debounce } from 'lodash'
 import { deepEqual } from '@/utils/object'
 
+const dhy001 = 'dyh001'
+
 // 深对比两个对象,找出值不相同的字段的 key 并以数组形式返回
 function deepCompareObjects(newVal, oldVal) {
   const differentKeys = [];
@@ -752,27 +754,28 @@ export default {
 
       const { [schemaKeyMap.specification]: specification } = value
       const { [schemaKeyMap.payment]: payment } = value
-      ma.show = specification === 'dyh001'  // 自定义规格才展示
+      ma.show = specification === dhy001  // 自定义规格才展示
       
       // 1.付费类型为“续费”,则禁用服务列表全部,不支持修改;
       // 2.如果是升级,则禁用已选的
       if (payment === 1) {
         // 购买,恢复,不禁用
         this.$set(ma.props, 'disabled', false)
-        this.$set(ma.props, 'disabledService', [])
+        this.$set(ma.props, 'alreadyBuyIds', [])
       } else if (payment === 2) {
         // 付费类型为“续费”,则禁用服务列表全部,不支持修改;
         this.$set(ma.props, 'disabled', true)
-        this.$set(ma.props, 'disabledService', [])
+        this.$set(ma.props, 'alreadyBuyIds', [])
       } else if (payment === 3) {
         // 2.如果是升级,则禁用已选的
         let disabledService = []
-        this.$set(ma.props, 'disabled', false)
         const t = this.selectedRelatedOrder
         if (t && Array.isArray(t.serviceList)) {
           disabledService = t.serviceList
         }
-        this.$set(ma.props, 'disabledService', disabledService)
+        this.$set(ma.props, 'disabled', false)
+        this.$set(ma.props, 'serviceEndTime', t.serviceEndTimeText || '')
+        this.$set(ma.props, 'alreadyBuyIds', disabledService)
       }
     },
     // 检查code是否是当前产品的规格
@@ -821,7 +824,8 @@ export default {
         if (upgradeContent.length === 1 && upgradeContent.includes(2)) {
           // 仅为增购子账号:默认为关联订单的产品规格,其他产品规格不展示;
           this.$set(ma.props, 'onlyShowSelected', true)
-        } else if (!upgradeContent.includes(2)) {
+          this.$set(ma.props, 'disabled', true)
+        } else if (upgradeContent.includes(1)) {
           // 有“补充服务”或为空:仅可选择不低于关联订单的产品规格,其他产品规格不展示;
           this.$set(ma.props, 'onlyShowSelected', false)
           this.$set(ma.props, 'disabled', false)
@@ -879,7 +883,25 @@ export default {
     dhyChangeSpecificationSchema() {
       // (注:特殊处理,大会员自定义版默认展示),其他产品规格不展示。
       const ma = this.getSchemaItemWithKey(schemaKeyMap.specification)
-
+      // const { [schemaKeyMap.specification]: spec } = this.value
+      const { [schemaKeyMap.payment]: payment } = this.value
+      if (payment === 3) {
+        // 续费+升级服务(在commonChangeSpecificationSchema中已经判断):默认切换到自定义
+        this.refreshValue({ [schemaKeyMap.specification]: dhy001 })
+        // 禁用其他服务
+        this.$set(ma.props, 'disabled', true)
+        // 后续禁用在serviceList中判断
+        const sv = this.getSchemaItemWithKey(schemaKeyMap.serviceList)
+        if (!sv) {
+          return console.error('没找到schema: ', schemaKeyMap.serviceList)
+        }
+        const t = this.selectedRelatedOrder
+        if (t && Array.isArray(t.serviceList)) {
+          // this.$set(sv.props, '', t.serviceList)
+          // this.$set(sv.props, '', t.serviceList)
+        }
+        this.$set(ma.props, 'onlyShowSelected', true)
+      }
     },
     onChangedFields(changedArr) {
       if (this.checkNeedRefreshPrice(changedArr)) {

+ 22 - 1
src/views/create-order/ui/TableCard.vue

@@ -1,6 +1,7 @@
 <template>
   <div class="table-card">
     <el-table
+      class="table-card-table"
       border
       :data="tableData"
       :style="{ width: width }"
@@ -9,7 +10,8 @@
         v-for="(item, index) in columns"
         :key="index"
         :prop="item.prop"
-        :label="item.label">
+        :label="item.label"
+        :width="item.width">
       </el-table-column>
     </el-table>
   </div>
@@ -34,4 +36,23 @@ export default {
 }
 </script>
 <style lang="scss" scoped>
+.table-card {
+  ::v-deep{
+    .cell {
+      text-align: center;
+      font-size: 14px;
+      font-weight: 400;
+      color: $gray_10;
+    }
+    .has-gutter {
+      th {
+        background: #F9FAFB;
+        .cell {
+          padding: 0;
+          color: #686868;
+        }
+      }
+    }
+  }
+}
 </style>