瀏覽代碼

Merge remote-tracking branch 'origin/master'

zyy 1 周之前
父節點
當前提交
e339060440
共有 59 個文件被更改,包括 4781 次插入238 次删除
  1. 3 0
      .env.prod-hcl
  2. 3 3
      .env.prod-hsyy
  3. 9 3
      .env.prod-mengniu
  4. 44 0
      .env.prod-sxtb
  5. 1 0
      package.json
  6. 70 0
      src/api/crm/business.js
  7. 53 0
      src/api/crm/extDetail.js
  8. 28 0
      src/api/crm/extLog.js
  9. 7 0
      src/api/his/company.js
  10. 8 0
      src/api/his/user.js
  11. 47 0
      src/api/hisStore/orderRefundReason.js
  12. 39 0
      src/api/hisStore/productDiscount.js
  13. 39 0
      src/api/hisStore/productFlashSale.js
  14. 53 0
      src/api/hisStore/refundReason.js
  15. 8 1
      src/api/hisStore/storeAfterSales.js
  16. 10 0
      src/api/hisStore/userEndCategory.js
  17. 8 0
      src/api/system/config.js
  18. 53 0
      src/api/system/show.js
  19. 二進制
      src/assets/logo/bjzmlxsh.png
  20. 二進制
      src/assets/logo/hsyynew.jpg
  21. 二進制
      src/assets/logo/kxwl.png
  22. 二進制
      src/assets/logo/shukang.png
  23. 86 1
      src/views/components/course/userCourseCatalogDetails.vue
  24. 4 4
      src/views/components/course/userCourseCatalogDetailsZM.vue
  25. 43 2
      src/views/course/coursePlaySourceConfig/index.vue
  26. 46 9
      src/views/course/courseQuestionBank/index.vue
  27. 27 9
      src/views/course/userCourse/indexZM.vue
  28. 140 86
      src/views/course/userCoursePeriod/courseStatistics.vue
  29. 110 9
      src/views/course/userCoursePeriod/index.vue
  30. 480 0
      src/views/crm/components/addBusiness.vue
  31. 96 0
      src/views/crm/components/setColumn.vue
  32. 826 0
      src/views/crm/customerBusiness/index.vue
  33. 17 2
      src/views/his/company/index.vue
  34. 17 17
      src/views/his/package/index.vue
  35. 221 3
      src/views/his/user/indexProject.vue
  36. 112 7
      src/views/hisStore/components/productAfterSalesOrder.vue
  37. 28 2
      src/views/hisStore/components/productOrder.vue
  38. 63 3
      src/views/hisStore/integralGoods/index.vue
  39. 528 0
      src/views/hisStore/productDiscount/index.vue
  40. 347 0
      src/views/hisStore/productFlashSale/index.vue
  41. 515 0
      src/views/hisStore/refundReason/index.vue
  42. 2 2
      src/views/hisStore/store/recommend.vue
  43. 4 1
      src/views/hisStore/storeAfterSales/index.vue
  44. 45 11
      src/views/hisStore/storeOrder/dimensionStatistics/index.vue
  45. 13 2
      src/views/hisStore/storeOrder/healthStoreList.vue
  46. 13 1
      src/views/hisStore/storeOrder/index.vue
  47. 26 6
      src/views/hisStore/storeProduct/index.vue
  48. 38 3
      src/views/hisStore/storeProduct/indexZm.vue
  49. 81 7
      src/views/hisStore/userEndCategory/index.vue
  50. 73 23
      src/views/live/components/productAfterSalesOrder.vue
  51. 3 2
      src/views/live/liveAfteraSales/index.vue
  52. 1 1
      src/views/live/liveAnchor/index.vue
  53. 25 0
      src/views/live/liveConfig/liveLotteryConf.vue
  54. 2 2
      src/views/live/liveConfig/liveRedConf.vue
  55. 6 6
      src/views/live/liveCoupon/index.vue
  56. 3 0
      src/views/live/liveOrder/index.vue
  57. 18 4
      src/views/live/liveOrder/liveOrderDetails.vue
  58. 229 6
      src/views/system/config/config.vue
  59. 10 0
      src/views/system/config/integralConfig.vue

+ 3 - 0
.env.prod-hcl

@@ -44,3 +44,6 @@ VUE_APP_COURSE_DEFAULT = 1
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+# 恒春来
+VUE_APP_FS_USER_INFO = 'hcl'

+ 3 - 3
.env.prod-hsyy

@@ -1,7 +1,7 @@
 # 页面标题
-VUE_APP_TITLE =河山医院
+VUE_APP_TITLE =河山恒康
 # 首页菜单标题
-VUE_APP_TITLE_INDEX =河山医院
+VUE_APP_TITLE_INDEX =河山恒康
 # 公司名称
 VUE_APP_COMPANY_NAME =哈尔滨市河山医院有限公司
 # ICP备案号
@@ -9,7 +9,7 @@ VUE_APP_ICP_RECORD =蒙ICP备2024019526号-1
 # ICP网站访问地址
 VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG
-VUE_APP_LOG_URL =@/assets/logo/hsyy.png
+VUE_APP_LOG_URL =@/assets/logo/hsyynew.jpg
 # 存储桶配置
 VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
 # 存储桶配置

+ 9 - 3
.env.prod-mengniu

@@ -1,7 +1,7 @@
 # 页面标题
-VUE_APP_TITLE =蒙牛总管理系统
+VUE_APP_TITLE =康享未来总管理系统
 # 首页菜单标题
-VUE_APP_TITLE_INDEX =蒙牛管理系统
+VUE_APP_TITLE_INDEX =康享未来管理系统
 # 公司名称
 VUE_APP_COMPANY_NAME =重庆云联融智科技有限公司
 # ICP备案号
@@ -9,7 +9,7 @@ VUE_APP_ICP_RECORD =渝ICP备2024031984号-1
 # ICP网站访问地址
 VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG
-VUE_APP_LOG_URL =@/assets/logo/mengniu.png
+VUE_APP_LOG_URL =@/assets/logo/kxwl.png
 # 存储桶配置
 VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
 # 存储桶配置
@@ -32,6 +32,9 @@ VUE_APP_VIDEO_URL = https://mengniuvolcengine.ylrztop.com
 #火山云视频点播空间名
 VUE_APP_HSY_SPACE = mengniu-2114522511
 
+#直播解码路径
+VUE_APP_LIVE_PATH = /live
+
 # 开发环境配置
 ENV = 'development'
 
@@ -43,3 +46,6 @@ VUE_APP_COURSE_DEFAULT = 2
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+#上面地址是为了解决跨越,用nginx进行了转发
+VUE_APP_LIVE_WS_URL = wss://websocket.shukangjiankang.cn/ws

+ 44 - 0
.env.prod-sxtb

@@ -0,0 +1,44 @@
+# 页面标题
+VUE_APP_TITLE =挑宝易购总管理系统
+# 首页菜单标题
+VUE_APP_TITLE_INDEX =挑宝易购
+# 公司名称
+VUE_APP_COMPANY_NAME =西安挑宝益康医药连锁有限公司
+# ICP备案号
+VUE_APP_ICP_RECORD =宁ICP备2022001349号
+# ICP网站访问地址
+VUE_APP_ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/sxjz.png
+# 存储桶配置
+VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
+# 存储桶配置
+VUE_APP_OBS_SECRET_ACCESS_KEY = sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX
+# 存储桶配置
+VUE_APP_OBS_SERVER = https://obs.cn-north-4.myhuaweicloud.com
+# 存储桶配置
+VUE_APP_OBS_BUCKET = sxjz-hw079058881
+# 存储桶配置
+VUE_APP_COS_BUCKET = sxjz-1323137866
+# 存储桶配置
+VUE_APP_COS_REGION = ap-chongqing
+# 线路一地址
+VUE_APP_VIDEO_LINE_1 = https://sxjztcpv.ylrzcloud.com
+# 线路二地址
+VUE_APP_VIDEO_LINE_2 = https://sxjzobs.ylrztop.com
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://sxjzvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = sxjz-2114522511
+
+# 开发环境配置
+ENV = 'development'
+
+# 今正互联网医院管理系统/开发环境
+VUE_APP_BASE_API = '/prod-api'
+
+#默认 1、会员 2、企微
+VUE_APP_COURSE_DEFAULT = 1
+
+# 路由懒加载
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 1 - 0
package.json

@@ -19,6 +19,7 @@
     "build:prod-bly": "vue-cli-service build --mode prod-bly",
     "build:prod-fzbt": "vue-cli-service build --mode prod-fzbt",
     "build:prod-sxjz": "vue-cli-service build --mode prod-sxjz",
+    "build:prod-sxtb": "vue-cli-service build --mode prod-sxtb",
     "build:prod-xfk": "vue-cli-service build --mode prod-xfk",
     "build:prod-jnmy": "vue-cli-service build --mode prod-jnmy",
     "build:prod-knt": "vue-cli-service build --mode prod-knt",

+ 70 - 0
src/api/crm/business.js

@@ -0,0 +1,70 @@
+import request from '@/utils/request'
+
+// 查询商机列表
+export function listBusiness(query) {
+  return request({
+    url: '/crm/business/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询商机详细
+export function getBusiness(businessId) {
+  return request({
+    url: '/crm/business/' + businessId,
+    method: 'get'
+  })
+}
+
+// 新增商机
+export function addBusiness(data) {
+  return request({
+    url: '/crm/business',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改商机
+export function updateBusiness(data) {
+  return request({
+    url: '/crm/business',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除商机
+export function delBusiness(businessId) {
+  return request({
+    url: '/crm/business/' + businessId,
+    method: 'delete'
+  })
+}
+
+// 导出商机
+export function exportBusiness(query) {
+  return request({
+    url: '/crm/business/export',
+    method: 'get',
+    params: query
+  })
+}
+
+//捞取商机
+export function gainBusiness(data) {
+  return request({
+    url: '/crm/business/gain',
+    method: 'post',
+    data: data
+  })
+}
+
+// // 删除商机
+// export function setBusinessPool(businessId) {
+//   return request({
+//     url: '/crm/business/' + businessId,
+//     method: 'get'
+//   })
+// }

+ 53 - 0
src/api/crm/extDetail.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询字段扩展详情列表
+export function listDetail(query) {
+  return request({
+    url: '/crm/detail/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询字段扩展详情详细
+export function getDetail(id) {
+  return request({
+    url: '/crm/detail/' + id,
+    method: 'get'
+  })
+}
+
+// 新增字段扩展详情
+export function addDetail(data) {
+  return request({
+    url: '/crm/detail',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改字段扩展详情
+export function updateDetail(data) {
+  return request({
+    url: '/crm/detail',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除字段扩展详情
+export function delDetail(id) {
+  return request({
+    url: '/crm/detail/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出字段扩展详情
+export function exportDetail(query) {
+  return request({
+    url: '/crm/detail/export',
+    method: 'get',
+    params: query
+  })
+}

+ 28 - 0
src/api/crm/extLog.js

@@ -0,0 +1,28 @@
+import request from '@/utils/request'
+
+// 查询修改字段扩展日志列表
+export function listLog(query) {
+  return request({
+    url: '/crm/log/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询修改字段扩展日志详细
+export function getLog(logId) {
+  return request({
+    url: '/crm/log/' + logId,
+    method: 'get'
+  })
+}
+
+
+// 导出修改字段扩展日志
+export function exportLog(query) {
+  return request({
+    url: '/crm/log/export',
+    method: 'get',
+    params: query
+  })
+}

+ 7 - 0
src/api/his/company.js

@@ -111,3 +111,10 @@ export function exitMiniProgram(data) {
     data: data
   })
 }
+
+export function getGatewayCompanyList(companyId) {
+  return request({
+    url: '/easyCall/gateway/getGatewayCompanyList',
+    method: 'get'
+  })
+}

+ 8 - 0
src/api/his/user.js

@@ -151,3 +151,11 @@ export function batchUnbindUser(data) {
     data: data
   })
 }
+
+export function statisticsList(query) {
+  return request({
+    url: '/his/user/statisticsList',
+    method: 'get',
+    params: query
+  })
+}

+ 47 - 0
src/api/hisStore/orderRefundReason.js

@@ -0,0 +1,47 @@
+import request from '@/utils/request'
+
+export function listOrderRefundReason(query) {
+  return request({
+    url: '/store/store/orderRefundReason/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getOrderRefundReason(id) {
+  return request({
+    url: '/store/store/orderRefundReason/' + id,
+    method: 'get'
+  })
+}
+
+export function getByOrderCode(orderCode) {
+  return request({
+    url: '/store/store/orderRefundReason/getByOrderCode',
+    method: 'get',
+    params: { orderCode }
+  })
+}
+
+export function addOrderRefundReason(data) {
+  return request({
+    url: '/store/store/orderRefundReason',
+    method: 'post',
+    data: data
+  })
+}
+
+export function updateOrderRefundReason(data) {
+  return request({
+    url: '/store/store/orderRefundReason',
+    method: 'put',
+    data: data
+  })
+}
+
+export function delOrderRefundReason(ids) {
+  return request({
+    url: '/store/store/orderRefundReason/' + ids,
+    method: 'delete'
+  })
+}

+ 39 - 0
src/api/hisStore/productDiscount.js

@@ -0,0 +1,39 @@
+import request from '@/utils/request'
+
+export function listProductDiscount(query) {
+  return request({
+    url: '/store/store/productDiscount/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getProductDiscount(id) {
+  return request({
+    url: '/store/store/productDiscount/' + id,
+    method: 'get'
+  })
+}
+
+export function addProductDiscount(data) {
+  return request({
+    url: '/store/store/productDiscount',
+    method: 'post',
+    data: data
+  })
+}
+
+export function updateProductDiscount(data) {
+  return request({
+    url: '/store/store/productDiscount',
+    method: 'put',
+    data: data
+  })
+}
+
+export function delProductDiscount(id) {
+  return request({
+    url: '/store/store/productDiscount/' + id,
+    method: 'delete'
+  })
+}

+ 39 - 0
src/api/hisStore/productFlashSale.js

@@ -0,0 +1,39 @@
+import request from '@/utils/request'
+
+export function listProductFlashSale(query) {
+  return request({
+    url: '/store/store/productFlashSale/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getProductFlashSale(id) {
+  return request({
+    url: '/store/store/productFlashSale/' + id,
+    method: 'get'
+  })
+}
+
+export function addProductFlashSale(data) {
+  return request({
+    url: '/store/store/productFlashSale',
+    method: 'post',
+    data: data
+  })
+}
+
+export function updateProductFlashSale(data) {
+  return request({
+    url: '/store/store/productFlashSale',
+    method: 'put',
+    data: data
+  })
+}
+
+export function delProductFlashSale(id) {
+  return request({
+    url: '/store/store/productFlashSale/' + id,
+    method: 'delete'
+  })
+}

+ 53 - 0
src/api/hisStore/refundReason.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+export function listRefundReason(query) {
+  return request({
+    url: '/store/store/refundReason/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getChildrenByParentId(parentId) {
+  return request({
+    url: '/store/store/refundReason/children/' + parentId,
+    method: 'get'
+  })
+}
+
+export function getRefundReasonTree() {
+  return request({
+    url: '/store/store/refundReason/tree',
+    method: 'get'
+  })
+}
+
+export function getRefundReason(id) {
+  return request({
+    url: '/store/store/refundReason/' + id,
+    method: 'get'
+  })
+}
+
+export function addRefundReason(data) {
+  return request({
+    url: '/store/store/refundReason',
+    method: 'post',
+    data: data
+  })
+}
+
+export function updateRefundReason(data) {
+  return request({
+    url: '/store/store/refundReason',
+    method: 'put',
+    data: data
+  })
+}
+
+export function delRefundReason(id) {
+  return request({
+    url: '/store/store/refundReason/' + id,
+    method: 'delete'
+  })
+}

+ 8 - 1
src/api/hisStore/storeAfterSales.js

@@ -52,7 +52,14 @@ export function exportStoreAfterSales(query) {
   })
 }
 
-
+// 查询售后原因列表
+export function listRefundReason(query) {
+  return request({
+    url: '/store/store/refundReason/list',
+    method: 'get',
+    params: query
+  })
+}
 
 export function cancel(data) {
   return request({

+ 10 - 0
src/api/hisStore/userEndCategory.js

@@ -56,3 +56,13 @@ export function listCategoryProducts(id, params) {
     params: { id, ...params }
   })
 }
+
+/** 批量保存关联排序(当前页商品 productId + sort,id 为用户分端类 ID) */
+export function saveCategoryProductsSort(id, items) {
+  return request({
+    url: '/store/store/userEndCategory/products/sort',
+    method: 'put',
+    params: { id },
+    data: items
+  })
+}

+ 8 - 0
src/api/system/config.js

@@ -89,3 +89,11 @@ export function updateIsTownOn(query) {
     params: query
   })
 }
+
+export function getGatewayList(query) {
+  return request({
+    url: '/easyCall/gateway/list',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/system/show.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询用户展示字段列表
+export function listShow(query) {
+  return request({
+    url: '/system/show/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询用户展示字段详细
+export function getShow(type) {
+  return request({
+    url: '/system/show/' + type,
+    method: 'get'
+  })
+}
+
+// 新增用户展示字段
+export function addShow(data) {
+  return request({
+    url: '/system/show',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改用户展示字段
+export function updateShow(data) {
+  return request({
+    url: '/system/show',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除用户展示字段
+export function delShow(id) {
+  return request({
+    url: '/system/show/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出用户展示字段
+export function exportShow(query) {
+  return request({
+    url: '/system/show/export',
+    method: 'get',
+    params: query
+  })
+}

二進制
src/assets/logo/bjzmlxsh.png


二進制
src/assets/logo/hsyynew.jpg


二進制
src/assets/logo/kxwl.png


二進制
src/assets/logo/shukang.png


+ 86 - 1
src/views/components/course/userCourseCatalogDetails.vue

@@ -118,6 +118,10 @@
           <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
                      v-hasPermi="['course:userCourseVideo:remove']">删除
           </el-button>
+          <el-button size="mini"
+                     type="text" v-if="projectFrom === 'myhk'" @click="openXsyDialog(scope.row)">
+            复制参数
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -514,6 +518,29 @@
         <el-button @click="cancelEditCoverForm">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 销售易参数弹窗 -->
+    <el-dialog
+      title="复制销售易参数"
+      :visible.sync="xsyDialogVisible"
+      width="500px"
+      append-to-body
+    >
+      <div style="margin-bottom: 20px;">
+        <el-input
+          v-model="xsyParams"
+          type="textarea"
+          :rows="3"
+          readonly
+          placeholder="参数将显示在这里"
+          class="xsy-textarea"
+        />
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="xsyDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="copyXsyParams">复制参数</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -589,6 +616,7 @@ export default {
       open: false,
       uploadUrl: process.env.VUE_APP_BASE_API + "/common/uploadOSS",
       baseUrl: process.env.VUE_APP_BASE_API,
+      projectFrom:process.env.VUE_APP_PROJECT,
       typeOptions: [],
       files: [],
       fileList: [],
@@ -698,7 +726,10 @@ export default {
             {required: true, message: "视频封面不能为空", trigger: "change"}
           ],
         }
-      }
+      },
+      // 销售易弹窗
+      xsyDialogVisible: false,
+      xsyParams: '',
     }
   },
   created() {
@@ -1445,6 +1476,56 @@ export default {
         thumbnail: null,
       }
     },
+    // 复制销售易链接
+    openXsyDialog(row) {
+      // 构建JSON字符串
+      const courseData = {
+        videoId: row.videoId,
+        courseId: row.courseId,
+        companyId: 0
+      };
+
+      this.xsyParams = `pages_course/videoxsy?course=${JSON.stringify(courseData)}`;
+      this.xsyDialogVisible = true;
+    },
+    // 复制销售易参数
+    copyXsyParams() {
+      this.copyLink(this.xsyParams);
+    },
+    copyLink(url) {
+      const link = url;
+      // navigator clipboard 需要https等安全上下文
+      if (navigator.clipboard && window.isSecureContext) {
+        // navigator clipboard 向剪贴板写文本
+        navigator.clipboard.writeText(link).then(() => {
+          this.$message.success('链接已复制到剪贴板');
+        });
+      } else {
+        // document.execCommand('copy') 向剪贴板写文本
+        let input = document.createElement('input')
+        input.style.position = 'fixed'
+        input.style.top = '-10000px'
+        input.style.zIndex = '-999'
+        document.body.appendChild(input)
+        input.value = link
+        input.focus()
+        input.select()
+        try {
+          let result = document.execCommand('copy')
+          document.body.removeChild(input)
+          if (!result || result === 'unsuccessful') {
+            this.$message.error('复制失败');
+            console.log('复制失败')
+          } else {
+            this.$message.success('链接已复制到剪贴板');
+            console.log('复制成功')
+          }
+        } catch (e) {
+          document.body.removeChild(input)
+          alert('当前浏览器不支持复制功能,请检查更新或更换其他浏览器操作')
+        }
+      }
+    },
 
   }
 }
@@ -1455,6 +1536,10 @@ export default {
   align-items: center;
   justify-content: center;
 }
+.xsy-textarea >>> .el-textarea__inner {
+ background-color: #f5f5f5 !important;
+ resize: none !important;
+}
 </style>
 <style>
 .avatar-uploader .el-upload {

+ 4 - 4
src/views/components/course/userCourseCatalogDetailsZM.vue

@@ -944,8 +944,8 @@ export default {
         }
       }
 
-      // 验证:当修改弹出时间时,需要验证关闭时间
-      if (fieldName === 'cardPopupTime' && product.cardCloseTime) {
+      // 验证:当修改弹出时间时,需要验证关闭时间(若卡片关闭时间为 00:00:00 则不校验关闭>弹出)
+      if (fieldName === 'cardPopupTime' && product.cardCloseTime && product.cardCloseTime !== '00:00:00') {
         const popupSeconds = this.timeToSeconds(value || '00:00:00');
         const closeSeconds = this.timeToSeconds(product.cardCloseTime);
         const diffSeconds = closeSeconds - popupSeconds;
@@ -986,8 +986,8 @@ export default {
         }
       }
 
-      // 验证:当修改上架时间时,需要验证下架时间
-      if (fieldName === 'onShelfTime' && product.offShelfTime) {
+      // 验证:当修改上架时间时,需要验证下架时间(若下架时间为 00:00:00 则不校验关闭>开启)
+      if (fieldName === 'onShelfTime' && product.offShelfTime && product.offShelfTime !== '00:00:00') {
         const onShelfSeconds = this.timeToSeconds(value || '00:00:00');
         const offShelfSeconds = this.timeToSeconds(product.offShelfTime);
         const diffSeconds = offShelfSeconds - onShelfSeconds;

+ 43 - 2
src/views/course/coursePlaySourceConfig/index.vue

@@ -152,6 +152,11 @@
           <dict-tag  :options="typesOptions" :value="scope.row.type"/>
         </template>
       </el-table-column>
+      <el-table-column label="授权方式" align="center" prop="authType">
+        <template slot-scope="scope">
+          <dict-tag  :options="authTypeOptions" :value="scope.row.authType"/>
+        </template>
+      </el-table-column>
       <el-table-column label="互医/商城小程序" align="center" prop="isMall" width="80px">
         <template slot-scope="scope">
           <el-tag prop="isMall" v-for="(item, index) in isMallOptions" v-if="scope.row.isMall==item.dictValue">{{item.dictLabel}}</el-tag>
@@ -309,6 +314,17 @@
         <el-form-item label="图标" prop="img">
           <image-upload v-model="form.img" :file-type='["png", "jpg", "jpeg"]' :limit="1"/>
         </el-form-item>
+        <el-form-item label="授权方式" prop="authType">
+          <el-select
+            v-model="form.authType"
+            placeholder="请选择授权方式">
+            <el-option
+              v-for="item in authTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"/>
+          </el-select>
+        </el-form-item>
         <el-form-item label="类型" prop="type">
           <el-select
             v-model="form.type"
@@ -341,7 +357,7 @@
 
       </el-form>
       <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button type="primary" @click="showOpenPlatformWarning">确 定</el-button>
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
@@ -437,6 +453,7 @@ export default {
     return {
       sysPayModes: [],
       bindCurrentRow: {},
+      authTypeOptions: [],
       bindForm:{
         bindTitle: '绑定支付配置',
         bindShow: false,
@@ -516,6 +533,7 @@ export default {
       title: null,
       open: false,
       form: {
+        authType:'1',
         setCompanyIdList: []
       },
       bindRules:{
@@ -561,6 +579,9 @@ export default {
     }
   },
   created() {
+    this.getDicts("play_config_auth_type").then(response => {
+      this.authTypeOptions = response.data;
+    });
     this.getDicts("sys_pay_mode").then(response => {
       this.sysPayModes = response.data;
     });
@@ -584,6 +605,25 @@ export default {
       this.companyOptions = [];
       this.open = false;
     },
+    showOpenPlatformWarning() {
+      if (this.form.authType=='1'){
+        this.$confirm('必须绑定开放平台,否则会导致看课冲突。是否继续?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          // 用户点击确定,执行原有提交逻辑
+          this.submitForm()
+        }).catch(() => {
+          // 用户点击取消,停留在当前弹窗,不执行任何操作
+          // 可以在这里添加一些提示信息
+          console.log('用户取消提交')
+        })
+      }else {
+        this.submitForm()
+      }
+
+    },
     // 协议配置处理
     handleAgreement(row) {
       // 获取当前行的协议相关信息
@@ -817,7 +857,8 @@ export default {
       get(id).then(response => {
         this.form = {
           ...response.data,
-          type: response.data.type.toString()
+          type: response.data.type.toString(),
+          authType: response.data.authType ? response.data.authType.toString() : '1'
         }
         if(!!this.form.setCompanyIds){
            this.$set(

+ 46 - 9
src/views/course/courseQuestionBank/index.vue

@@ -148,7 +148,7 @@
         </template>
       </el-table-column>
       <el-table-column label="排序" align="center" prop="sort" />
-      <el-table-column label="答案" align="center" prop="answer" />
+      <el-table-column v-if="!hideAnswerFields" label="答案" align="center" prop="answer" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
@@ -179,7 +179,7 @@
 
     <!-- 添加或修改题库对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+      <el-form ref="form" :model="form" :rules="dynamicRules" label-width="80px">
         <el-form-item label="标题" prop="title">
           <el-input v-model="form.title" placeholder="请输入问题" />
         </el-form-item>
@@ -239,7 +239,7 @@
                 <el-input v-model="scope.row.name" :placeholder="getOptionLabel(scope.$index) + ':' + '请输入标题'" ></el-input>
               </template>
             </el-table-column>
-            <el-table-column label="是否为答案" prop="isWrite" >
+            <el-table-column v-if="!hideAnswerFields" label="是否为答案" prop="isWrite" >
               <template slot-scope="scope">
                 <el-tooltip
                   v-if="!scope.row.name"
@@ -272,12 +272,10 @@
             </el-table-column>
           </el-table>
         </el-form-item>
-        <el-form-item label="答案:" prop="answer">
-          <template slot-scope="scope">
-            <span style="background-color: #faedc9; font-size: 20px;">
-              {{ form.answer }}
-            </span>
-          </template>
+        <el-form-item v-if="!hideAnswerFields" label="答案:" prop="answer">
+          <span style="background-color: #faedc9; font-size: 20px;">
+            {{ form.answer }}
+          </span>
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
@@ -338,6 +336,7 @@ import {
   getCatePidList,
   getCateListByPid
 } from '@/api/course/userCourseCategory'
+import { getConfigByKey } from '@/api/system/config'
 
 export default {
   name: "CourseQuestionBank",
@@ -345,6 +344,22 @@ export default {
     alphabet() {
       return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
     },
+    /** 点播配置:不校验答案时隐藏列表/表单中的答案相关项(与 validateAnswerWhenWatch 为 false / "0" 一致) */
+    hideAnswerFields() {
+      const cfg = this.courseConfig;
+      if (!cfg || !Object.prototype.hasOwnProperty.call(cfg, 'validateAnswerWhenWatch')) {
+        return false;
+      }
+      const v = cfg.validateAnswerWhenWatch;
+      return v === false || v === '0' || v === 0 || v === 'false';
+    },
+    dynamicRules() {
+      const r = { ...this.rules };
+      if (this.hideAnswerFields) {
+        delete r.answer;
+      }
+      return r;
+    },
   },
   watch: {
   },
@@ -352,6 +367,8 @@ export default {
   },
   data() {
     return {
+      /** course.config 解析后的对象 */
+      courseConfig: {},
       exportFailLoading: false,
       //单选
       selectedAnswer:null,
@@ -433,6 +450,15 @@ export default {
   },
   created() {
     this.getList();
+    getConfigByKey('course.config').then(response => {
+      if (response.data && response.data.configValue) {
+        try {
+          this.courseConfig = JSON.parse(response.data.configValue) || {};
+        } catch (e) {
+          this.courseConfig = {};
+        }
+      }
+    }).catch(() => { this.courseConfig = {}; });
     this.getDicts("sys_course_temp_type").then(response => {
       this.typeOptions = response.data;
     });
@@ -631,6 +657,17 @@ export default {
             return
           }
 
+          if (this.hideAnswerFields) {
+            this.question.forEach(q => { q.isAnswer = 0 });
+            this.selectedAnswer = null;
+            this.selectedAnswers = [];
+            if (this.form.type === 2) {
+              this.form.answer = [];
+            } else {
+              this.form.answer = '';
+            }
+          }
+
           this.form.question=JSON.stringify(this.question)
 
           if (this.form.type===2){

+ 27 - 9
src/views/course/userCourse/indexZM.vue

@@ -263,15 +263,25 @@
         <el-form-item label="课程封面" prop="imgUrl">
           <ImageUpload v-model="form.imgUrl" type="image" :num="10" :width="150" :height="150"/>
         </el-form-item>
-        <el-form-item label="关联公司" prop="tags">
-          <el-select v-model="companyIds" multiple placeholder="请选择公司" filterable clearable style="width: 90%;">
-            <el-option
-              v-for="dict in companyOptions"
-              :key="dict.dictValue"
-              :label="dict.dictLabel"
-              :value="dict.dictValue"
-            />
-          </el-select>
+        <el-form-item label="关联公司aa" prop="tags">
+          <div style="display: flex; align-items: center;">
+            <el-select v-model="companyIds" multiple placeholder="请选择公司" filterable clearable style="flex: 1; margin-right: 10px;">
+              <el-option
+                v-for="dict in companyOptions"
+                :key="dict.dictValue"
+                :label="dict.dictLabel"
+                :value="dict.dictValue"
+              />
+            </el-select>
+            <el-button
+              type="primary"
+              size="small"
+              @click="selectAllCompanies('form')"
+              :disabled="companyOptions.length === 0"
+            >
+              全选
+            </el-button>
+          </div>
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
@@ -718,6 +728,14 @@ export default {
     });
   },
   methods: {
+    selectAllCompanies(type) {
+      const allCompanyIds = this.companyOptions.map(company => company.dictValue);
+      if (type === 'query') {
+        this.queryParams.companyIdsList = allCompanyIds;
+      } else if (type === 'form') {
+        this.companyIds = allCompanyIds;
+      }
+    },
     selectTalent() {
 
     },

+ 140 - 86
src/views/course/userCoursePeriod/courseStatistics.vue

@@ -27,13 +27,6 @@
     >
       <el-table-column label="课程名称" align="center" prop="courseName" width="200" />
       <el-table-column label="小节" align="center" prop="videoName" width="210" />
-<!--      <el-table-column label="小节状态" align="center" prop="videoStatus" width="120">-->
-<!--        <template slot-scope="scope">-->
-<!--          <el-tag :type="scope.row.videoStatus === '已开课' ? 'success' : scope.row.videoStatus === '已结束' ? 'info' : 'warning'">-->
-<!--            {{ scope.row.videoStatus }}-->
-<!--          </el-tag>-->
-<!--        </template>-->
-<!--      </el-table-column>-->
       <el-table-column label="开课状态" align="center" prop="openStatus" width="120">
         <template slot-scope="scope">
           <el-tag :type="scope.row.openStatus === '已开课' ? 'success' : scope.row.openStatus === '已结束' ? 'info' : 'warning'">
@@ -112,78 +105,84 @@
         </el-card>
 
         <!-- 第二块:首次点播数据 -->
+<!--        <el-card class="detail-card" shadow="never">-->
+<!--          <div slot="header" class="card-header">-->
+<!--            <span>首次点播数据</span>-->
+<!--          </div>-->
+<!--          <el-row :gutter="20">-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">观看人数</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstWatchCount || 0 }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">>=20分钟人数(首次)</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstWatch20MinCount || 0 }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">>=30分钟人数(首次)</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstWatch30MinCount || 0 }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">到课完课率首次(>=20分钟)</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstCompleteRate20Min || '0%' }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">到课完课率首次(>=30分钟)</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstCompleteRate30Min || '0%' }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--          </el-row>-->
+<!--        </el-card>-->
+
+        <!-- 第三块:实际看课数据(修复后) -->
         <el-card class="detail-card" shadow="never">
           <div slot="header" class="card-header">
-            <span>首次点播数据</span>
+            <span>实际看课数据</span>
           </div>
           <el-row :gutter="20">
             <el-col :span="6">
               <div class="stat-item">
-                <div class="stat-label">观看人数</div>
-                <div class="stat-value">{{ detailDialog.data.firstWatchCount || 0 }}</div>
+                <div class="stat-label">实际到课人数</div>
+                <div class="stat-value">{{ detailDialog.data.totalStudents || 0 }}</div>
               </div>
             </el-col>
             <el-col :span="6">
               <div class="stat-item">
-                <div class="stat-label">>=20分钟人数(首次)</div>
-                <div class="stat-value">{{ detailDialog.data.firstWatch20MinCount || 0 }}</div>
+                <div class="stat-label">实际完课人数</div>
+                <div class="stat-value">{{ detailDialog.data.completedCount || 0 }}</div>
               </div>
             </el-col>
             <el-col :span="6">
               <div class="stat-item">
-                <div class="stat-label">>=30分钟人数(首次)</div>
-                <div class="stat-value">{{ detailDialog.data.firstWatch30MinCount || 0 }}</div>
+                <div class="stat-label">实际完课率</div>
+                <div class="stat-value">{{ detailDialog.data.actualCompletionRate ? detailDialog.data.actualCompletionRate + '%' : '0%' }}</div>
               </div>
             </el-col>
             <el-col :span="6">
               <div class="stat-item">
-                <div class="stat-label">到课完课率首次(>=20分钟)</div>
-                <div class="stat-value">{{ detailDialog.data.firstCompleteRate20Min || '0%' }}</div>
+                <div class="stat-label">人均看课时长</div>
+                <div class="stat-value">{{ formatDuration(detailDialog.data.avgWatchDurationMinutes)}}</div>
               </div>
             </el-col>
             <el-col :span="6">
               <div class="stat-item">
-                <div class="stat-label">到课完课率首次(>=30分钟)</div>
-                <div class="stat-value">{{ detailDialog.data.firstCompleteRate30Min || '0%' }}</div>
+                <div class="stat-label">人均完课时长</div>
+                <div class="stat-value">{{ formatDuration(detailDialog.data.avgCompletedDuration)}}</div>
               </div>
             </el-col>
-          </el-row>
-        </el-card>
-
-        <!-- 第三块:第2-n次观看数据 -->
-        <el-card class="detail-card" shadow="never">
-          <div slot="header" class="card-header">
-            <span>第2-n次观看数据</span>
-          </div>
-          <el-row :gutter="20">
             <el-col :span="6">
               <div class="stat-item">
-                <div class="stat-label">观看人数</div>
-                <div class="stat-value">{{ detailDialog.data.repeatWatchCount || 0 }}</div>
-              </div>
-            </el-col>
-            <el-col :span="6">
-              <div class="stat-item">
-                <div class="stat-label">>=20分钟人数</div>
-                <div class="stat-value">{{ detailDialog.data.repeatWatch20MinCount || 0 }}</div>
-              </div>
-            </el-col>
-            <el-col :span="6">
-              <div class="stat-item">
-                <div class="stat-label">>=30分钟人数</div>
-                <div class="stat-value">{{ detailDialog.data.repeatWatch30MinCount || 0 }}</div>
-              </div>
-            </el-col>
-            <el-col :span="6">
-              <div class="stat-item">
-                <div class="stat-label">到课完课率2-n次(>=20分钟)</div>
-                <div class="stat-value">{{ detailDialog.data.repeatCompleteRate20Min || '0%' }}</div>
-              </div>
-            </el-col>
-            <el-col :span="6">
-              <div class="stat-item">
-                <div class="stat-label">到课完课率2-n次(>=30分钟)</div>
-                <div class="stat-value">{{ detailDialog.data.repeatCompleteRate30Min || '0%' }}</div>
+                <div class="stat-label">人均完课完播率</div>
+                <div class="stat-value">{{ detailDialog.data.avgCompletionPlaybackRate ? detailDialog.data.avgCompletionPlaybackRate + '%' : '0%' }}</div>
               </div>
             </el-col>
           </el-row>
@@ -233,7 +232,7 @@
             </el-col>
             <el-col :span="6">
               <div class="stat-item">
-                <div class="stat-label">领红包人数</div>
+                <div class="stat-label">领红包/积分人数</div>
                 <div class="stat-value">{{ detailDialog.data.redPacketUserCount || 0 }}</div>
               </div>
             </el-col>
@@ -249,7 +248,7 @@
         <!-- 第五块:单品销量统计 -->
         <el-card class="detail-card" shadow="never">
           <div slot="header" class="card-header">
-            <span>品销量统计</span>
+            <span>品销量统计</span>
           </div>
           <el-table
             :data="detailDialog.data.productList || []"
@@ -287,12 +286,12 @@
           style="width: 100%"
         >
           <el-table-column label="用户名称" align="center" prop="userName" width="150" />
-          <el-table-column label="观看时长" align="center" prop="watchDuration" width="120">
-            <template slot-scope="scope">
-              {{ formatDuration(scope.row.watchDuration) }}
-            </template>
-          </el-table-column>
-          <el-table-column label="第2-n次观看时长" align="center" prop="repeatWatchDuration" width="150">
+<!--          <el-table-column label="观看时长" align="center" prop="watchDuration" width="120">-->
+<!--            <template slot-scope="scope">-->
+<!--              {{ formatDuration(scope.row.watchDuration) }}-->
+<!--            </template>-->
+<!--          </el-table-column>-->
+          <el-table-column label="观看时长" align="center" prop="repeatWatchDuration" width="150">
             <template slot-scope="scope">
               {{ formatDuration(scope.row.repeatWatchDuration) }}
             </template>
@@ -304,7 +303,18 @@
             </template>
           </el-table-column>
           <el-table-column label="分公司名称" align="center" prop="companyName" width="150" />
-          <el-table-column label="销售名称" align="center" prop="salesName" />
+          <el-table-column label="销售名称" align="center" prop="salesName" width="120" />
+          <el-table-column
+            label="课程评分"
+            align="center"
+            prop="courseRating"
+            min-width="260"
+            show-overflow-tooltip
+          >
+            <template slot-scope="scope">
+              <span class="course-rating-cell">{{ formatCourseRating(scope.row.courseRating) }}</span>
+            </template>
+          </el-table-column>
         </el-table>
 
         <!-- 分页 -->
@@ -452,7 +462,6 @@ export default {
       this.detailDialog.visible = true;
       this.detailDialog.loading = true;
 
-      // 调用API获取总体数据
       const videoId = row.videoId || row.id;
       const periodId = this.queryParams.periodId;
 
@@ -465,25 +474,30 @@ export default {
       getCourseStatisticsDetail(videoId, periodId).then(response => {
         if (response.code === 200 && response.data) {
           const data = response.data;
+          // 安全获取实际看课数据对象
+          const actualVO = data.fsActualCompletionVO || {};
+
           // 设置总体数据
           this.detailDialog.data = {
-            videoDuration: data.videoDuration || 0,
-            totalWatchCount: data.totalWatchCount || 0,
-            totalCompleteCount: data.totalCompleteCount || 0,
+            // 总体数据
+            videoDuration: data.videoDuration ?? 0,
+            totalWatchCount: data.totalWatchCount ?? 0,
+            totalCompleteCount: data.totalCompleteCount ?? 0,
             completeRate: data.completeRate != null ? Number(data.completeRate).toFixed(2) + '%' : '0%',
-            // 首次点播数据(接口返回)
+            // 首次点播数据
             firstWatchCount: data.firstWatchCount ?? 0,
             firstWatch20MinCount: data.firstWatch20MinCount ?? 0,
             firstWatch30MinCount: data.firstWatch30MinCount ?? 0,
             firstCompleteRate20Min: data.firstCompleteRate20Min != null ? Number(data.firstCompleteRate20Min).toFixed(2) + '%' : '0%',
             firstCompleteRate30Min: data.firstCompleteRate30Min != null ? Number(data.firstCompleteRate30Min).toFixed(2) + '%' : '0%',
-            // 第2-n次观看数据(接口返回)
-            repeatWatchCount: data.repeatWatchCount ?? 0,
-            repeatWatch20MinCount: data.repeatWatch20MinCount ?? 0,
-            repeatWatch30MinCount: data.repeatWatch30MinCount ?? 0,
-            repeatCompleteRate20Min: data.repeatCompleteRate20Min != null ? Number(data.repeatCompleteRate20Min).toFixed(2) + '%' : '0%',
-            repeatCompleteRate30Min: data.repeatCompleteRate30Min != null ? Number(data.repeatCompleteRate30Min).toFixed(2) + '%' : '0%',
-            // 订单数据(接口返回)
+            // 实际看课数据(修复后,所有字段都有默认值)
+            totalStudents: actualVO.totalStudents ?? 0,
+            completedCount: actualVO.completedCount ?? 0,
+            actualCompletionRate: actualVO.actualCompletionRate != null ? Number(actualVO.actualCompletionRate).toFixed(2) : '0',
+            avgWatchDurationMinutes: actualVO.avgWatchDurationMinutes ?? 0,
+            avgCompletedDuration: actualVO.avgCompletedDuration ?? 0,
+            avgCompletionPlaybackRate: actualVO.avgCompletionPlaybackRate != null ? Number(actualVO.avgCompletionPlaybackRate).toFixed(2) : '0',
+            // 订单数据
             gmv: data.gmv != null ? Number(data.gmv).toFixed(2) : '0.00',
             paidUserCount: data.paidUserCount ?? 0,
             paidOrderCount: data.paidOrderCount ?? 0,
@@ -529,9 +543,11 @@ export default {
       this.userDetailDialog.loading = true;
       getCourseStatisticsUserDetailList(this.userDetailDialog.queryParams).then(response => {
         if (response.code === 200 && response.data) {
-          const d = response.data;
+          const raw = response.data;
+          // 兼容 PageInfo 直接放在 data,或再包一层 data
+          const d = raw.data != null && raw.list == null && raw.rows == null ? raw.data : raw;
           this.userDetailDialog.list = d.list || d.rows || [];
-          this.userDetailDialog.total = d.total ?? 0;
+          this.userDetailDialog.total = d.total != null ? d.total : 0;
         } else {
           this.userDetailDialog.list = [];
           this.userDetailDialog.total = 0;
@@ -568,19 +584,45 @@ export default {
         });
       }).catch(() => {});
     },
+    /**
+     * 课程评分:后端在关闭「看课校验答案」时返回答题 JSON(courseRating);否则为空
+     */
+    formatCourseRating(val) {
+      if (val == null || val === '') {
+        return '—';
+      }
+      if (typeof val === 'string') {
+        const s = val.trim();
+        if (!s) return '—';
+        try {
+          const parsed = JSON.parse(s);
+          return typeof parsed === 'object' ? JSON.stringify(parsed) : String(val);
+        } catch (e) {
+          return val;
+        }
+      }
+      try {
+        return typeof val === 'object' ? JSON.stringify(val) : String(val);
+      } catch (e) {
+        return String(val);
+      }
+    },
     /** 格式化时长 */
     formatDuration(seconds) {
-      if (!seconds) return '0秒';
-      const hours = Math.floor(seconds / 3600);
-      const minutes = Math.floor((seconds % 3600) / 60);
-      const secs = seconds % 60;
-      if (hours > 0) {
-        return `${hours}小时${minutes}分钟${secs}秒`;
-      } else if (minutes > 0) {
-        return `${minutes}分钟${secs}秒`;
-      } else {
-        return `${secs}秒`;
+      if (seconds == null || isNaN(seconds)) return '0秒';
+      let total = Math.abs(seconds);
+      const hours = Math.floor(total / 3600);
+      const minutes = Math.floor((total % 3600) / 60);
+      const secs = Math.floor(total % 60);
+
+      const parts = [];
+      if (hours > 0) parts.push(`${hours}小时`);
+      if (minutes > 0) parts.push(`${minutes}分`);
+      if (secs > 0 || parts.length === 0) {
+        parts.push(`${secs}秒`);
       }
+
+      return parts.join('');
     }
   }
 };
@@ -629,4 +671,16 @@ export default {
     overflow-y: auto;
   }
 }
+
+.course-rating-cell {
+  display: inline-block;
+  max-width: 100%;
+  text-align: left;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  font-size: 12px;
+  line-height: 1.4;
+  vertical-align: middle;
+}
 </style>

+ 110 - 9
src/views/course/userCoursePeriod/index.vue

@@ -163,7 +163,6 @@
         <el-table v-loading="loading" :data="periodList" @selection-change="handleSelectionChange" border>
           <el-table-column type="selection" width="55" align="center" />
           <el-table-column label="营期名称" align="center" prop="periodName" />
-          <el-table-column label="公司名称" align="center" prop="companyName" />
           <el-table-column label="营期状态" align="center" prop="periodStatus" width="100" :formatter="periodStatusFormatter" />
           <el-table-column label="营期线" align="center" prop="periodLine" width="180" />
           <el-table-column label="开营开始时间" align="center" prop="periodStartingTime" width="180" />
@@ -204,6 +203,12 @@
                 @click="handleClosePeriod(scope.row)"
                 v-hasPermi="['course:period:close']"
               >结束营期</el-button>
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-office-building"
+                @click="openPeriodCompanyCards(scope.row)"
+              >公司列表</el-button>
               <el-button
                 size="mini"
                 type="text"
@@ -225,6 +230,35 @@
       </el-main>
     </el-container>
 
+    <!-- 当前营期关联公司(按行 companyId / companyName 解析) -->
+    <el-dialog
+      :title="periodCompanyCardTitle"
+      :visible.sync="periodCompanyCardVisible"
+      width="720px"
+      append-to-body
+    >
+      <div class="period-company-card-scroll">
+        <el-row v-if="periodCompanyCardList && periodCompanyCardList.length > 0" :gutter="12">
+          <el-col
+            v-for="(item, idx) in periodCompanyCardList"
+            :key="item.companyId != null ? String(item.companyId) + '-' + idx : 'noid-' + idx"
+            :xs="24"
+            :sm="12"
+            :md="8"
+          >
+            <el-card shadow="hover" class="period-company-name-card">
+              <div class="period-company-name-card__title">{{ item.companyName || '—' }}</div>
+              <div v-if="item.companyId != null && item.companyId !== ''" class="period-company-name-card__id">ID:{{ item.companyId }}</div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-empty v-else description="该营期未关联公司" />
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="periodCompanyCardVisible = false">关 闭</el-button>
+      </span>
+    </el-dialog>
+
     <!-- 添加或修改会员营期对话框-->
     <el-drawer :title="title" :visible.sync="open" size="700px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="120px">
@@ -232,14 +266,24 @@
           <el-input v-model="form.periodName" placeholder="请输入营期名称" />
         </el-form-item>
          <el-form-item label="公司" prop="companyId">
-          <el-select v-model="form.companyId" placeholder="请选择公司" multiple filterable>
-            <el-option
-              v-for="item in companyOptions"
-              :key="item.companyId"
-              :label="item.companyName"
-              :value="item.companyId"
-            />
-          </el-select>
+          <div style="display: flex; align-items: center;">
+            <el-select v-model="form.companyId" placeholder="请选择公司" multiple clearable filterable style="flex: 1; margin-right: 10px;">
+              <el-option
+                v-for="item in companyOptions"
+                :key="item.companyId"
+                :label="item.companyName"
+                :value="item.companyId"
+              />
+            </el-select>
+            <el-button
+              type="primary"
+              size="small"
+              @click="selectAllCompanies"
+              :disabled="companyOptions.length === 0"
+            >
+              全选
+            </el-button>
+          </div>
         </el-form-item>
         <el-form-item label="课程风格" prop="courseStyle">
           <image-upload v-model="form.courseStyle" :limit="1" />
@@ -894,6 +938,10 @@ export default {
       batchSetRedPacketDisabled: true,
       // 批量设置红包弹出框
       batchRedPacketVisible: false,
+      // 营期行 — 关联公司卡片弹窗
+      periodCompanyCardVisible: false,
+      periodCompanyCardTitle: '公司列表',
+      periodCompanyCardList: [],
     };
   },
   created() {
@@ -907,6 +955,9 @@ export default {
 
   },
   methods: {
+    selectAllCompanies() {
+      this.form.companyId = this.companyOptions.map(company => company.companyId);
+    },
     selectAllVideos() {
       this.course.form.videoIds = this.videoList.map(video => parseInt(video.dictValue));
     },
@@ -1701,6 +1752,33 @@ export default {
         })
       }).catch(() => {})
     },
+    /** 当前营期行关联公司:解析 companyId、companyName(逗号分隔),卡片展示 */
+    openPeriodCompanyCards(row) {
+      const pname = row.periodName ? String(row.periodName) : ''
+      this.periodCompanyCardTitle = pname ? `公司列表 - ${pname}` : '公司列表'
+      const idStr = row.companyId != null && row.companyId !== '' ? String(row.companyId) : ''
+      const nameStr = row.companyName != null && row.companyName !== '' ? String(row.companyName) : ''
+      const ids = idStr ? idStr.split(',').map(s => s.trim()).filter(s => s !== '') : []
+      const names = nameStr ? nameStr.split(',').map(s => s.trim()) : []
+      const list = []
+      ids.forEach((id, i) => {
+        let companyName = names[i]
+        if (!companyName) {
+          const opt = (this.companyOptions || []).find(c => String(c.companyId) === String(id))
+          companyName = opt ? opt.companyName : ''
+        }
+        const num = Number(id)
+        list.push({
+          companyId: !isNaN(num) ? num : id,
+          companyName: companyName || '—'
+        })
+      })
+      if (list.length === 0 && names.length > 0) {
+        names.forEach(n => list.push({ companyId: null, companyName: n || '—' }))
+      }
+      this.periodCompanyCardList = list
+      this.periodCompanyCardVisible = true
+    },
     handleBatchRedPacketSuccess() {
       this.batchRedPacketVisible = false;
       this.getCourseList();
@@ -2095,4 +2173,27 @@ export default {
   display: flex;
   align-items: center;
 }
+
+.period-company-card-scroll {
+  max-height: 65vh;
+  overflow-y: auto;
+  padding-right: 4px;
+}
+
+.period-company-name-card {
+  margin-bottom: 12px;
+}
+
+.period-company-name-card__title {
+  font-weight: 600;
+  font-size: 14px;
+  line-height: 1.4;
+  word-break: break-all;
+}
+
+.period-company-name-card__id {
+  margin-top: 8px;
+  font-size: 12px;
+  color: #909399;
+}
 </style>

+ 480 - 0
src/views/crm/components/addBusiness.vue

@@ -0,0 +1,480 @@
+<template>
+    <div>
+        <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+            <el-form-item label="线索来源" prop="source">
+                <el-select v-model="form.source" :disabled="form.source !== null" placeholder="请选择线索来源" clearable size="small">
+                <el-option
+                    v-for="item in clueSourceOptions"
+                    :key="item.dictValue"
+                    :label="item.dictLabel"
+                    :value="item.dictValue"
+                />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="客户经理" prop="manager">
+                <el-input v-model="form.manager" placeholder="请输入客户经理" />
+            </el-form-item>
+            <el-form-item label="客户名称" prop="companyName">
+                <el-input v-model="form.companyName" :disabled="form.customerId !== null" placeholder="请输入公司名称(只填公司全称,个人用户就填名字)" />
+            </el-form-item>
+            <el-form-item label="联系电话" prop="mobile">
+                <el-input  v-model="form.mobile" :disabled="form.customerId !== null" placeholder="请输入电话号码"/>
+                <!-- <el-button type="text" @click="showAddContactDialog">添加其他联系电话</el-button> -->
+            </el-form-item>
+            <el-form-item label="其他联系方式">
+                <el-button type="text" @click="showAddContactDialog">添加其他联系电话</el-button>
+            </el-form-item>
+            <el-table border v-loading="loading" :data="mobileList" v-if="mobileList && mobileList.length > 0" >
+                <el-table-column label="姓名" align="center" prop="name" />
+                <el-table-column label="手机" align="center" prop="mobile" />
+                <el-table-column label="备注" align="center" prop="remark" />
+                <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template slot-scope="scope">
+                    <!-- <el-button
+                    size="mini"
+                    type="text"
+                    icon="el-icon-edit"
+                    @click="handleUpdate(scope.row)"
+                    v-hasPermi="['crm:customerContacts:edit']"
+                    >修改</el-button> -->
+                    <el-button
+                    size="mini"
+                    type="text"
+                    icon="el-icon-delete"
+                    @click="handleDelete(scope.row)"
+                    v-hasPermi="['crm:customerContacts:remove']"
+                    v-if="scope.row.contactsId == null"
+                    >删除</el-button>
+                </template>
+                </el-table-column>
+            </el-table>
+
+            <el-form-item label="接口人角色" prop="contactRole">
+                <el-select v-model="form.contactRole" placeholder="请选择" clearable size="small">
+                    <el-option
+                        v-for="item in contactRoleOptions"
+                        :key="item.dictValue"
+                        :label="item.dictLabel"
+                        :value="item.dictValue"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="业务场景" prop="businessScenario">
+                <!-- <el-input v-model="form.businessScenario" placeholder="例:网站,电商,游戏,小程序,APP,OA,ERP,CRM,物联网,AI人工智能,国产化,数字人,内部办公系统,等保,其他" /> -->
+                <el-select v-model="form.businessScenario"  placeholder="请选择线索业务场景" clearable size="small">
+                <el-option
+                    v-for="item in businessScenarioOptions"
+                    :key="item.dictValue"
+                    :label="item.dictLabel"
+                    :value="item.dictValue"
+                />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="意向产品" prop="product">
+                <!-- <el-input v-model="form.product" placeholder="请输入方案涉及产品" /> -->
+                <el-button icon="el-icon-plus" size="mini" @click="addProduct">添加产品</el-button>
+            </el-form-item>
+            <el-form-item label="采购周期" prop="purchaseCycle">
+                <el-input v-model="form.purchaseCycle" placeholder="请输入采购周期(单位:天)" />
+            </el-form-item>
+            <el-form-item label="跟进状态" prop="businessStatus">
+                <el-radio-group v-model="form.businessStatus">
+                    <el-radio label="0">跟进中</el-radio>
+                    <el-radio label="1">已流失</el-radio>
+                    <el-radio label="2">已赢单</el-radio>
+                    <el-radio label="3">待验证</el-radio>
+                </el-radio-group>
+            </el-form-item>
+            <!-- <el-form-item label="沟通进展" prop="remark">
+                <el-input v-model="form.remark" placeholder="请输入沟通进展" />
+            </el-form-item> -->
+            <el-form-item label="项目阶段" prop="projectPhase">
+                <el-select v-model="form.projectPhase" placeholder="请选择" clearable size="small">
+                    <el-option
+                        v-for="item in projectPhaseOptions"
+                        :key="item.dictValue"
+                        :label="item.dictLabel"
+                        :value="item.dictValue"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="意向等级" prop="level">
+                <el-select v-model="form.level" placeholder="请选择" clearable size="small">
+                    <el-option
+                        v-for="item in levelOptions"
+                        :key="item.dictValue"
+                        :label="item.dictLabel"
+                        :value="item.dictValue"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="是否绑定BP账号">
+                <el-radio-group v-model="form.isBp">
+                    <el-radio label="0">未注册</el-radio>
+                    <el-radio label="1">已注册未绑定</el-radio>
+                    <el-radio label="2">不明确</el-radio>
+                    <el-radio label="3">已绑定</el-radio>
+                </el-radio-group>
+            </el-form-item>
+            <!-- <el-form-item label="BP账户" prop="bpAccount">
+                <el-input v-model="form.bpAccount" placeholder="请输入BP账户" />
+            </el-form-item> -->
+            <el-form-item label="预计成单时间" prop="preTime">
+                <el-date-picker clearable size="small" style="width: 200px"
+                    v-model="form.preTime"
+                    type="date"
+                    value-format="yyyy-MM-dd"
+                    placeholder="选择预计成单时间">
+                </el-date-picker>
+            </el-form-item>
+            <el-form-item label="预计成交金额" prop="preMoney">
+                <el-input v-model="form.preMoney" placeholder="请输入预计成交金额(元)" />
+            </el-form-item>
+            <el-form-item label="下次跟进时间" prop="nextTime">
+                <el-date-picker clearable size="small" style="width: 200px"
+                    v-model="form.nextTime"
+                    type="date"
+                    value-format="yyyy-MM-dd"
+                    placeholder="选择下次跟进时间">
+                </el-date-picker>
+            </el-form-item>
+            <!-- <el-form-item label="回收时间" prop="recoveryTime">
+                <el-date-picker clearable size="small" style="width: 200px"
+                    v-model="form.recoveryTime"
+                    type="date"
+                    value-format="yyyy-MM-dd"
+                    placeholder="选择回收时间">
+                </el-date-picker>
+            </el-form-item> -->
+            <!-- <el-form-item label="状态">
+                <el-radio-group v-model="form.status">
+                    <el-radio label="1">已分配</el-radio>
+                    <el-radio label="2">进行中</el-radio>
+                    <el-radio label="3">回收</el-radio>
+                </el-radio-group>
+            </el-form-item> -->
+        </el-form>
+        <div class="footer">
+            <el-button type="primary" @click="submitForm">确 定</el-button>
+        </div>
+
+
+        <el-dialog  :title=title :visible.sync="open" width="500px" append-to-body>
+            <el-form ref="contactForm" :model="contactForm" :rules="contactRules" label-width="120px">
+            <el-form-item label="联系人名称" prop="name">
+                <el-input v-model="contactForm.name" placeholder="请输入联系人名称" />
+            </el-form-item>
+            <el-form-item label="手机" prop="mobile">
+                <el-input v-model="contactForm.mobile" placeholder="请输入手机" />
+            </el-form-item>
+            <el-form-item label="备注" prop="remark">
+                <el-input v-model="contactForm.remark" type="textarea" placeholder="请输入内容" />
+            </el-form-item>
+            </el-form>
+            <div slot="footer" class="dialog-footer">
+                <el-button type="primary" @click="submitContactForm">确 定</el-button>
+                <el-button @click="cancelContact">取 消</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+    import { addBusiness } from "@/api/crm/business";
+    import { listCustomerContacts, getCustomerContacts, addCustomerContacts, exportCustomerContacts } from "@/api/crm/customerContacts";
+    export default {
+        name: "business",
+        data() {
+            return {
+                // 弹出层标题
+                title: "",
+                // 是否显示弹出层
+                open: false,
+                // 遮罩层
+                loading: true,
+                mobileList:[],
+                // dialogVisible:false,
+                //联系方式表格
+                contactForm:{
+                    contactsId:null,
+                    customerId:null,
+                    businessId:null,
+                    name:null,
+                    mobile:null,
+                    remark:null
+                },
+                businessScenarioOptions:[],
+                clueSourceOptions:[],
+                contactRoleOptions:[],
+                projectPhaseOptions:[],
+                levelOptions:[],
+                form: {
+                    businessId: null,
+                    customerId: null,
+                    source: null,
+                    manager: null,
+                    companyName: null,
+                    mobile: null,
+                    mobileList:[],
+                    contactRole: null,
+                    businessScenario: null,
+                    product: null,
+                    purchaseCycle: null,
+                    businessStatus: "0",
+                    remark: null,
+                    projectPhase: null,
+                    level: null,
+                    isBp: '0',
+                    bpAccount: null,
+                    preTime: null,
+                    preMoney: null,
+                    nextTime: null,
+                    createTime: null,
+                    updateTime: null,
+                    createBy: null,
+                    recoveryTime: null,
+                    status: '1'
+                },
+                // 表单校验
+                rules: {
+                    source:[
+                        { required: true, message: "客户来源不能为空", trigger: "change" }
+                    ],
+                    companyName: [
+                        { required: true, message: "客户名称不能为空", trigger: "blur" }
+                    ],
+                    mobile: [
+                        { required: true, message: "联系电话不能为空", trigger: "blur" },
+                    ],
+                    businessScenario: [
+                        { required: true, message: "业务场景不能为空", trigger: "change" }
+                    ],
+                    businessStatus:[
+                        { required: true, message: "请选择跟进状态", trigger: "change" }
+                    ],
+                    projectPhase:[
+                        { required: true, message: "请选择项目阶段", trigger: "change" }
+                    ],
+
+                },
+                //联系方式表单校验
+                contactRules:{
+                    name:[
+                        { required: true, message: "联系人不能为空", trigger: "blur" }
+                    ],
+                    mobile: [
+                        { required: true, message: "手机不能为空", trigger: "blur" },
+                        { min: 8, max: 11, message: '长度在 8 到 11 个字符', trigger: 'blur' }
+                    ]
+                }
+
+            };
+        },
+        created() {
+
+            this.getDicts("crm_customer_source").then((response) => {
+                this.clueSourceOptions = response.data;
+            });
+            this.getDicts("crm_contact_role").then((response) => {
+                this.contactRoleOptions = response.data;
+            });
+            this.getDicts("crm_project_phase").then((response) => {
+                this.projectPhaseOptions = response.data;
+            });
+            this.getDicts("crm_level").then((response) => {
+                this.levelOptions = response.data;
+            });
+            this.getDicts("business_scenario_type").then((response) => {
+                this.businessScenarioOptions = response.data;
+            });
+        },
+        methods: {
+            addProduct(){
+
+            },
+            delCustomerContacts(mobile){
+                const index = this.mobileList.findIndex(phone => phone.id === mobile);
+                if (index !== -1) {
+                    this.mobileList.splice(index, 1); // 删除找到的对象
+                }
+                this.open = false;
+                this.loading = false;
+                console.log("3333333333333",JSON.String(mobileList))
+            },
+            /** 删除按钮操作 */
+            handleDelete(row) {
+                this.loading = true;
+                const mobile = row.mobile
+                const index = this.mobileList.findIndex(phone => phone.mobile === mobile);
+                if (index !== -1) {
+                    this.mobileList.splice(index, 1); // 删除找到的对象
+                }
+                this.open = false;
+                this.loading = false;
+
+            },
+            /** 修改按钮操作 */
+            // handleUpdate(row) {
+            //     // this.resetContact();
+            //     if (row.contactsId == undefined){
+            //         console.log('============================')
+            //         this.contactForm = {name:this.form.companyName,mobile:this.form.mobile}
+            //     } else{
+            //         this.resetContact();
+            //         getCustomerContacts(row.contactsId).then(response => {
+            //             this.contactForm = response.data;
+            //         });
+            //     }
+            //     // const contactsId = row.contactsId
+            //     console.log("row",JSON.stringify(this.contactForm))
+            //     this.open = true;
+            //     this.title = "修改客户联系人";
+            // },
+            /** 提交按钮 */
+            submitContactForm() {
+                this.$refs["contactForm"].validate(valid => {
+                if (valid) {
+                    console.log("13579246810",JSON.stringify(this.contactForm))
+                        //新增
+                        const newMobile = {
+                            mobile: this.contactForm.mobile,
+                            name:this.contactForm.name,
+                            remark:this.contactForm.remark,
+                        };
+                        this.mobileList.push(newMobile);
+                        this.open = false;
+                        this.loading = false;
+                }});
+            },
+            updateMobileList(mobile, newMobileData) {
+                // 查找要修改的对象
+                const index = this.mobileList.findIndex(phone => phone.mobile === mobile);
+                if (index !== -1) {
+                // 更新找到的对象
+                this.$set(this.mobileList, index, { ...this.mobileList[index], ...newMobileData });
+                }
+            },
+            // 取消按钮
+            cancelContact() {
+                this.open = false;
+                this.resetContact();
+            },
+            resetContact(){
+                this.contactForm = {
+                    contactsId: null,
+                    customerId: null,
+                    name: null,
+                    mobile: null,
+                    email: null,
+                    weixin: null,
+                    address: null,
+                    remark: null,
+                    createUserId: null,
+                    createTime: null,
+                    updateTime: null,
+                    isDel: null,
+                    companyId: null
+                };
+            },
+            //添加联系方式
+            showAddContactDialog() {
+                this.open = true; // 显示弹窗
+                this.contactForm.mobile = ''; // 清空表单数据
+            },
+            addContact() {
+                // 添加联系方式逻辑
+                console.log('添加联系方式:', this.contactForm);
+                this.form.mobile = this.contactForm.mobile; // 将联系方式添加到form.mobile
+                this.open = false; // 关闭弹窗
+            },
+            updateReset(row){
+                this.form = row;
+                if(row.source != null){
+                    this.form.source = String(row.source);
+                }
+                if(row.contactRole != null){
+                    this.form.contactRole = String(row.contactRole);
+                }
+                this.mobileList = this.form.mobileList;
+                this.loading = false;
+                if(row.businessStatus != null){
+                    this.form.businessStatus = String(row.businessStatus);
+                }
+                if(row.level != null){
+                    this.form.level = String(row.level);
+                }
+                if(row.isBp != null){
+                    this.form.isBp = String(row.isBp);
+                }
+            },
+            reset(row) {
+                this.form = row;
+                this.form.source=String(row.source);
+                //电话
+                this.getContact(row.customerId,this.form.businessId);
+                //跟进状态
+                this.form.businessStatus = '0'
+                //客户名字
+                this.form.companyName = row.customerName
+            },
+            getContact(customerId,businessId){
+                this.loading = true;
+                listCustomerContacts({customerId:customerId,businessId:businessId}).then(response=>{
+                    if (response.code === 200) {
+                        if(response.rows.length>0)
+                            this.mobileList = response.rows;
+                            this.loading = false;
+                        } else{
+                            this.mobileList = []
+                        }
+                })
+                console.log("========",JSON.stringify(this.mobileList))
+            },
+            /** 提交按钮 */
+            submitForm() {
+                this.$refs["form"].validate(valid => {
+                if (valid) {
+                    this.form.mobileList = this.mobileList;
+                    addBusiness(this.form).then(response => {
+                        if (response.code === 200) {
+                            this.msgSuccess("提交成功");
+                            this.$emit('closeBusiness');
+                        }
+                    });
+                }
+                });
+            },
+        }
+    };
+</script>
+<style lang="scss" scoped>
+.contents{
+    height: 100%;
+    background-color: #fff;
+    padding: 20px;
+
+}
+.footer{
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+}
+</style>
+
+
+<style scoped>
+  .avatar-uploader .el-upload {
+    border: 1px dashed #d9d9d9;
+    border-radius: 6px;
+    cursor: pointer;
+    position: relative;
+    overflow: hidden;
+  }
+  .avatar-uploader .el-upload:hover {
+    border-color: #409EFF;
+  }
+</style>
+
+
+
+

+ 96 - 0
src/views/crm/components/setColumn.vue

@@ -0,0 +1,96 @@
+<template>
+    <div>
+        <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+            <el-form-item label="全部字段" prop="columns">
+                <p></p>
+                <el-checkbox-group v-model="form.columns">
+                    <!-- 循环遍历选项 -->
+                    <el-checkbox
+                        v-for="(item, index) in allColumns"
+                        :key="index"
+                        :label="item.prop"
+                        :disabled="item.prop == 'customerId' || item.prop == 'businessId'"
+                    >
+                        {{ item.label }}
+                    </el-checkbox>
+                </el-checkbox-group>
+            </el-form-item>
+            <div   class="footer">
+                <el-button type="primary" @click="submitForm">确定</el-button>
+                <el-button  @click="cancel">取消</el-button>
+            </div>
+        </el-form>
+    </div>
+</template>
+
+<script>
+    import {updateShow} from "@/api/system/show";
+
+    export default {
+        name: "business",
+        data() {
+            return {
+                form:{
+                    type:'',
+                    columns:[],
+                },
+                allColumns:[],
+                // 表单校验
+                rules: {
+                    type: [
+                        { required: true, message: "type不能为空", trigger: "blur" }
+                    ],
+                    columns: [
+                        { required: true, message: '请至少选择一个选项', trigger: 'change' },
+                        ],
+
+                },
+
+
+            }
+        },
+
+        created() {
+
+        },
+        methods: {
+            cancel(){
+                this.$emit('close');
+            },
+            reset(allColumns,selectedKeys,type,){
+                this.allColumns = allColumns;
+                this.form.columns = selectedKeys;
+                this.form.type = type;
+                console.log("==========",JSON.stringify(this.allColumns))
+                console.log("==========2",JSON.stringify(this.form.columns))
+            },
+            /** 提交按钮 */
+            submitForm() {
+                this.$refs["form"].validate(valid => {
+                if (valid) {
+                    console.log("==========2",JSON.stringify(this.form))
+                    updateShow(this.form).then(response => {
+                        if (response.code === 200) {
+                            this.msgSuccess("提交成功");
+                            this.$emit('close');
+                        }
+                    });
+                }
+                });
+            },
+        }
+    };
+</script>
+<style lang="scss" scoped>
+.contents{
+    height: 100%;
+    background-color: #fff;
+    padding: 20px;
+
+}
+.footer{
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+}
+</style>

+ 826 - 0
src/views/crm/customerBusiness/index.vue

@@ -0,0 +1,826 @@
+<template>
+  <div class="app-container"  customer-page-box>
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <!-- <el-form-item label="客户名称" prop="companyName">
+        <el-input
+          v-model="queryParams.companyName"
+          placeholder="请输入客户名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item> -->
+      <el-form-item label="线索来源" prop="source">
+        <el-select v-model="queryParams.source" placeholder="请选择线索来源" clearable size="small">
+          <el-option
+            v-for="item in clueSourceOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <!-- <el-form-item label="客户经理" prop="manager">
+        <el-input
+          v-model="queryParams.manager"
+          placeholder="请输入客户经理"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item> -->
+      <el-form-item label="公司名称" prop="companyName">
+        <el-input
+          v-model="queryParams.companyName"
+          placeholder="请输入公司名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="电话号码" prop="mobile">
+        <el-input
+          v-model="queryParams.mobile"
+          placeholder="请输入电话号码"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <!-- <el-form-item label="接口人角色" prop="contactRole">
+        <el-select v-model="queryParams.contactRole" placeholder="请选择" clearable size="small">
+          <el-option
+            v-for="item in contactRoleOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+         </el-select>
+      </el-form-item> -->
+      <el-form-item label="业务场景" prop="businessScenario">
+        <el-input
+          v-model="queryParams.businessScenario"
+          placeholder="请输入业务场景"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <!-- <el-form-item label="方案涉及的产品" prop="product">
+        <el-input
+          v-model="queryParams.product"
+          placeholder="请输入方案涉及的产品"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item> -->
+      <!-- <el-form-item label="采购周期(单位:天)" prop="purchaseCycle">
+        <el-input
+          v-model="queryParams.purchaseCycle"
+          placeholder="请输入采购周期(单位:天)"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item> -->
+      <el-form-item label="跟进状态" prop="businessStatus">
+        <el-select v-model="queryParams.businessStatus" placeholder="请选择跟进状态" clearable size="small">
+          <el-option
+            v-for="item in businessStatusOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="项目阶段" prop="projectPhase">
+        <el-select v-model="queryParams.projectPhase" placeholder="请选择" clearable size="small">
+          <el-option
+              v-for="item in projectPhaseOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="意向等级" prop="level">
+        <el-select v-model="queryParams.level" placeholder="请选择" clearable size="small">
+          <el-option
+              v-for="item in levelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <!-- <el-form-item label="是否绑定BP账号" prop="isBp">
+        <el-input
+          v-model="queryParams.isBp"
+          placeholder="请输入是否绑定BP账号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item> -->
+      <!-- <el-form-item label="BP账户" prop="bpAccount">
+        <el-input
+          v-model="queryParams.bpAccount"
+          placeholder="请输入BP账户"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item> -->
+      <!-- <el-form-item label="预计成单时间" prop="preTime">
+        <el-date-picker clearable size="small" style="width: 200px"
+          v-model="queryParams.preTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择预计成单时间">
+        </el-date-picker>
+      </el-form-item> -->
+      <!-- <el-form-item label="预计付费(元)" prop="preMoney">
+        <el-input
+          v-model="queryParams.preMoney"
+          placeholder="请输入预计付费(元)"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item> -->
+      <!-- <el-form-item label="挖掘时间" prop="createTime">
+        <el-date-picker clearable size="small" style="width: 200px"
+          v-model="queryParams.createTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择挖掘时间">
+        </el-date-picker>
+      </el-form-item> -->
+      <el-form-item label="挖掘时间" prop="createTimeRange">
+        <el-date-picker
+          style="width:205.4px"
+          clearable size="small"
+          v-model="createTimeRange"
+          type="daterange"
+          value-format="yyyy-MM-dd"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <!-- <el-form-item label="下次跟进时间" prop="nextTime">
+        <el-date-picker clearable size="small" style="width: 200px"
+          v-model="queryParams.nextTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择下次跟进时间">
+        </el-date-picker>
+      </el-form-item> -->
+      <el-form-item label="下次跟进时间" prop="nextTimeRange">
+        <el-date-picker
+          style="width:205.4px"
+          clearable size="small"
+          v-model="nextTimeRange"
+          type="daterange"
+          value-format="yyyy-MM-dd"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <!-- <el-form-item label="回收时间" prop="recoveryTime">
+        <el-date-picker clearable size="small" style="width: 200px"
+          v-model="queryParams.recoveryTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择回收时间">
+        </el-date-picker>
+      </el-form-item> -->
+      <el-form-item label="回收时间" prop="recoveryRange">
+        <el-date-picker
+          style="width:205.4px"
+          clearable size="small"
+          v-model="recoveryRange"
+          type="daterange"
+          value-format="yyyy-MM-dd"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <!-- <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option
+              v-for="item in statusOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item> -->
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-hasPermi="['crm:business:export']"
+        >导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button size="mini" icon="el-icon-s-operation" @click="handleSetColumn()" >设置列表</el-button>
+      </el-col>
+	  <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <div>
+      <!-- <el-table v-loading="loading" :data="businessList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="商机ID" align="center" prop="businessId" />
+
+        <el-table-column label="线索来源" align="center" prop="source">
+          <template slot-scope="scope">
+              <span prop="source" v-for="(item, index) in clueSourceOptions"    v-if="scope.row.source==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="客户经理" align="center" prop="manager" />
+        <el-table-column label="公司名称" align="center" prop="companyName" />
+        <el-table-column label="电话号码" align="center" width="100">
+          <template slot-scope="scope">
+            <div v-if="scope.row.mobileList">
+              <el-tag v-for="(phone, index) in scope.row.mobileList" :key="index">
+                {{ phone.mobile }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="接口人角色" align="center" prop="contactRole" width="120">
+          <template slot-scope="scope">
+              <span prop="contactRole" v-for="(item, index) in contactRoleOptions"    v-if="scope.row.contactRole==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="业务场景" align="center" prop="businessScenario">
+          <template slot-scope="scope">
+              <span prop="businessScenario" v-for="(item, index) in businessScenarioOptions"    v-if="scope.row.businessScenario==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="方案涉及产品" align="center" prop="product"  width="100"/>
+        <el-table-column label="采购周期" align="center" prop="purchaseCycle">
+          <template slot-scope="scope">
+              <span>{{scope.row.purchaseCycle}}天</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="跟进状态" align="center" prop="businessStatus">
+          <template slot-scope="scope">
+              <span prop="businessStatus" v-for="(item, index) in businessStatusOptions"    v-if="scope.row.businessStatus==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="项目阶段" align="center" prop="projectPhase" width="200">
+          <template slot-scope="scope">
+              <span prop="projectPhase" v-for="(item, index) in projectPhaseOptions" v-if="scope.row.projectPhase==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="意向等级" align="center" prop="level" width="100">
+          <template slot-scope="scope">
+              <span prop="level" v-for="(item, index) in levelOptions" v-if="scope.row.level==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="是否绑定BP账号" align="center" prop="isBp" width="120">
+          <template slot-scope="scope">
+              <span prop="isBp" v-for="(item, index) in isBpOptions" v-if="scope.row.isBp==item.dictValue">{{item.dictLabel}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="BP账户" align="center" prop="bpAccount" />
+        <el-table-column label="预计成单时间" align="center" prop="preTime" width="180">
+          <template slot-scope="scope">
+            <span>{{ parseTime(scope.row.preTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="预计付费" align="center" prop="preMoney">
+          <template slot-scope="scope">
+              <span>{{scope.row.preMoney}}元</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="挖掘时间" align="center" prop="createTime" width="180"/>
+        <el-table-column label="下次跟进时间" align="center" prop="nextTime" width="180">
+          <template slot-scope="scope">
+            <span>{{ parseTime(scope.row.nextTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-edit"
+              @click="handleUpdate(scope.row)"
+              v-hasPermi="['crm:business:edit']"
+            >修改</el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-delete"
+              @click="handleDelete(scope.row)"
+              v-hasPermi="['crm:business:remove']"
+            >退回公海</el-button>
+          </template>
+        </el-table-column>
+      </el-table> -->
+
+      <!-- 动态表格 -->
+    <el-table height="500" border v-loading="loading" :data="businessList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <!-- 动态渲染列 -->
+      <el-table-column
+        v-for="col in visibleColumns"
+        :key="col.prop"
+        :label="col.label"
+        :width="col.width"
+        align="center"
+        :prop="col.prop">
+
+        <template slot-scope="scope">
+          <!-- 如果是线索来源字段 -->
+          <template v-if="col.prop === 'source'">
+            <el-tag v-for="item in clueSourceOptions" :key="item.dictValue" v-show="scope.row.source == item.dictValue">
+              {{ item.dictLabel }}
+            </el-tag>
+          </template>
+
+
+          <!-- 如果是电话号码字段 -->
+          <template v-else-if="col.prop === 'mobileList'">
+            <el-tag v-for="(phone, index) in scope.row.mobileList" :key="index" v-show="scope.row.mobileList">
+              {{ phone.mobile }}
+            </el-tag>
+          </template>
+
+          <!-- 如果是接口人角色字段 -->
+          <template v-else-if="col.prop === 'contactRole'">
+            <span v-for="item in contactRoleOptions" :key="item.dictValue" v-show="scope.row.contactRole == item.dictValue">
+              {{ item.dictLabel }}
+            </span>
+          </template>
+
+          <!-- 如果是业务场景字段 -->
+          <template v-else-if="col.prop === 'businessScenario'">
+            <span v-for="(item, index) in businessScenarioOptions" :key="item.dictValue" v-show="scope.row.businessScenario == item.dictValue">
+              {{ item.dictLabel }}
+            </span>
+          </template>
+
+          <!-- 如果是采购周期字段 -->
+          <template v-else-if="col.prop === 'purchaseCycle'">
+            <span>{{scope.row.purchaseCycle||'--'}}天</span>
+          </template>
+
+
+          <!-- 如果是跟进状态字段 -->
+          <template v-else-if="col.prop === 'businessStatus'">
+            <span v-for="item in businessStatusOptions" :key="item.dictValue" v-show="scope.row.businessStatus == item.dictValue">
+              {{ item.dictLabel }}
+            </span>
+          </template>
+
+          <!-- 如果是项目阶段字段 -->
+          <template v-else-if="col.prop === 'projectPhase'">
+            <span v-for="(item, index) in projectPhaseOptions" :key="item.dictValue" v-show="scope.row.projectPhase == item.dictValue">
+              {{ item.dictLabel }}
+            </span>
+          </template>
+
+          <!-- 如果是意向等级字段 -->
+          <template v-else-if="col.prop === 'level'">
+            <span v-for="(item, index) in levelOptions" :key="item.dictValue" v-show="scope.row.level == item.dictValue">
+              {{ item.dictLabel }}
+            </span>
+          </template>
+
+          <!-- 如果是预计成单时间字段 -->
+          <template v-else-if="col.prop === 'preTime'">
+            <span>{{ parseTime(scope.row.preTime, '{y}-{m}-{d}') }}</span>
+          </template>
+
+          <!-- 如果是是否绑定BP账号字段 -->
+          <template v-else-if="col.prop === 'isBp'">
+            <span v-for="(item, index) in isBpOptions" :key="item.dictValue" v-show="scope.row.isBp == item.dictValue">
+              {{ item.dictLabel }}
+            </span>
+          </template>
+
+          <!-- 如果是预计付费字段 -->
+          <template v-else-if="col.prop === 'preMoney'">
+            <span>{{scope.row.preMoney||'--'}}元</span>
+          </template>
+
+          <!-- 如果是下次跟进时间字段 -->
+          <template v-else-if="col.prop === 'nextTime'">
+            <span>{{ parseTime(scope.row.nextTime, '{y}-{m}-{d}') }}</span>
+          </template>
+
+          <!-- 其他普通字段 -->
+          <span v-else>{{ scope.row[col.prop] }}</span>
+        </template>
+
+
+      </el-table-column>
+
+      <!-- 操作列,始终显示 -->
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope" v-if="scope.row.customerId !== ''">
+          <el-button size="mini" type="text" icon="el-icon-s-custom" @click="handleShow(scope.row)" v-hasPermi="['crm:customer:query']" >查看相关客户</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+
+      <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
+        <add-business @closeBusiness="closeBusiness"   ref="addBusiness" />
+      </el-dialog>
+      <!-- 显示 allColumns 数据的对话框 -->
+      <el-dialog :visible.sync="columns.open" :title="columns.title">
+        <set-column ref="setColumn" @close="closeSetColumn"/>
+      </el-dialog>
+      <el-drawer
+      size="75%"
+        :title="show.title" :visible.sync="show.open">
+          <customer-details  ref="customerDetails" />
+      </el-drawer>
+
+    </div>
+  </div>
+</template>
+
+<script>
+import { listBusiness, getBusiness, exportBusiness,setBusinessPool } from "@/api/crm/business";
+import addBusiness from '../components/addBusiness.vue';
+import setColumn from '../components/setColumn.vue';
+import {getShow} from "@/api/system/show";
+import customerDetails from '../components/customerDetails.vue';
+export default {
+  name: "Business",
+  components: {addBusiness,setColumn,customerDetails},
+  data() {
+    return {
+      show:{
+        title:"客户详情",
+        open:false,
+      },
+      columns:{
+        title:"设置列表",
+        open:false,
+      },
+      columnType:'business',
+      visibleColumns: [],
+       // 可选字段
+       allColumns: [
+        { prop: "businessId", label: "商机ID" },
+        // { prop: "companyName", label: "客户名称" },
+        { prop: "customerId", label: "客户ID" },
+        { prop: "source", label: "线索来源" },
+        { prop: "manager", label: "客户经理" },
+        { prop: "companyName", label: "公司名称" },
+        { prop: "mobileList", label: "电话号码" },
+        { prop: "contactRole", label: "接口人角色" },
+        { prop: "businessScenario", label: "业务场景" },
+        { prop: "product", label: "方案涉及产品" },
+        { prop: "purchaseCycle", label: "采购周期" },
+        { prop: "businessStatus", label: "跟进状态" },
+        { prop: "projectPhase", label: "项目阶段" },
+        { prop: "level", label: "意向等级" },
+        { prop: "isBp", label: "是否绑定BP账号" },
+        { prop: "bpAccount", label: "BP账户" },
+        { prop: "preTime", label: "预计成单时间", width: 105 },
+        { prop: "preMoney", label: "预计付费" },
+        { prop: "createTime", label: "挖掘时间", width: 105 },
+        { prop: "nextTime", label: "下次跟进时间", width: 105 }
+
+      ],
+      // 选中的字段(默认显示所有字段)
+      selectedKeys: [
+        "businessId",
+        "customerId",
+        "source",
+        "manager",
+        "companyName",
+        "mobileList",
+        "contactRole",
+        "businessScenario",
+        "product",
+        "purchaseCycle",
+        "businessStatus",
+        "projectPhase",
+        "level",
+        "isBp",
+        "bpAccount",
+        "preTime",
+        "preMoney",
+        "createTime",
+        "nextTime"
+      ],
+      createTimeRange:[],
+      nextTimeRange:[],
+      recoveryRange:[],
+      statusOptions:[
+        {
+          dictValue:1,
+          dictLabel:"已分配"
+        },
+        {
+          dictValue:2,
+          dictLabel:"进行中"
+        },
+        {
+          dictValue:3,
+          dictLabel:"回收"
+        }
+      ],
+      clueSourceOptions:[],
+      contactRoleOptions:[],
+      projectPhaseOptions:[],
+      businessScenarioOptions:[],
+      levelOptions:[],
+      businessStatusOptions:[],
+      isBpOptions:[
+        {
+          dictValue:0,
+          dictLabel:"未注册"
+        },
+        {
+          dictValue:1,
+          dictLabel:"已注册未绑定"
+        },
+        {
+          dictValue:2,
+          dictLabel:"不明确"
+        },
+        {
+          dictValue:3,
+          dictLabel:"已绑定"
+        }
+      ],
+
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 商机表格数据
+      businessList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        // customerId: null,
+        source: null,
+        manager: null,
+        companyName: null,
+        mobile: null,
+        contactRole: null,
+        businessScenario: null,
+        product: null,
+        purchaseCycle: null,
+        businessStatus: null,
+        projectPhase: null,
+        level: null,
+        isBp: null,
+        bpAccount: null,
+        preTime: null,
+        preMoney: null,
+        nextTime: null,
+        recoveryTime: null,
+        status: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        customerId: [
+          { required: true, message: "客户id不能为空", trigger: "blur" }
+        ],
+      },
+
+    };
+  },
+  created() {
+    this.getDicts("business_scenario_type").then((response) => {
+      this.businessScenarioOptions = response.data;
+    });
+    this.getDicts("crm_customer_source").then((response) => {
+      this.clueSourceOptions = response.data;
+    });
+    this.getDicts("crm_contact_role").then((response) => {
+      this.contactRoleOptions = response.data;
+    });
+    this.getDicts("crm_project_phase").then((response) => {
+      this.projectPhaseOptions = response.data;
+    });
+    this.getDicts("crm_level").then((response) => {
+      this.levelOptions = response.data;
+    });
+    this.getDicts("crm_business_status").then((response) => {
+      this.businessStatusOptions = response.data;
+    });
+    this.getList();
+  },
+  methods: {
+    handleShow(row){
+      var that=this;
+      that.show.open=true;
+      setTimeout(() => {
+          that.$refs.customerDetails.getDetails(row.customerId);
+      }, 200);
+    },
+    handleSetColumn(){
+      this.columns.open=true;
+      var that=this;
+      setTimeout(() => {
+            that.$refs.setColumn.reset(that.allColumns,that.selectedKeys,that.columnType);
+      }, 200);
+    },
+    closeSetColumn(){
+      this.columns.open=false;
+      this.getList();
+    },
+    handleBusiness(row){
+        this.business.open=true;
+        setTimeout(() => {
+            this.$refs.addBusiness.reset(row);
+        }, 200);
+    },
+    closeBusiness(){
+        this.business.open=false;
+        this.getList();
+    },
+    //查询显示字段
+    async getColumn(){
+      const response = await getShow(this.columnType);
+      if (response.code === 200 && response.data) {
+        this.selectedKeys = response.data.columns.split(",");
+        this.$set(this, "visibleColumns", this.allColumns.filter(col => this.selectedKeys.includes(col.prop)));
+
+      } else{
+        this.$set(this, "visibleColumns", this.allColumns);
+      }
+    },
+    /** 查询客户列表 */
+    async getList() {
+      this.loading = true;
+      await this.getColumn(); // 确保 visibleColumns 先获取到
+
+      if(this.createTimeRange!=null&&this.createTimeRange.length==2){
+        this.queryParams.createTimeRange=this.createTimeRange[0]+"--"+this.createTimeRange[1]
+      }
+      else{
+        this.queryParams.createTimeRange=null;
+      }
+      if(this.nextTimeRange!=null&&this.nextTimeRange.length==2){
+        this.queryParams.nextTimeRange=this.nextTimeRange[0]+"--"+this.nextTimeRange[1]
+      }
+      else{
+        this.queryParams.nextTimeRange=null;
+      }
+      if(this.recoveryRange!=null&&this.recoveryRange.length==2){
+        this.queryParams.recoveryRange=this.recoveryRange[0]+"--"+this.recoveryRange[1]
+      }
+      else{
+        this.queryParams.recoveryRange=null;
+      }
+      const response = await listBusiness(this.addDateRange(this.queryParams, this.dateRange));
+
+      // this.customerList = response.rows;
+      // 处理 customerList 数据,确保不会有 undefined 或 null
+      this.businessList = response.rows.map(row => {
+        let newRow = {};
+        this.visibleColumns.forEach(col => {
+          newRow[col.prop] = row[col.prop] != null ? row[col.prop] : ""; // 如果是 null 或 undefined,就填 "-"
+        });
+        return newRow;
+      });
+      this.total = response.total;
+      this.loading = false;
+
+    },
+    // /** 查询商机列表 */
+    // getList() {
+    //   this.loading = true;
+    //   if(this.createTimeRange!=null&&this.createTimeRange.length==2){
+    //     this.queryParams.createTimeRange=this.createTimeRange[0]+"--"+this.createTimeRange[1]
+    //   }
+    //   else{
+    //     this.queryParams.createTimeRange=null;
+    //   }
+    //   if(this.nextTimeRange!=null&&this.nextTimeRange.length==2){
+    //     this.queryParams.nextTimeRange=this.nextTimeRange[0]+"--"+this.nextTimeRange[1]
+    //   }
+    //   else{
+    //     this.queryParams.nextTimeRange=null;
+    //   }
+    //   if(this.recoveryRange!=null&&this.recoveryRange.length==2){
+    //     this.queryParams.recoveryRange=this.recoveryRange[0]+"--"+this.recoveryRange[1]
+    //   }
+    //   else{
+    //     this.queryParams.recoveryRange=null;
+    //   }
+    //   listBusiness(this.queryParams).then(response => {
+    //     this.businessList = response.rows;
+    //     this.total = response.total;
+    //     this.loading = false;
+    //   });
+    // },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        businessId: null,
+        customerId: null,
+        source: null,
+        manager: null,
+        companyName: null,
+        mobile: null,
+        contactRole: null,
+        businessScenario: null,
+        product: null,
+        purchaseCycle: null,
+        businessStatus: 0,
+        remark: null,
+        projectPhase: null,
+        level: null,
+        isBp: null,
+        bpAccount: null,
+        preTime: null,
+        preMoney: null,
+        nextTime: null,
+        createTime: null,
+        updateTime: null,
+        createBy: null,
+        recoveryTime: null,
+        status: 0,
+        // createTimeRange:null,
+        // nextTimeRange:null,
+        // recoveryRange:null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.businessId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有商机数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return exportBusiness(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+        }).catch(function() {});
+    }
+  }
+};
+</script>

+ 17 - 2
src/views/his/company/index.vue

@@ -475,6 +475,17 @@
         <el-form-item label="备注" prop="remark">
           <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注"/>
         </el-form-item>
+         <el-form-item label="可用外呼网关" prop="showGatewayIds">
+            <!-- <el-input v-model="" placeholder="请输入DeepSeekChat模型名称"></el-input> -->
+             <el-select v-model="form.showGatewayIds" multiple filterable placeholder="请选择系统可见外呼网关">
+              <el-option
+                v-for="item in gatewayList"
+                :key="item.id"
+                :label="item.gwDesc"
+                :value="item.id">
+              </el-option>
+            </el-select>
+          </el-form-item>
       </el-form>
 
 
@@ -741,7 +752,7 @@ import {
   exportCompany,
   resetPwd,
   getDivConfig,
-  setDiv, exitMiniProgram
+  setDiv, exitMiniProgram,getGatewayCompanyList
 } from '@/api/his/company'
 import { getFollowDoctorList } from '@/api/his/doctor'
 import { docList } from '@/api/his/doctor'
@@ -756,7 +767,7 @@ export default {
   name: 'Company',
   data() {
     return {
-
+      gatewayList:[],
       projectFrom:process.env.VUE_APP_HSY_SPACE,
 
       signProjectName:"",
@@ -941,6 +952,10 @@ export default {
     }
   },
   created() {
+    getGatewayCompanyList().then(res=>{
+        this.gatewayList = res.data;
+        console.log(this.gatewayList);
+    }).catch(res=>{});
 
     cateList().then((response) => {
       if (this.projectFrom =='mengniu-2114522511'){

+ 17 - 17
src/views/his/package/index.vue

@@ -275,7 +275,7 @@
                  <el-radio :label="item.dictValue" v-for="item in packageTypeOptions" >{{item.dictLabel}}</el-radio>
               </el-radio-group>
          </el-form-item>
-        <el-form-item label="套餐包子类型" prop="packageSubType">
+        <el-form-item label="套餐包子类型" prop="packageSubType" v-if="form.packageType != 3">
               <el-radio-group v-model="form.packageSubType">
                 <el-tooltip content="咨询包不能推ERP" placement="top" :open-delay="100" v-if="form.packageType == 1">
                   <el-radio label="1">咨询包</el-radio>
@@ -284,22 +284,22 @@
                 <el-radio  label="3" v-if="form.packageType==2">产品包</el-radio>
               </el-radio-group>
          </el-form-item>
-         <el-form-item label="药品类型" prop="productType" v-if="form.packageSubType!=1">
+         <el-form-item label="药品类型" prop="productType" v-if="form.packageSubType!=1 && form.packageType != 3 ">
            <el-radio-group v-model="form.productType">
             <el-radio :label="item.dictValue" v-for="item in productTypeOptions" >{{item.dictLabel}}</el-radio>
            </el-radio-group>
          </el-form-item>
 
-         <el-form-item label="药品制作类型" prop="recipeType" v-if="form.productType==2&&form.packageSubType!=1">
+         <el-form-item label="药品制作类型" prop="recipeType" v-if="form.productType==2&&form.packageSubType!=1 && form.packageType != 3">
            <el-radio v-model="form.recipeType" label="0">颗粒剂</el-radio>
              <el-radio v-model="form.recipeType" label="1">膏方</el-radio>
 			 <el-radio v-model="form.recipeType" label="2">饮片</el-radio>
          </el-form-item>
 
-         <el-form-item label="剂数" prop="counts" v-if="form.productType==2&&form.packageSubType!=1">
+         <el-form-item label="剂数" prop="counts" v-if="form.productType==2&&form.packageSubType!=1 && form.packageType != 3">
            <el-input-number v-model="form.counts"  :min="1" label="剂数"></el-input-number>
          </el-form-item>
-         <el-form-item label="一日几次" prop="usageFrequencyUnit" v-if="form.productType==2&&form.packageSubType!=1">
+         <el-form-item label="一日几次" prop="usageFrequencyUnit" v-if="form.productType==2&&form.packageSubType!=1 && form.packageType != 3">
           <el-select v-model="form.usageFrequencyUnit" placeholder="请选择">
               <el-option
                 v-for="item in usageFrequencyUnitOptions"
@@ -309,15 +309,15 @@
               </el-option>
             </el-select>
          </el-form-item>
-          <el-form-item label="用药数量" prop="tags">
+          <el-form-item label="用药数量" prop="tags" v-if="form.packageType != 3">
             <el-input v-model="form.usagePerUseCount" placeholder="请输入用药数量" />
           </el-form-item>
-        <el-form-item label="是否食品/保健品" prop="isHealthProductType" label-width="130px">
-          <el-radio-group v-model="form.isHealthProductType">
-            <el-radio :label="1">是</el-radio>
-            <el-radio :label="0">否</el-radio>
-          </el-radio-group>
-        </el-form-item>
+<!--        <el-form-item label="是否食品/保健品" prop="isHealthProductType" label-width="130px">-->
+<!--          <el-radio-group v-model="form.isHealthProductType">-->
+<!--            <el-radio :label="1">是</el-radio>-->
+<!--            <el-radio :label="0">否</el-radio>-->
+<!--          </el-radio-group>-->
+<!--        </el-form-item>-->
         <el-form-item label="是否展示" prop="isShow">
               <el-radio-group v-model="form.isShow">
                        <el-radio :label="item.dictValue" v-for="item in orOptions" >{{item.dictLabel}}</el-radio>
@@ -795,7 +795,7 @@ export default {
 
       // 表单参数
       form: {
-        isHealthProductType: 0
+        // isHealthProductType: 0
       },
       // 表单校验
       rules: {
@@ -841,9 +841,9 @@ export default {
               followTempId: [
                 { required: true, message: '请选择随访模板', trigger: 'change' }
               ],
-              isHealthProductType: [
-                { required: true, message: '请选择是否是食品/保健品类型', trigger: 'change' }
-              ],
+              // isHealthProductType: [
+              //   { required: true, message: '请选择是否是食品/保健品类型', trigger: 'change' }
+              // ],
 
       }
     };
@@ -1096,7 +1096,7 @@ export default {
         counts:null,
         followNum:null,
         secondName:null,
-        isHealthProductType: 0,
+        // isHealthProductType: 0,
       };
       this.photoArr=[];
       this.resetForm("form");

+ 221 - 3
src/views/his/user/indexProject.vue

@@ -179,6 +179,15 @@
           v-hasPermi="['his:user:unbind']"
         >解绑会员</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          icon="el-icon-data-analysis"
+          size="mini"
+          @click="openMemberStatistics"
+          v-hasPermi="['his:user:statistics']"
+        >会员统计</el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -190,7 +199,7 @@
           <el-tag v-if="scope.row.projectId !== null">{{ getProjectLabel(scope.row.projectId,scope.row) }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="会员昵称" align="center" prop="nickname" />
+      <el-table-column label="会员昵称" align="center" prop="nickName" />
       <el-table-column label="会员头像" align="center" width="80">
         <template slot-scope="scope">
           <el-popover
@@ -369,6 +378,83 @@
       <userDetailsByNew  ref="userDetailsByNew" />
     </el-drawer>
 
+    <!-- 会员统计 -->
+    <el-dialog
+      title="会员统计"
+      :visible.sync="memberStatisticsOpen"
+      width="1260px"
+      append-to-body
+      @close="resetMemberStatisticsDialog"
+    >
+      <el-form :inline="true" size="small">
+        <el-form-item label="销售公司">
+          <el-select
+            v-model="memberStatisticsQuery.companyId"
+            placeholder="请选择销售公司"
+            clearable
+            filterable
+            style="width: 240px"
+          >
+            <el-option
+              v-for="item in companyQueryOptions"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleMemberStatisticsSearch">搜索</el-button>
+        </el-form-item>
+      </el-form>
+      <div v-loading="memberStatisticsLoading" style="min-height: 200px">
+        <el-table
+          v-if="memberStatisticsRows.length > 0"
+          :data="memberStatisticsRows"
+          border
+          max-height="480"
+        >
+          <el-table-column
+            v-for="col in statisticsDisplayColumns"
+            :key="col.prop"
+            :label="col.label"
+            :prop="col.prop"
+            min-width="120"
+            show-overflow-tooltip
+          >
+            <template slot-scope="scope">
+              <template v-if="col.prop === 'status'">
+                <el-tag v-if="String(scope.row.status) === '1'" type="success">正常</el-tag>
+                <el-tag v-else-if="String(scope.row.status) === '0'" type="danger">禁止</el-tag>
+                <span v-else>{{ scope.row.status }}</span>
+              </template>
+              <template v-else-if="col.prop === 'avatar'">
+                <el-popover
+                  v-if="scope.row.avatar"
+                  placement="right"
+                  title=""
+                  trigger="hover"
+                >
+                  <img slot="reference" :src="scope.row.avatar" width="40" height="40" style="object-fit: cover; border-radius: 4px;" />
+                  <img :src="scope.row.avatar" style="max-width: 220px; max-height: 220px;" />
+                </el-popover>
+                <span v-else>-</span>
+              </template>
+              <span v-else>{{ scope.row[col.prop] }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-empty v-else-if="!memberStatisticsLoading" description="暂无数据" />
+      </div>
+      <pagination
+        v-show="memberStatisticsTotal > 0"
+        :total="memberStatisticsTotal"
+        :page.sync="memberStatisticsQuery.pageNum"
+        :limit.sync="memberStatisticsQuery.pageSize"
+        @pagination="getMemberStatisticsList"
+      />
+    </el-dialog>
+
     <!-- 更换会员归属对话框 -->
     <el-dialog title="更换会员归属" :visible.sync="changeCompanyUserOpen" width="500px" append-to-body>
       <el-form ref="changeCompanyUserForm" :model="changeCompanyUserForm" :rules="changeCompanyUserRules" label-width="100px">
@@ -402,7 +488,7 @@
 </template>
 
 <script>
-import {listUserByProject, getUser, addUser, updateUser, exportUser, delUserCompanyUser,exportListProject, batchUnbindUser} from "@/api/his/user";
+import {listUserByProject, getUser, addUser, updateUser, exportUser, delUserCompanyUser,exportListProject, batchUnbindUser, statisticsList} from "@/api/his/user";
 import { getCompanyUserList, changeCompanyUser, getCompanyList } from '@/api/company/companyUser';
 import userDetailsByNew from '@/views/his/user/userDetails.vue'
 export default {
@@ -511,9 +597,46 @@ export default {
       companyUserOptions: [],
       companyOptions: [],
       projectOptions: [],
-      selectedUser: []
+      selectedUser: [],
+      // 会员统计弹窗
+      memberStatisticsOpen: false,
+      memberStatisticsLoading: false,
+      memberStatisticsRows: [],
+      memberStatisticsTotal: 0,
+      memberStatisticsQuery: {
+        pageNum: 1,
+        pageSize: 10,
+        companyId: null,
+      }
     };
   },
+  computed: {
+    statisticsDisplayColumns() {
+      const row = this.memberStatisticsRows[0]
+      if (!row) {
+        return []
+      }
+      const labelMap = {
+        userId: '会员ID',
+        nickName: '会员昵称',
+        avatar: '头像',
+        // phone: '手机号码',
+        status: '状态',
+        // companyId: '公司ID',
+        companyName: '所属公司',
+        watchDaysLast3: '最近3天看课天数',
+        watchDaysLast5: '最近5天看课天数',
+        watchDaysLast7: '最近7天看课天数',
+      }
+      // 只展示配置了中文的字段,并按 labelMap 的顺序展示
+      return Object.keys(labelMap)
+        .filter(prop => Object.prototype.hasOwnProperty.call(row, prop))
+        .map(prop => ({
+          prop,
+          label: labelMap[prop]
+        }))
+    }
+  },
   created() {
     this.getDicts("project_user_status").then((response) => {
       this.statusOptions = response.data;
@@ -874,6 +997,101 @@ export default {
     getProjectLabel(projectId) {
       return this.projectOptions.find(item => parseInt(item.dictValue) === projectId)?.dictLabel;
     },
+
+    // 如果主页面有选择公司参数,则直接使用,否则就使用下拉框的第一个参数
+    resolveDefaultStatisticsCompanyId() {
+      const options = this.companyQueryOptions || []
+      if (!options.length) {
+        return null
+      }
+      let companyId = this.queryParams.companyId
+      if (companyId != null && companyId !== '') {
+        const exists = options.some(o => String(o.companyId) === String(companyId))
+        if (exists) {
+          return companyId
+        }
+      }
+      return options[0].companyId
+    },
+    openMemberStatistics() {
+      const ensureOptions = () => {
+        return new Promise((res) => {
+          const options = this.companyQueryOptions || []
+          if (options.length) {
+            res(true)
+            return
+          }
+          getCompanyList().then(response => {
+            if (response.code === 200) {
+              this.companyQueryOptions = response.data || []
+              res(!!this.companyQueryOptions.length)
+            } else {
+              this.$message.error(response.msg || '获取公司列表失败')
+              res(false)
+            }
+          }).catch(() => {
+            this.$message.error('获取公司列表失败')
+            res(false)
+          })
+        })
+      }
+      ensureOptions().then(ok => {
+        if (!ok) {
+          this.$message.warning('没有公司数据')
+          return
+        }
+        const companyId = this.resolveDefaultStatisticsCompanyId()
+        if (companyId == null) {
+          this.$message.warning('没有所属公司')
+          return
+        }
+        this.memberStatisticsQuery = {
+          pageNum: 1,
+          pageSize: 10,
+          companyId,
+        }
+        this.memberStatisticsOpen = true
+        this.$nextTick(() => this.getMemberStatisticsList())
+      })
+    },
+
+    // 查询列表
+    getMemberStatisticsList() {
+      if (this.memberStatisticsQuery.companyId == null || this.memberStatisticsQuery.companyId === '') {
+        this.$message.warning('请选择销售公司')
+        return
+      }
+      this.memberStatisticsLoading = true
+      statisticsList(this.memberStatisticsQuery).then(response => {
+        this.memberStatisticsRows = response.rows || []
+        this.memberStatisticsTotal = response.total || 0
+      }).catch(() => {
+        this.memberStatisticsRows = []
+        this.memberStatisticsTotal = 0
+      }).finally(() => {
+        this.memberStatisticsLoading = false
+      })
+    },
+
+    // handleMemberStatisticsCompanyChange() {
+    //   this.memberStatisticsQuery.pageNum = 1
+    //   this.getMemberStatisticsList()
+    // },
+    handleMemberStatisticsSearch() {
+      this.memberStatisticsQuery.pageNum = 1
+      this.getMemberStatisticsList()
+    },
+
+    // 关闭弹窗
+    resetMemberStatisticsDialog() {
+      this.memberStatisticsRows = []
+      this.memberStatisticsTotal = 0
+      this.memberStatisticsQuery = {
+        pageNum: 1,
+        pageSize: 10,
+        companyId: null,
+      }
+    }
   }
 };
 

+ 112 - 7
src/views/hisStore/components/productAfterSalesOrder.vue

@@ -24,7 +24,8 @@
             <el-button size="mini"  @click="showOrder">查看订单</el-button>
          </div>
         </div>
-        <div style="margin: 20px 0px">
+
+        <div style="margin: 20px 0px" >
           <span class="font-small">基本信息</span>
         </div>
         <el-descriptions   :column="4" border  >
@@ -73,6 +74,22 @@
                   <el-tag  v-for="(item, index) in salesStatusOptions"    v-if="afterSales.salesStatus==item.dictValue" >{{item.dictLabel}}</el-tag>
                 </span>
             </el-descriptions-item>
+            <el-descriptions-item label="财务审核原因"  v-if="afterSales!=null && afterSales.status >= 4">
+                <span>
+                  {{afterSales.auditReasonName}}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="财务审核备注"  v-if="afterSales!=null && afterSales.status >= 4">
+                <span>
+                  {{afterSales.auditRemark}}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="售后一级原因" v-if="afterSales!=null">
+              <span>{{ afterSales.reasonValue1 || afterSales.reasonLevel1Text || '-' }}</span>
+            </el-descriptions-item>
+            <el-descriptions-item label="售后二级原因" v-if="afterSales!=null">
+              <span>{{ afterSales.reasonValue2 || afterSales.reasonLevel2Text || '-' }}</span>
+            </el-descriptions-item>
 
         </el-descriptions>
         <div style="margin: 20px 0px">
@@ -193,9 +210,39 @@
         <el-form-item label="退款金额" prop="refundAmount"  >
           <el-input-number v-model="form.refundAmount" :min="0"  />
         </el-form-item>
+        <el-form-item label="一级原因" prop="reasonId1">
+          <el-select v-model="form.reasonId1" placeholder="请选择一级原因" @change="handleReason1Change" style="width: 100%">
+            <el-option
+              v-for="item in reasonList"
+              :key="item.id"
+              :label="item.reasonName"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="二级原因" v-if="form.reasonId1 != null" prop="reasonId2">
+          <el-select v-model="form.reasonId2" placeholder="请选择二级原因" style="width: 100%">
+            <el-option
+              v-for="item in reason2List"
+              :key="item.id"
+              :label="item.reasonName"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="审核备注" prop="auditRemark">
+          <el-input
+            type="textarea"
+            :rows="3"
+            maxlength="200"
+            placeholder="请输入审核备注"
+            v-model="form.auditRemark">
+          </el-input>
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitAuditForm">确 定</el-button>
+        <el-button @click="audit.open = false">取 消</el-button>
       </div>
     </el-dialog>
     <el-drawer
@@ -227,7 +274,7 @@
 </template>
 
 <script>
-import {getStoreAfterSales,cancel,refund,audit1,audit2,updateStoreAfterSales} from "@/api/hisStore/storeAfterSales";
+import {getStoreAfterSales,cancel,refund,audit1,audit2,updateStoreAfterSales,listRefundReason} from "@/api/hisStore/storeAfterSales";
 
 import productOrder from "./productOrder";
 export default {
@@ -270,16 +317,39 @@ export default {
       logs:[],
       form:{
         refundAmount:0,
+        reasonId1: null,
+        reasonId2: null,
+        auditRemark: ''
       },
-      rules:{
+      rules: {
         status: [
           { required: true, message: "状态不能为空", trigger: "blur" }
         ],
         refundAmount: [
           { required: true, message: "退款金额不能为空", trigger: "blur" }
         ],
-      }
-
+        // 新增一级原因必填
+        reasonId1: [
+          { required: true, message: "请选择一级原因", trigger: "change" }
+        ],
+        reasonId2: [
+          {
+            validator: (rule, value, callback) => {
+              if (this.form.reasonId1 && !value) {
+                callback(new Error("请选择二级原因"));
+              } else {
+                callback();
+              }
+            },
+            trigger: "change"
+          }
+        ],
+        auditRemark: [
+          { required: true, message: "请输入审核备注", trigger: "blur" }
+        ]
+      },
+      reasonList: [],
+      reason2List: []
     };
   },
   computed: {
@@ -362,9 +432,44 @@ export default {
 
     },
     handleRefund(){
-        this.audit.open=true;
         this.form.salesId=this.afterSales.id;
         this.form.refundAmount=this.order.payMoney;
+        this.form.reasonId1 = null;
+        this.form.reasonId2 = null;
+        this.form.auditRemark = '';
+        this.reason2List = [];
+        this.audit.open=true;
+        this.getReasonList();
+    },
+    getReasonList() {
+      listRefundReason({ typeLevel: 1 }).then(response => {
+        this.reasonList = response.rows.map(item => {
+          return {
+            ...item,
+            children: []
+          };
+        });
+        this.reasonList.forEach(item => {
+          if (item.childrenCount > 0) {
+            this.getReason2List(item.id, item);
+          }
+        });
+      });
+    },
+    getReason2List(parentId, parent) {
+      listRefundReason({ parentId: parentId, typeLevel: 2 }).then(response => {
+        parent.children = response.rows;
+      });
+    },
+    handleReason1Change(val) {
+        this.form.reasonId2 = null;
+        this.reason2List = [];
+        if (val) {
+          const selected = this.reasonList.find(item => item.id === val);
+          if (selected && selected.children) {
+            this.reason2List = selected.children;
+          }
+        }
     },
     submitAuditForm() {
       this.$refs["form"].validate(valid => {
@@ -402,7 +507,7 @@ export default {
             this.user=response.user;
             this.order=response.order;
         });
-     }
+     },
   }
 };
 </script>

+ 28 - 2
src/views/hisStore/components/productOrder.vue

@@ -184,6 +184,16 @@
                       {{ order.videoName }}
                     </span>
             </el-descriptions-item>
+            <el-descriptions-item label="财务审核原因" v-if="afterSales!=null && afterSales.auditReasonName">
+                <span>
+                  {{ afterSales.auditReasonName }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="财务审核备注" v-if="afterSales!=null && afterSales.auditRemark">
+                <span>
+                  {{ afterSales.auditRemark }}
+                </span>
+            </el-descriptions-item>
       </el-descriptions>
       <div style="margin: 20px 0px"  v-if="order!=null">
         <span class="font-small">
@@ -323,9 +333,15 @@
               </span>
           </el-descriptions-item>
           <el-descriptions-item label="实付金额"  >
-              <span v-if="order!=null">
+              <span v-if="order!=null && !isBeiJingZhuoMei">
                 ¥{{order.payMoney.toFixed(2)}}
               </span>
+            <span v-else-if="order!=null && isBeiJingZhuoMei && order.status != -3">
+                ¥{{order.payMoney.toFixed(2)}}
+              </span>
+              <span v-else>
+                ¥0
+              </span>
           </el-descriptions-item>
           <el-descriptions-item label="代收金额"  >
               <span v-if="order!=null">
@@ -823,6 +839,7 @@ import singleImg from '@/components/Material/single'
 import {getCompanyList} from "@/api/company/company";
 import {listStore} from '@/api/hisStore/store';
 import {addStoreOrderItem, delStoreOrderItem, updateNumStoreOrderItem} from "../../../api/hisStore/storeOrderItem";
+import{ getSignProjectName } from '@/api/course/qw/courseWatchLog'
 
 
 export default {
@@ -835,6 +852,7 @@ export default {
   },
   data() {
     return {
+      isBeiJingZhuoMei:false,
       zdyInfo: process.env.VUE_APP_FS_USER_INFO,
       isMedicalMall: this.$store.state.user.medicalMallConfig.medicalMall,
       dialogVisibleImage: false,
@@ -964,6 +982,7 @@ export default {
       tuiMoneyLogs:[],
       erpOrder:null,
       auditLogs: [],
+      afterSales: null,
       // 数量修改弹窗相关数据
       quantityDialog: {
         title: "修改商品数量",
@@ -1026,6 +1045,7 @@ export default {
     getTcmScheduleList().then(response => {
       this.scheduleOptions = response.data;
     });
+    this.checkProjectName()
   },
   methods: {
     // 显示修改数量弹窗
@@ -1551,6 +1571,7 @@ export default {
             this.payments=response.payments;
             this.tuiMoneyLogs=response.tuiMoneyLogs;
             this.auditLogs = response.auditLogs;
+            this.afterSales = response.afterSales;
         });
      },
     editPayDeliveryHandle() {
@@ -1576,7 +1597,12 @@ export default {
         }
       });
     },
-  }
+    checkProjectName() {
+      getSignProjectName().then(r=>{
+        this.isBeiJingZhuoMei = r.signProjectName === '北京卓美';
+      });
+    },
+  },
 };
 </script>
 <style scoped>

+ 63 - 3
src/views/hisStore/integralGoods/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
       <el-form-item label="商品名称" prop="goodsName">
         <el-input
           v-model="queryParams.goodsName"
@@ -30,6 +30,26 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option
+            v-for="dict in statusOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属小程序" prop="appId">
+        <el-select style="width: 240px" v-model="queryParams.appId" placeholder="请选择所属小程序" clearable size="small">
+          <el-option
+            v-for="dict in appMallOptions"
+            :key="dict.appid"
+            :label="dict.name + '(' + dict.appid + ')'"
+            :value="dict.appid"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -122,6 +142,7 @@
         </template>
       </el-table-column>
       <el-table-column label="所需积分" align="center" prop="integral" />
+<!--      <el-table-column label="需支付金额" align="center" prop="cash" />-->
       <el-table-column label="排序" align="center" prop="sort" />
       <el-table-column label="库存" align="center" prop="stock" />
       <el-table-column label="创建时间" align="center" prop="createTime" width="180"/>
@@ -188,6 +209,9 @@
         <el-form-item label="所需积分" prop="integral">
           <el-input-number v-model="form.integral"  :min="0"  label="所需积分"></el-input-number>
         </el-form-item>
+<!--        <el-form-item label="支付金额" prop="cash">-->
+<!--          <el-input-number v-model="form.cash"  :min="0" :precision="2" :step="0.1"  label="需支付金额"></el-input-number>-->
+<!--        </el-form-item>-->
         <el-form-item label="商品编号" prop="barCode">
           <el-input v-model="form.barCode" placeholder="请输入商品编号"  style="width: 200px;"/>
         </el-form-item>
@@ -211,6 +235,16 @@
             >{{dict.dictLabel}}</el-radio>
           </el-radio-group>
         </el-form-item>
+        <el-form-item label="所属小程序" prop="appIds">
+          <el-select style="width: 240px" v-model="appIds" placeholder="请选择所属小程序" clearable size="small" multiple>
+            <el-option
+              v-for="dict in appMallOptions"
+              :key="dict.appid"
+              :label="dict.name + '(' + dict.appid + ')'"
+              :value="dict.appid"
+            />
+          </el-select>
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -262,6 +296,7 @@ import Material from '@/components/Material';
 import Editor from '@/components/Editor/wang';
 import { getToken } from "@/utils/auth";
 import integralGoodsDetails from '../components/integralGoodsDetails';
+import { list as getAppMallOptions } from '@/api/course/coursePlaySourceConfig'
 export default {
   name: "HisIntegralGoods",
   components: {Material,Editor,integralGoodsDetails},
@@ -275,6 +310,11 @@ export default {
   },
   data() {
     return {
+
+      appIds:[], // 选择的小程序
+      // 小程序列表
+      appMallOptions:[],
+
       show:{
               open:false,
             },
@@ -325,6 +365,7 @@ export default {
         goodsName: null,
         goodsType: null,
         status: null,
+        appId: null,
         integral: null,
         createTime: null
       },
@@ -343,6 +384,10 @@ export default {
   },
   created() {
     this.getList();
+
+    // 查询小程序
+    this.getAppMallOptions();
+
     this.getDicts("sys_integral_goods_type").then(response => {
       this.goodsTypeOptions = response.data;
     });
@@ -351,6 +396,11 @@ export default {
     });
   },
   methods: {
+    getAppMallOptions() {
+      getAppMallOptions({pageNum:1,pageSize:100}).then(response => {
+        this.appMallOptions = response.rows;
+      })
+    },
         handledetails(row){
             this.show.open=true;
             setTimeout(() => {
@@ -411,6 +461,7 @@ export default {
         otPrice: null,
         goodsType: null,
         status: 0,
+        cash: 0,
         integral: null,
         sort: null,
         stock: null,
@@ -462,6 +513,8 @@ export default {
          if(this.form.images!=null){
              this.photoArr=this.form.images.split(",");
            }
+        this.appIds = this.form.appIds ? this.form.appIds.split(',') : [];
+
         //修改按钮
         setTimeout(() => {
                   if(this.form.descs==null){
@@ -477,15 +530,22 @@ export default {
     submitForm() {
       this.$refs["form"].validate(valid => {
         if (valid) {
+
+          // 小程序
+          const params = {
+            ...this.form,
+            appIds: this.appIds.join(',') // 数组转字符串
+          };
+
           if (this.form.goodsId != null) {
-            updateIntegralGoods(this.form).then(response => {
+            updateIntegralGoods(params).then(response => {
               this.msgSuccess("修改成功");
               this.open = false;
               this.reset();
               this.getList();
             });
           } else {
-            addIntegralGoods(this.form).then(response => {
+            addIntegralGoods(params).then(response => {
               this.msgSuccess("新增成功");
               this.open = false;
               this.reset();

+ 528 - 0
src/views/hisStore/productDiscount/index.vue

@@ -0,0 +1,528 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="商品名称" prop="productName">
+        <el-input
+          v-model="queryParams.productName"
+          placeholder="请输入商品名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option label="上架" :value="1"/>
+          <el-option label="下架" :value="0"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="活动时间" prop="timeRange">
+        <el-date-picker
+          v-model="queryParams.timeRange"
+          type="datetimerange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          size="small"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['store:productDiscount:add']">新增</el-button>
+      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['store:productDiscount:export']">导出</el-button>-->
+<!--      </el-col>-->
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="list" border>
+      <el-table-column label="序号" type="index" width="55" align="center" />
+      <el-table-column label="商品信息" align="center" min-width="200">
+        <template slot-scope="scope">
+          <div style="display:flex;align-items:center;">
+            <el-image v-if="scope.row.productImage" style="width:50px;height:50px;margin-right:10px;" :src="scope.row.productImage" fit="contain" :preview-src-list="[scope.row.productImage]"/>
+            <span>{{ scope.row.productName || '-' }}</span>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="原价" align="center" prop="originalPrice" width="100">
+        <template slot-scope="scope">
+          <span v-if="scope.row.originalPrice != null">¥{{ scope.row.originalPrice.toFixed(2) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="折扣" align="center" prop="discount" width="80">
+        <template slot-scope="scope">
+          <span>{{ (scope.row.discount * 10).toFixed(1) }}折</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="折扣价格" align="center" prop="discountPrice" width="110">
+        <template slot-scope="scope">
+          <span style="color:#f56c6c;font-weight:bold;">¥{{ scope.row.discountPrice ? scope.row.discountPrice.toFixed(2) : '0.00' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="库存" align="center" prop="stock" width="80" />
+      <el-table-column label="开始时间" align="center" prop="startTime" width="160">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="结束时间" align="center" prop="endTime" width="160">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="80">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.status === 1" type="success">上架</el-tag>
+          <el-tag v-else type="info">下架</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['store:productDiscount:edit']">编辑</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['store:productDiscount:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+    <!-- 添加/编辑限时折扣 - 支持批量选择商品 -->
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-alert
+        v-if="!form.id && selectedProducts.length === 0"
+        title="提示:可批量选择多个商品,统一设置折扣信息后一键添加"
+        type="info"
+        :closable="false"
+        show-icon
+        style="margin-bottom:15px;"
+      />
+
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <!-- 批量选择商品 -->
+        <el-form-item label="选择商品" prop="productIds" v-if="!form.id">
+          <el-select
+            v-model="selectedProducts"
+            multiple
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请输入商品名称搜索,可多选"
+            :remote-method="searchProduct"
+            :loading="productLoading"
+            style="width:100%"
+            collapse-tags
+            collapse-tags-tooltip
+          >
+            <el-option
+              v-for="item in productOptions"
+              :key="item.productId"
+              :label="item.productName"
+              :value="item.productId"
+              :disabled="isProductSelected(item.productId)"
+            >
+              <div style="display:flex;justify-content:space-between;align-items:center;">
+                <span style="float:left">{{ item.productName }}</span>
+                <span style="float:right;color:#8492a6;font-size:12px;margin-left:15px;">ID:{{ item.productId }} | ¥{{ item.price || item.originalPrice || '-' }}</span>
+              </div>
+            </el-option>
+          </el-select>
+          <div style="color:#909399;font-size:12px;margin-top:5px;" v-if="selectedProducts.length > 0">
+            已选择 {{ selectedProducts.length }} 个商品
+          </div>
+        </el-form-item>
+
+        <!-- 编辑模式显示单个商品 -->
+        <el-form-item label="选择商品" prop="productId" v-if="form.id">
+          <el-select
+            v-model="form.productId"
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请输入商品名称搜索"
+            :remote-method="searchProduct"
+            :loading="productLoading"
+            style="width:100%"
+            disabled
+          >
+            <el-option v-for="item in productOptions" :key="item.productId" :label="item.productName" :value="item.productId"/>
+          </el-select>
+        </el-form-item>
+
+        <!-- 公共设置项 -->
+        <el-divider content-position="left">折扣设置(以下设置将应用于所有选中商品)</el-divider>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="原价" prop="originalPrice">
+              <el-input-number v-model="form.originalPrice" :precision="2" :min="0" placeholder="请输入原价" style="width:100%" @change="handleOriginalPriceChange"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="库存" prop="stock">
+              <el-input-number v-model="form.stock" :min="0" placeholder="请输入库存数量" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 折扣滑块:支持小数步长 0.1 -->
+        <el-form-item label="折扣" prop="discount" class="discount-slider-item">
+          <el-slider
+            v-model="form.discountValue"
+            :marks="discountMarks"
+            :min="1"
+            :max="10"
+            :step="0.1"
+            show-input
+            @change="handleDiscountChange"
+          />
+        </el-form-item>
+
+        <el-form-item label="折扣价格" prop="discountPrice">
+          <el-input-number v-model="form.discountPrice" :precision="2" :min="0" placeholder="自动计算或手动输入" style="width:100%" @change="handleDiscountPriceChange"/>
+        </el-form-item>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="开始时间" prop="startTime">
+              <el-date-picker v-model="form.startTime" type="datetime" placeholder="选择开始时间" value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="结束时间" prop="endTime">
+              <el-date-picker v-model="form.endTime" type="datetime" placeholder="选择结束时间" value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="1">上架</el-radio>
+            <el-radio :label="0">下架</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" :rows="3"/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm" :loading="submitLoading">{{ form.id ? '确 定' : `确 定 (${selectedProducts.length}个商品)` }}</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  addProductDiscount,
+  delProductDiscount,
+  getProductDiscount,
+  listProductDiscount,
+  updateProductDiscount
+} from '@/api/hisStore/productDiscount'
+import { getStoreProduct, listStoreProduct } from '@/api/hisStore/storeProduct'
+
+export default {
+  name: 'ProductDiscount',
+  data() {
+    return {
+      loading: true,
+      showSearch: true,
+      list: [],
+      total: 0,
+      title: '',
+      open: false,
+      submitLoading: false,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        productName: null,
+        status: null,
+        timeRange: null,
+        beginTime: null,
+        endTime: null
+      },
+      form: {},
+      rules: {
+        originalPrice: [{ required: true, message: '请输入原价', trigger: 'blur' }],
+        discount: [{ required: true, message: '请设置折扣', trigger: 'change' }],
+        discountPrice: [{ required: true, message: '请输入折扣价格', trigger: 'blur' }],
+        stock: [{ required: true, message: '请输入库存', trigger: 'blur' }],
+        startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
+        endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
+        status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+      },
+      productLoading: false,
+      productOptions: [],
+      selectedProducts: [],
+      discountMarks: {
+        1: '1折',
+        5: '5折',
+        10: '不打折'
+      }
+    }
+  },
+  watch: {
+    'form.discountValue': {
+      handler(newVal) {
+        if (newVal !== undefined && newVal !== null) {
+          this.form.discount = newVal / 10
+          this.$nextTick(() => {
+            this.calcDiscountPrice()
+          })
+        }
+      },
+      immediate: true
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      const params = { ...this.queryParams }
+      if (this.queryParams.timeRange && this.queryParams.timeRange.length === 2) {
+        params.beginTime = this.queryParams.timeRange[0]
+        params.endTime = this.queryParams.timeRange[1]
+      }
+      delete params.timeRange
+      listProductDiscount(params).then(response => {
+        this.list = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      })
+    },
+
+    searchProduct(query) {
+      if (query !== '') {
+        this.productLoading = true
+        listStoreProduct({ productName: query, isDel: 0, pageSize: 50 }).then(response => {
+          const currentSelected = this.selectedProducts.map(id =>
+            this.productOptions.find(p => p.productId === id)
+          ).filter(Boolean)
+          this.productOptions = [...currentSelected, ...(response.rows || [])]
+          this.productLoading = false
+        })
+      } else {
+        if (this.selectedProducts.length > 0) {
+          this.productOptions = this.selectedProducts.map(id =>
+            this.productOptions.find(p => p.productId === id)
+          ).filter(Boolean)
+        } else {
+          this.productOptions = []
+        }
+      }
+    },
+
+    isProductSelected(productId) {
+      return this.selectedProducts.includes(productId)
+    },
+
+    handleOriginalPriceChange(val) {
+      console.log('原价变化:', val)
+      this.calcDiscountPrice()
+    },
+
+    handleDiscountChange(val) {
+      console.log('折扣滑块变化:', val)
+      this.form.discount = this.form.discountValue / 10
+      this.calcDiscountPrice()
+    },
+
+    handleDiscountPriceChange(val) {
+      console.log('折扣价格变化:', val)
+      if (this.form.originalPrice && this.form.originalPrice > 0 && val >= 0) {
+        const newDiscount = parseFloat((val / this.form.originalPrice).toFixed(4))
+        if (newDiscount >= 0.1 && newDiscount <= 1) {
+          this.form.discountValue = newDiscount * 10
+          this.form.discount = newDiscount
+        }
+      }
+    },
+
+    calcDiscountPrice() {
+      if (this.form.originalPrice && this.form.originalPrice > 0 && this.form.discount !== undefined && this.form.discount !== null) {
+        if (this.form.discount >= 1) {
+          this.form.discountPrice = this.form.originalPrice
+        } else {
+          console.log("折扣价格-------》",parseFloat((this.form.originalPrice * this.form.discount).toFixed(2)))
+          this.form.discountPrice = parseFloat((this.form.originalPrice * this.form.discount).toFixed(2))
+        }
+        console.log('计算结果 - 原价:', this.form.originalPrice, '折扣:', this.form.discount, '价格:', this.form.discountPrice)
+      } else {
+        if (!this.form.originalPrice || this.form.originalPrice <= 0) {
+          this.form.discountPrice = null
+        }
+      }
+    },
+
+    cancel() {
+      this.open = false
+      this.reset()
+    },
+
+    reset() {
+      this.form = {
+        id: null,
+        productId: null,
+        stock: 0,
+        originalPrice: null,
+        discount: 1,
+        discountValue: 10,
+        discountPrice: null,
+        startTime: null,
+        endTime: null,
+        status: 1,
+        remark: null
+      }
+      this.selectedProducts = []
+      this.productOptions = []
+      this.submitLoading = false
+      this.resetForm('form')
+    },
+
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+
+    handleAdd() {
+      this.reset()
+      this.open = true
+      this.title = '批量添加限时折扣'
+    },
+
+    handleUpdate(row) {
+      this.reset()
+      getProductDiscount(row.id).then(response => {
+        this.form = response.data
+        if (this.form.discount) {
+          this.form.discountValue = this.form.discount * 10
+        }
+        if (this.form.productId) {
+          getStoreProduct(this.form.productId).then(res => {
+            if (res.data) {
+              this.productOptions = [res.data]
+            }
+          })
+        }
+        this.selectedProducts = [this.form.productId]
+        this.open = true
+        this.title = '编辑限时折扣'
+      })
+    },
+
+    handleDelete(row) {
+      const ids = row.id
+      this.$confirm('是否确认删除限时折扣编号为"' + ids + '"的数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return delProductDiscount(ids)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+
+    handleExport() {
+      this.download('/store/store/productDiscount/export', {
+        ...this.queryParams
+      }, `productDiscount_${new Date().getTime()}.xlsx`)
+    },
+
+    async submitForm() {
+      if (!this.form.id && this.selectedProducts.length === 0) {
+        this.msgError('请至少选择一个商品')
+        return
+      }
+
+      try {
+        await this.$refs['form'].validate()
+
+        this.submitLoading = true
+
+        if (this.form.id) {
+          const submitData = { ...this.form }
+          delete submitData.discountValue
+          delete submitData.productIds
+
+          await updateProductDiscount(submitData)
+          this.msgSuccess('修改成功')
+          this.open = false
+          this.getList()
+        } else {
+          const baseData = { ...this.form }
+          delete baseData.id
+          delete baseData.productId
+          delete baseData.discountValue
+          delete baseData.productIds
+
+          const promises = this.selectedProducts.map(productId => {
+            const productData = {
+              ...baseData,
+              productId: productId
+            }
+            return addProductDiscount(productData)
+          })
+
+          await Promise.all(promises)
+          this.msgSuccess(`成功添加 ${this.selectedProducts.length} 个限时折扣商品`)
+          this.open = false
+          this.getList()
+        }
+
+        this.submitLoading = false
+      } catch (error) {
+        console.error('提交失败:', error)
+        this.submitLoading = false
+        if (error !== 'cancel') {
+          this.msgError(error.message || '操作失败,请重试')
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.discount-slider-item >>> .el-slider {
+  overflow: visible;
+  margin: 0 10px;
+}
+.discount-slider-item >>> .el-slider__marks-text {
+  white-space: nowrap;
+  font-size: 12px;
+  transform: translateX(-50%) scale(0.95);
+  min-width: 40px;
+  text-align: center;
+}
+.discount-slider-item >>> .el-slider__marks {
+  height: 30px;
+}
+
+/* 多选下拉框优化 */
+>>> .el-select .el-tag {
+  max-width: 150px;
+}
+>>> .el-select .el-tag__close {
+  margin-left: 4px;
+}
+</style>

+ 347 - 0
src/views/hisStore/productFlashSale/index.vue

@@ -0,0 +1,347 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="商品名称" prop="productName">
+        <el-input
+          v-model="queryParams.productName"
+          placeholder="请输入商品名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option label="上架" :value="1"/>
+          <el-option label="下架" :value="0"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['store:productFlashSale:add']">新增</el-button>
+      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['store:productFlashSale:export']">导出</el-button>-->
+<!--      </el-col>-->
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="list" border>
+      <el-table-column label="序号" type="index" width="55" align="center" />
+      <el-table-column label="商品信息" align="center" min-width="200">
+        <template slot-scope="scope">
+          <div style="display:flex;align-items:center;">
+            <el-image v-if="scope.row.productImage" style="width:50px;height:50px;margin-right:10px;" :src="scope.row.productImage" fit="contain" :preview-src-list="[scope.row.productImage]"/>
+            <span>{{ scope.row.productName || '-' }}</span>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="原价" align="center" prop="originalPrice" width="100">
+        <template slot-scope="scope">
+          <span v-if="scope.row.originalPrice != null">¥{{ scope.row.originalPrice.toFixed(2) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="秒杀价" align="center" prop="flashPrice" width="110">
+        <template slot-scope="scope">
+          <span style="color:#f56c6c;font-weight:bold;">¥{{ scope.row.flashPrice ? scope.row.flashPrice.toFixed(2) : '0.00' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="库存" align="center" prop="stock" width="80" />
+      <el-table-column label="开始时间" align="center" prop="startTime" width="160">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="结束时间" align="center" prop="endTime" width="160">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="80">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.status === 1" type="success">上架</el-tag>
+          <el-tag v-else type="info">下架</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['store:productFlashSale:edit']">编辑</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['store:productFlashSale:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="选择商品" prop="productId" v-if="!form.id">
+          <el-select
+            v-model="form.productId"
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请输入商品名称搜索"
+            :remote-method="searchProduct"
+            :loading="productLoading"
+            style="width:100%"
+          >
+            <el-option
+              v-for="item in productOptions"
+              :key="item.productId"
+              :label="item.productName"
+              :value="item.productId"
+            >
+              <div style="display:flex;justify-content:space-between;align-items:center;">
+                <span style="float:left">{{ item.productName }}</span>
+                <span style="float:right;color:#8492a6;font-size:12px;margin-left:15px;">ID:{{ item.productId }} | ¥{{ item.price || item.originalPrice || '-' }}</span>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="选择商品" v-if="form.id">
+          <el-select
+            v-model="form.productId"
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请输入商品名称搜索"
+            :remote-method="searchProduct"
+            :loading="productLoading"
+            style="width:100%"
+            disabled
+          >
+            <el-option v-for="item in productOptions" :key="item.productId" :label="item.productName" :value="item.productId"/>
+          </el-select>
+        </el-form-item>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="原价" prop="originalPrice">
+              <el-input-number v-model="form.originalPrice" :precision="2" :min="0" placeholder="请输入原价" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="秒杀价" prop="flashPrice">
+              <el-input-number v-model="form.flashPrice" :precision="2" :min="0" placeholder="请输入秒杀价" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="库存" prop="stock">
+              <el-input-number v-model="form.stock" :min="0" placeholder="请输入库存数量" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态" prop="status">
+              <el-radio-group v-model="form.status">
+                <el-radio :label="1">上架</el-radio>
+                <el-radio :label="0">下架</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="开始时间" prop="startTime">
+              <el-date-picker v-model="form.startTime" type="datetime" placeholder="选择开始时间" value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="结束时间" prop="endTime">
+              <el-date-picker v-model="form.endTime" type="datetime" placeholder="选择结束时间" value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" :rows="3"/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  addProductFlashSale,
+  delProductFlashSale,
+  getProductFlashSale,
+  listProductFlashSale,
+  updateProductFlashSale
+} from '@/api/hisStore/productFlashSale'
+import { getStoreProduct, listStoreProduct } from '@/api/hisStore/storeProduct'
+
+export default {
+  name: 'ProductFlashSale',
+  data() {
+    return {
+      loading: true,
+      showSearch: true,
+      list: [],
+      total: 0,
+      title: '',
+      open: false,
+      submitLoading: false,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        productName: null,
+        status: null
+      },
+      form: {},
+      rules: {
+        productId: [{ required: true, message: '请选择商品', trigger: 'change' }],
+        originalPrice: [{ required: true, message: '请输入原价', trigger: 'blur' }],
+        flashPrice: [{ required: true, message: '请输入秒杀价', trigger: 'blur' }],
+        stock: [{ required: true, message: '请输入库存', trigger: 'blur' }],
+        startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
+        endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
+        status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+      },
+      productLoading: false,
+      productOptions: []
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      listProductFlashSale(this.queryParams).then(response => {
+        this.list = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      })
+    },
+
+    searchProduct(query) {
+      if (query !== '') {
+        this.productLoading = true
+        listStoreProduct({ productName: query, isDel: 0, pageSize: 50 }).then(response => {
+          this.productOptions = response.rows || []
+          this.productLoading = false
+        })
+      } else {
+        this.productOptions = []
+      }
+    },
+
+    cancel() {
+      this.open = false
+      this.reset()
+    },
+
+    reset() {
+      this.form = {
+        id: null,
+        productId: null,
+        originalPrice: null,
+        flashPrice: null,
+        stock: 0,
+        startTime: null,
+        endTime: null,
+        status: 1,
+        remark: null
+      }
+      this.productOptions = []
+      this.submitLoading = false
+      this.resetForm('form')
+    },
+
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+
+    handleAdd() {
+      this.reset()
+      this.open = true
+      this.title = '新增秒杀商品'
+    },
+
+    handleUpdate(row) {
+      this.reset()
+      getProductFlashSale(row.id).then(response => {
+        this.form = response.data
+        if (this.form.productId) {
+          getStoreProduct(this.form.productId).then(res => {
+            if (res.data) {
+              this.productOptions = [res.data]
+            }
+          })
+        }
+        this.open = true
+        this.title = '编辑秒杀商品'
+      })
+    },
+
+    handleDelete(row) {
+      const ids = row.id
+      this.$confirm('是否确认删除秒杀商品编号为"' + ids + '"的数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return delProductFlashSale(ids)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+
+    handleExport() {
+      this.download('/store/store/productFlashSale/export', {
+        ...this.queryParams
+      }, `productFlashSale_${new Date().getTime()}.xlsx`)
+    },
+
+    submitForm() {
+      this.$refs['form'].validate(valid => {
+        if (valid) {
+          this.submitLoading = true
+          if (this.form.id) {
+            updateProductFlashSale(this.form).then(() => {
+              this.msgSuccess('修改成功')
+              this.open = false
+              this.getList()
+              this.submitLoading = false
+            }).catch(() => {
+              this.submitLoading = false
+            })
+          } else {
+            addProductFlashSale(this.form).then(() => {
+              this.msgSuccess('新增成功')
+              this.open = false
+              this.getList()
+              this.submitLoading = false
+            }).catch(() => {
+              this.submitLoading = false
+            })
+          }
+        }
+      })
+    }
+  }
+}
+</script>

+ 515 - 0
src/views/hisStore/refundReason/index.vue

@@ -0,0 +1,515 @@
+<template>
+  <div class="app-container refund-reason-page">
+    <el-card shadow="never" class="refund-reason-page__filter">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
+        <el-form-item label="一级原因" prop="reasonName">
+          <el-input
+            v-model="queryParams.reasonName"
+            placeholder="请输入一级原因名称"
+            clearable
+            size="small"
+            style="width: 220px"
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+      <p v-show="showSearch" class="refund-reason-page__hint">
+        <i class="el-icon-info" />
+        点击每行左侧箭头展开查看二级原因;同一时间仅展开一行,便于逐层浏览。
+      </p>
+    </el-card>
+
+    <el-card shadow="never" class="refund-reason-page__table-card">
+      <el-row :gutter="10" class="mb8">
+        <el-col :span="1.5">
+          <el-button
+            type="primary"
+            plain
+            icon="el-icon-plus"
+            size="mini"
+            @click="handleAdd"
+            v-hasPermi="['store:refundReason:add']"
+          >新增一级原因</el-button>
+        </el-col>
+        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table
+        ref="refundReasonTable"
+        v-loading="loading"
+        :data="refundReasonList"
+        row-key="id"
+        :expand-row-keys="expandedRowKeys"
+        border
+        stripe
+        size="small"
+        class="refund-reason-table"
+        :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
+        @expand-change="handleExpandChange"
+      >
+        <el-table-column type="expand" width="46">
+          <template slot-scope="props">
+            <div class="refund-expand-panel">
+              <div class="refund-expand-panel__head">
+                <span class="refund-expand-panel__title">
+                  <i class="el-icon-tickets" />
+                  下属二级原因
+                </span>
+                <span class="refund-expand-panel__parent">「{{ props.row.reasonName }}」</span>
+                <el-tag size="mini" type="info" effect="plain">共 {{ props.row.childrenCount || 0 }} 条</el-tag>
+              </div>
+              <el-table
+                :key="'refund-child-' + props.row.id"
+                class="refund-reason-nested-table"
+                row-key="id"
+                :data="props.row.children"
+                size="mini"
+                border
+                v-loading="props.row.loading"
+                empty-text="暂无二级原因,可在该行操作列点击「新增二级原因」进行维护"
+              >
+                <el-table-column label="序号" type="index" width="56" align="center" />
+                <el-table-column label="二级原因" prop="reasonName" min-width="160" show-overflow-tooltip />
+                <el-table-column label="状态" align="center" prop="status" width="88">
+                  <template slot-scope="scope">
+                    <el-tag v-if="scope.row.status === 1" type="success" size="mini">正常</el-tag>
+                    <el-tag v-else type="danger" size="mini">禁用</el-tag>
+                  </template>
+                </el-table-column>
+                <el-table-column label="操作" align="center" width="168" fixed="right">
+                  <template slot-scope="scope">
+                    <el-button
+                      size="mini"
+                      type="text"
+                      icon="el-icon-edit"
+                      @click="handleUpdateChild(scope.row, props.row)"
+                      v-hasPermi="['store:refundReason:edit']"
+                    >修改</el-button>
+                    <el-button
+                      size="mini"
+                      type="text"
+                      icon="el-icon-delete"
+                      class="refund-text-danger"
+                      @click="handleDeleteChild(scope.row, props.row)"
+                      v-hasPermi="['store:refundReason:remove']"
+                    >删除</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="序号" type="index" width="60" align="center" />
+        <el-table-column label="一级原因" prop="reasonName" min-width="160" show-overflow-tooltip />
+        <el-table-column label="二级原因数量" align="center" width="110">
+          <template slot-scope="scope">
+            <el-tag size="mini" type="info" effect="plain">{{ scope.row.childrenCount || 0 }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" prop="status" width="88">
+          <template slot-scope="scope">
+            <el-tag v-if="scope.row.status === 1" type="success" size="small">正常</el-tag>
+            <el-tag v-else type="danger" size="small">禁用</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280">
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-edit"
+              @click="handleUpdate(scope.row)"
+              v-hasPermi="['store:refundReason:edit']"
+            >编辑</el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-plus"
+              @click="handleAddChild(scope.row)"
+              v-hasPermi="['store:refundReason:add']"
+            >新增二级原因</el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-delete"
+              class="refund-text-danger"
+              @click="handleDelete(scope.row)"
+              v-hasPermi="['store:refundReason:remove']"
+            >删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body custom-class="refund-reason-dialog">
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px" class="refund-reason-form">
+        <el-form-item label="原因名称" prop="reasonName">
+          <el-input v-model="form.reasonName" placeholder="请输入原因名称" maxlength="100" show-word-limit />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
+            <el-option label="正常" :value="1" />
+            <el-option label="禁用" :value="0" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="二级原因" v-if="form.typeLevel === 1 && form.id == null">
+          <div class="refund-dialog-children">
+            <el-button type="primary" size="mini" icon="el-icon-plus" @click="addChildReason">添加二级原因</el-button>
+            <div v-for="(item, index) in form.childrenReasons" :key="index" class="refund-dialog-children__row">
+              <el-input v-model="item.reasonName" placeholder="请输入二级原因名称" />
+              <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeChildReason(index)">删除</el-button>
+            </div>
+          </div>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listRefundReason, getChildrenByParentId, getRefundReason, addRefundReason, updateRefundReason, delRefundReason } from "@/api/hisStore/refundReason";
+
+export default {
+  name: "RefundReason",
+  data() {
+    return {
+      loading: true,
+      showSearch: true,
+      total: 0,
+      refundReasonList: [],
+      title: "",
+      open: false,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        reasonName: null,
+        typeLevel: 1
+      },
+      form: {},
+      expandedRowKeys: [],
+      rules: {
+        reasonName: [
+          { required: true, message: "原因名称不能为空", trigger: "blur" }
+        ],
+        status: [
+          { required: true, message: "请选择状态", trigger: "change" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    normalizeGetListArg(arg) {
+      if (arg != null && typeof arg === "object") {
+        return null;
+      }
+      return arg != null ? arg : null;
+    },
+    getList(arg) {
+      const syncChildrenParentId = this.normalizeGetListArg(arg);
+      this.loading = true;
+      listRefundReason(this.queryParams).then(response => {
+        this.refundReasonList = response.rows.map(item => {
+          return {
+            ...item,
+            children: [],
+            loading: false
+          };
+        });
+        this.total = response.total;
+        this.loading = false;
+        if (syncChildrenParentId != null) {
+          const r = this.refundReasonList.find(x => x.id == syncChildrenParentId);
+          this.expandedRowKeys = r ? [r.id] : [];
+          this.$nextTick(() => {
+            this.reloadRowChildren(syncChildrenParentId);
+          });
+        } else {
+          this.expandedRowKeys = [];
+        }
+      });
+    },
+    reloadRowChildren(parentId) {
+      const row = this.refundReasonList.find(r => r.id == parentId);
+      if (!row) {
+        return;
+      }
+      row.loading = true;
+      getChildrenByParentId(parentId)
+        .then(response => {
+          const list = response.data || [];
+          row.children = list;
+          row.childrenCount = list.length;
+        })
+        .finally(() => {
+          row.loading = false;
+        });
+    },
+    handleExpandChange(row, expandedRows) {
+      const isExpanded = expandedRows.some(r => r.id == row.id);
+      if (isExpanded) {
+        this.expandedRowKeys = [row.id];
+        this.reloadRowChildren(row.id);
+      } else {
+        this.expandedRowKeys = [];
+        this.$nextTick(() => {
+          row.children = [];
+        });
+      }
+    },
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    reset() {
+      this.form = {
+        id: null,
+        parentId: 0,
+        reasonName: null,
+        typeLevel: 1,
+        status: 1,
+        childrenReasons: []
+      };
+      this.resetForm("form");
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    handleAdd() {
+      this.reset();
+      this.form.childrenReasons = [];
+      this.open = true;
+      this.title = "新增一级原因";
+    },
+    handleAddChild(row) {
+      this.reset();
+      this.form.parentId = row.id;
+      this.form.typeLevel = 2;
+      this.open = true;
+      this.title = "新增二级原因";
+    },
+    addChildReason() {
+      this.form.childrenReasons.push({
+        reasonName: '',
+        status: 1
+      });
+    },
+    removeChildReason(index) {
+      this.form.childrenReasons.splice(index, 1);
+    },
+    handleUpdate(row) {
+      this.reset();
+      getRefundReason(row.id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "编辑一级原因";
+      });
+    },
+    handleUpdateChild(row, parent) {
+      this.reset();
+      getRefundReason(row.id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "编辑二级原因";
+      });
+    },
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateRefundReason(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              const syncId = this.form.typeLevel === 2 ? this.form.parentId : this.form.id;
+              this.getList(syncId);
+            });
+          } else {
+            addRefundReason(this.form).then(response => {
+              if (this.form.typeLevel === 1 && this.form.childrenReasons && this.form.childrenReasons.length > 0) {
+                const parentId = response.data;
+                const promises = this.form.childrenReasons
+                  .filter(item => item.reasonName && item.reasonName.trim() !== '')
+                  .map(item => {
+                    return addRefundReason({
+                      parentId: parentId,
+                      reasonName: item.reasonName,
+                      typeLevel: 2,
+                      status: item.status
+                    });
+                  });
+                if (promises.length > 0) {
+                  Promise.all(promises).then(() => {
+                    this.msgSuccess("新增成功");
+                    this.open = false;
+                    this.getList(parentId);
+                  });
+                } else {
+                  this.msgSuccess("新增成功");
+                  this.open = false;
+                  this.getList();
+                }
+              } else {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                const syncId = this.form.typeLevel === 2 ? this.form.parentId : null;
+                this.getList(syncId);
+              }
+            });
+          }
+        }
+      });
+    },
+    handleDelete(row) {
+      this.$confirm('确定删除该一级原因吗,删除将影响其下属二级原因都被同步删除,请谨慎操作。', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return delRefundReason(row.id);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    handleDeleteChild(row, parent) {
+      this.$confirm('是否确认删除该二级原因?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return delRefundReason(row.id);
+      }).then(() => {
+        this.reloadRowChildren(parent.id);
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    }
+  }
+};
+</script>
+
+<style scoped>
+.refund-reason-page__filter {
+  margin-bottom: 12px;
+}
+
+.refund-reason-page__filter >>> .el-card__body {
+  padding-bottom: 8px;
+}
+
+.refund-reason-page__hint {
+  margin: 0 0 4px;
+  padding: 8px 12px;
+  font-size: 12px;
+  line-height: 1.5;
+  color: #909399;
+  background: #f4f8ff;
+  border-radius: 4px;
+  border: 1px solid #d9ecff;
+}
+
+.refund-reason-page__hint .el-icon-info {
+  margin-right: 6px;
+  color: #409eff;
+}
+
+.refund-reason-page__table-card >>> .el-card__body {
+  padding-top: 16px;
+}
+
+.refund-reason-table >>> .el-table__expanded-cell {
+  padding: 0;
+  background: transparent;
+}
+
+.refund-expand-panel {
+  padding: 14px 16px 16px 20px;
+  margin: 4px 12px 12px 8px;
+  background: linear-gradient(180deg, #f8fafc 0%, #f0f4f8 100%);
+  border-radius: 6px;
+  border: 1px solid #e4e7ed;
+  border-left: 3px solid #409eff;
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
+}
+
+.refund-expand-panel__head {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 12px;
+}
+
+.refund-expand-panel__title {
+  font-size: 13px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.refund-expand-panel__title .el-icon-tickets {
+  margin-right: 6px;
+  color: #409eff;
+}
+
+.refund-expand-panel__parent {
+  font-size: 12px;
+  color: #606266;
+}
+
+.refund-reason-nested-table {
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+/* 避免展开行内嵌套表格误渲染展开列,出现「多一个小箭头」 */
+.refund-reason-nested-table >>> .el-table__expand-column {
+  display: none !important;
+  width: 0 !important;
+  min-width: 0 !important;
+}
+
+.refund-text-danger {
+  color: #f56c6c !important;
+}
+
+.refund-dialog-children__row {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-top: 10px;
+}
+
+.refund-dialog-children__row .el-input {
+  flex: 1;
+  max-width: 400px;
+}
+</style>
+
+<style>
+.refund-reason-dialog .el-dialog__body {
+  padding-top: 10px;
+  padding-bottom: 8px;
+}
+</style>

+ 2 - 2
src/views/hisStore/store/recommend.vue

@@ -148,7 +148,7 @@
     <!-- 添加或修改推荐店铺对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="120px">
-        <el-form-item label="推荐店铺Id" prop="storeName">
+        <el-form-item label="推荐店铺Id" prop="storeId">
           <el-select
             v-model="form.storeId"
             filterable
@@ -289,7 +289,7 @@ export default {
         this.storeLoading = true;
         // 这里应该调用实际的API来搜索店铺
         // 示例代码,需要根据实际API调整
-        listStore({ storeName: query }).then(response => {
+        listStore({ storeId: query }).then(response => {
           this.storeOptions = response.rows;
           this.storeLoading = false;
         }).catch(() => {

+ 4 - 1
src/views/hisStore/storeAfterSales/index.vue

@@ -197,7 +197,7 @@
       </el-table-column>
       <el-table-column label="售后状态" align="center" prop="salesStatus" >
           <template slot-scope="scope">
-              <div prop="status" v-for="(item, index) in salesStatusOptions"    v-if="scope.row.salesStatus==item.dictValue">{{item.dictLabel}}</div>
+              <div prop="status" v-for="(item, index) in  salesStatusOptions"    v-if="scope.row.salesStatus==item.dictValue">{{item.dictLabel}}</div>
           </template>
       </el-table-column>
       <el-table-column label="订单状态" align="center" prop="orderStatus" >
@@ -216,6 +216,9 @@
       <el-table-column label="客户退回物流单号" align="center" prop="deliverySn" />
 
       <el-table-column label="提交时间" align="center" prop="createTime" />
+      <el-table-column label="售后原因一级" align="center" prop="reasonValue1" />
+      <el-table-column label="售后原因二级" align="center" prop="reasonValue2" />
+      <el-table-column label="售后备注" align="center" prop="auditRemark" />
 
       <el-table-column label="操作" align="center" fixed="right" width="100px" class-name="small-padding fixed-width">
         <template slot-scope="scope">

+ 45 - 11
src/views/hisStore/storeOrder/dimensionStatistics/index.vue

@@ -54,7 +54,23 @@
         </el-select>
       </el-form-item>
       <el-form-item label="订单日期">
+        <!-- 恒春来企业:年月日范围 -->
         <el-date-picker
+          v-if="isHclInfo"
+          v-model="queryDate"
+          type="daterange"
+          align="right"
+          unlink-panels
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :picker-options="pickerOptions"
+          value-format="yyyy-MM-dd"
+        >
+        </el-date-picker>
+        <!-- 默认企业:月范围 -->
+        <el-date-picker
+          v-else
           v-model="queryDate"
           type="monthrange"
           align="right"
@@ -131,8 +147,7 @@
       <el-table-column label="员工账号" align="center" prop="userName"/>
       <el-table-column label="员工名称" align="center" prop="nickName"/>
       <el-table-column label="总单数" align="center" prop="totalNum"/>
-      <el-table-column label="总金额" align="center" prop="totalPrice"/>
-      <el-table-column label="实付金额" align="center" prop="actualPaymentAmount"/>
+      <el-table-column v-if="hclInfo !== 'hcl'" label="总金额" align="center" prop="totalPrice"/>
       <el-table-column v-if="zdyInfo === 'gzzdy'" label="在途单数" align="center" prop="transitNum"/>
       <el-table-column v-if="zdyInfo === 'gzzdy'" label="在途金额" align="center" prop="transitPrice"/>
       <el-table-column label="成交单数" align="center" prop="dealNum"/>
@@ -150,6 +165,7 @@
       <el-table-column label="签收金额" align="center" prop="signFPrice"/>
       <el-table-column label="退单数" align="center" prop="chargebackNum"/>
       <el-table-column label="退单金额" align="center" prop="chargebackPrice"/>
+      <el-table-column v-if="hclInfo === 'hcl'" label="实付总金额" align="center" prop="actualPaymentAmount"/>
     </el-table>
 
     <pagination
@@ -173,6 +189,7 @@ export default {
   data() {
     return {
       zdyInfo: process.env.VUE_APP_FS_USER_INFO,
+      hclInfo: process.env.VUE_APP_FS_USER_INFO,
       activeName: '1',
       // 遮罩层
       loading: true,
@@ -245,7 +262,13 @@ export default {
       }
     }
   },
+  computed: {
+    isHclInfo() {
+      return this.hclInfo === 'hcl';
+    }
+  },
   created() {
+    console.log('当前环境变量 VUE_APP_FS_USER_INFO:', process.env.VUE_APP_FS_USER_INFO);
     /**
      * 赋值默认日期
      * **/
@@ -366,17 +389,28 @@ export default {
      * 赋值时间组件默认日期
      * **/
     getDefaultDateRange() {
-      const end = new Date()
-      const start = new Date()
-      start.setMonth(start.getMonth() - 2)
+      const end = new Date();
+      const start = new Date();
+      start.setMonth(start.getMonth() - 2); // 最近三个月
 
-      const formatDate = (date) => {
-        const year = date.getFullYear()
-        const month = String(date.getMonth() + 1).padStart(2, '0')
-        return `${year}-${month}-01`
-      }
+      const formatDate = (date, toMonthFirst = false) => {
+        const year = date.getFullYear();
+        const month = String(date.getMonth() + 1).padStart(2, '0');
+        if (toMonthFirst) {
+          return `${year}-${month}-01`;
+        } else {
+          const day = String(date.getDate()).padStart(2, '0');
+          return `${year}-${month}-${day}`;
+        }
+      };
 
-      return [formatDate(start), formatDate(end)]
+      if (this.isHclInfo) {
+        // 恒春:返回具体日期范围
+        return [formatDate(start), formatDate(end)];
+      } else {
+        // 默认:返回月份范围(月份首日)
+        return [formatDate(start, true), formatDate(end, true)];
+      }
     },
 
     getSpanArr() {

+ 13 - 2
src/views/hisStore/storeOrder/healthStoreList.vue

@@ -461,9 +461,14 @@
       <el-table-column label="ERP电话" align="center" prop="erpPhone" width="120px" v-if="SFDFopen"/>
       <el-table-column label="ERP账号" align="center" prop="erpAccount" width="120px" v-if="SFDFopen"/>
       <el-table-column label="小程序名称" align="center" prop="miniProgramName"/>
-      <el-table-column align="center" label="用户昵称" prop="nickname" width="150px">
+      <el-table-column label="用户昵称" align="center" prop="nickname" width="150px" >
         <template slot-scope="scope">
-          <span>{{ scope.row.nickname }} </span>
+          <span>{{scope.row.userNickName}} </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="手机号" align="center" prop="nickname" width="150px" >
+        <template slot-scope="scope">
+          <span>{{scope.row.nickname}} </span>
         </template>
       </el-table-column>
       <el-table-column align="center" label="收件人" prop="realName" width="150px">
@@ -543,6 +548,9 @@
         </template>
       </el-table-column>
 
+      <el-table-column label="售后原因一级" align="center" prop="reasonValue1" width="120px" />
+      <el-table-column label="售后原因二级" align="center" prop="reasonValue2" width="120px" />
+      <el-table-column label="售后备注" align="center" prop="auditRemark" width="150px" />
       <el-table-column align="center" fixed="right" label="操作" width="80px">
         <template slot-scope="scope">
           <el-button
@@ -1115,6 +1123,9 @@ export default {
         { key: 'deliveryName', label: '快递公司', checked: false },
         { key: 'deliveryId', label: '快递单号', checked: false },
         { key: 'remark', label: '备注', checked: false },
+        { key: 'reasonValue1', label: '售后原因一级', checked: false },
+        { key: 'reasonValue2', label: '售后原因二级', checked: false },
+        { key: 'auditRemark', label: '售后备注', checked: false },
 
       ],
       appMallOptions:[],

+ 13 - 1
src/views/hisStore/storeOrder/index.vue

@@ -482,9 +482,14 @@
       <el-table-column label="小程序名称" width="120px" align="center" prop="miniProgramName"/>
       <el-table-column label="用户昵称" align="center" prop="nickname" width="150px" >
           <template slot-scope="scope">
-              <span>{{scope.row.nickname}} </span>
+              <span>{{scope.row.userNickName}} </span>
           </template>
       </el-table-column>
+      <el-table-column label="手机号" align="center" prop="nickname" width="150px" >
+        <template slot-scope="scope">
+          <span>{{scope.row.nickname}} </span>
+        </template>
+      </el-table-column>
       <el-table-column label="收件人" align="center" prop="realName" width="150px" >
           <template slot-scope="scope">
               <span>{{scope.row.realName}} </span>
@@ -595,6 +600,9 @@
           </template>
       </el-table-column>
 
+      <el-table-column label="售后原因一级" align="center" prop="reasonValue1" width="120px" />
+      <el-table-column label="售后原因二级" align="center" prop="reasonValue2" width="120px" />
+      <el-table-column label="售后备注" align="center" prop="auditRemark" width="150px" />
       <el-table-column label="操作" fixed="right" width="80px" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
@@ -1211,7 +1219,11 @@ export default {
         { key: 'deliveryId', label: '快递单号', checked: false },
         { key: 'remark', label: '备注', checked: false },
         { key: 'isAudit', label: '是否审核', checked: false },
+        { key: 'userNickName', label: '用户昵称', checked: false },
         { key: 'orderRemark', label: '订单备注', checked: false },
+        { key: 'reasonValue1', label: '售后原因一级', checked: false },
+        { key: 'reasonValue2', label: '售后原因二级', checked: false },
+        { key: 'auditRemark', label: '售后备注', checked: false },
       ],
 
       itemlist : [],

+ 26 - 6
src/views/hisStore/storeProduct/index.vue

@@ -47,7 +47,7 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="所属店铺" v-if="this.isStores">
+      <el-form-item label="" v-if="this.isStores">
         <el-select style="width: 240px" v-model="queryParams.storeIds" placeholder="请选择店铺" clearable size="small" >
           <el-option
             v-for="item in storeOptions"
@@ -763,8 +763,18 @@
           <el-col :span="24">
             <el-form-item label="运费模板:" prop="tempId">
               <div class="acea-row">
-                <el-select v-model="form.tempId"  class="mr20">
-                  <el-option v-for="(item,index) in templateList" :value="item.id" :key="index" :label="item.name">
+                <el-select
+                  v-model="form.tempId"
+                  class="mr20"
+                  filterable
+                  placeholder="可以输入文字进行过滤匹配"
+                >
+                  <el-option
+                    v-for="(item, index) in templateList"
+                    :value="item.id"
+                    :key="index"
+                    :label="item.name"
+                  >
                   </el-option>
                 </el-select>
               </div>
@@ -880,7 +890,7 @@
             />
           </el-select>
         </el-form-item>
-        <el-form-item label="所属店铺" prop="storeId" v-if="this.isStores">
+        <el-form-item label="所属店铺" v-if="this.isStores">
           <el-select style="width: 240px" v-model="form.storeId" placeholder="请选择店铺" clearable size="small" >
             <el-option
               v-for="item in storeOptions"
@@ -1269,6 +1279,9 @@ export default {
       if (row.isAudit == 0) {
         return '待审核';
       }
+      if (row.isAudit == 2) {
+        return '审核退回';
+      }
       const option = this.isShowOptions.find(item => item.dictValue == row.isShow);
       return option ? option.dictLabel : '未知状态';
     },
@@ -1277,8 +1290,15 @@ export default {
       if (row.isAudit == 0) {
         return 'warning';
       }
-      // 根据你的业务逻辑返回不同的类型,如:success, danger, info等
-      return row.isShow == 1 ? 'success' : 'info';
+      if (row.isAudit == 2) {
+        return 'danger';
+      }
+      const statusMap = {
+        0: 'warning',
+        1: 'success',
+        2: 'danger'
+      };
+      return statusMap[row.isShow] || 'info';
     },
     cancel1(){
       this.open1 = false;

+ 38 - 3
src/views/hisStore/storeProduct/indexZm.vue

@@ -791,9 +791,19 @@
           </el-col>
           <el-col :span="24">
             <el-form-item label="运费模板:" prop="tempId">
-              <div class="acea-row">
-                <el-select v-model="form.tempId"  class="mr20">
-                  <el-option v-for="(item,index) in templateList" :value="item.id" :key="index" :label="item.name">
+             <div class="acea-row">
+                <el-select
+                  v-model="form.tempId"
+                  class="mr20"
+                  filterable
+                  placeholder="可以输入文字进行过滤匹配"
+                >
+                  <el-option
+                    v-for="(item, index) in templateList"
+                    :value="item.id"
+                    :key="index"
+                    :label="item.name"
+                  >
                   </el-option>
                 </el-select>
               </div>
@@ -879,6 +889,19 @@
           </el-col>
 
         </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="单次购买数量" prop="singlePurchaseLimit">
+              <el-input-number
+                :min="0"
+                v-model="form.singlePurchaseLimit"
+                placeholder="单次下单最多件数,0不限"
+                :controls="true"
+                @input="handleSinglePurchaseLimitInput"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
         <el-form-item label="推广分类" prop="tuiCateId">
           <el-select style="width: 240px" v-model="form.tuiCateId" placeholder="请选择推广分类" clearable size="small" >
             <el-option
@@ -1514,6 +1537,17 @@ export default {
         }
       }
     },
+    handleSinglePurchaseLimitInput(value) {
+      if (value !== null && value !== undefined && value !== '') {
+        if (isNaN(value) || value < 0) {
+          this.$nextTick(() => {
+            this.form.singlePurchaseLimit = null;
+          });
+        } else {
+          this.form.singlePurchaseLimit = Math.floor(Number(value));
+        }
+      }
+    },
     // 删除规格
     handleRemoveRole (index) {
       this.attrs.splice(index, 1);
@@ -1779,6 +1813,7 @@ export default {
         contraindications: null, // 禁忌
         precautions: null ,// 注意事项
         purchaseLimit: null, // 限购数量
+        singlePurchaseLimit: null, // 单次购买数量上限
         userEndCategoryIds: [],
         tagIds: []
       };

+ 81 - 7
src/views/hisStore/userEndCategory/index.vue

@@ -50,7 +50,7 @@
           <el-tag v-else type="info">隐藏</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="排序" align="center" prop="sort" width="80" />
+      <el-table-column label="分类排序" align="center" prop="sort" width="80" />
       <el-table-column label="关联商品" align="center" width="120">
         <template slot-scope="scope">
           <el-button type="text" @click="showCategoryProducts(scope.row)">查看</el-button>
@@ -97,11 +97,26 @@
     </el-dialog>
 
     <!-- 关联商品弹窗 -->
-    <el-dialog title="关联商品" :visible.sync="productsOpen" width="960px" append-to-body>
-      <div class="mb8">关联商品数量: {{ productsTotal }}个</div>
+    <el-dialog title="关联商品" :visible.sync="productsOpen" width="1000px" append-to-body @closed="onProductsDialogClosed">
+      <div class="products-toolbar mb8">
+        <el-button
+          v-hasPermi="['store:userEndCategory:edit']"
+          size="small"
+          :type="productsSortEditMode ? 'warning' : 'default'"
+          @click="toggleProductsSortEdit"
+        >{{ productsSortEditMode ? '取消调整' : '调整排序' }}</el-button>
+        <el-button
+          v-hasPermi="['store:userEndCategory:edit']"
+          type="primary"
+          size="small"
+          :disabled="!productsSortEditMode"
+          @click="saveProductsSortAction"
+        >保存排序</el-button>
+        <span class="products-count">关联商品数量: {{ productsTotal }}个</span>
+      </div>
       <el-table v-loading="productsLoading" :data="productsList" border>
         <el-table-column label="商品ID" align="center" prop="productId" width="80" />
-        <el-table-column label="商品名称" align="center" prop="productName" min-width="140" show-overflow-tooltip />
+        <el-table-column label="商品名称" align="center" prop="productName" min-width="100" show-overflow-tooltip />
         <el-table-column label="售价" align="center" width="100">
           <template slot-scope="scope">
             <span v-if="scope.row.price != null">¥{{ scope.row.price.toFixed(2) }}</span>
@@ -123,6 +138,20 @@
             <span v-else>-</span>
           </template>
         </el-table-column>
+        <el-table-column label="商品排序" align="center" width="140">
+          <template slot-scope="scope">
+            <el-input-number
+              v-model="scope.row.sort"
+              :min="0"
+              :max="9999"
+              :step="1"
+              size="small"
+              controls-position="right"
+              :disabled="!productsSortEditMode"
+              class="product-sort-input"
+            />
+          </template>
+        </el-table-column>
       </el-table>
       <pagination v-show="productsTotal>0" :total="productsTotal" :page.sync="productsQuery.pageNum" :limit.sync="productsQuery.pageSize" @pagination="loadCategoryProducts" />
     </el-dialog>
@@ -130,7 +159,7 @@
 </template>
 
 <script>
-import { listUserEndCategory, getUserEndCategory, addUserEndCategory, updateUserEndCategory, delUserEndCategory, listCategoryProducts } from '@/api/hisStore/userEndCategory'
+import { listUserEndCategory, getUserEndCategory, addUserEndCategory, updateUserEndCategory, delUserEndCategory, listCategoryProducts, saveCategoryProductsSort } from '@/api/hisStore/userEndCategory'
 import Material from '@/components/Material'
 
 export default {
@@ -169,7 +198,8 @@ export default {
       productsList: [],
       productsTotal: 0,
       productsQuery: { pageNum: 1, pageSize: 10 },
-      currentCategoryId: null
+      currentCategoryId: null,
+      productsSortEditMode: false
     }
   },
   created() {
@@ -252,14 +282,44 @@ export default {
     showCategoryProducts(row) {
       this.currentCategoryId = row.id
       this.productsQuery = { pageNum: 1, pageSize: 10 }
+      this.productsSortEditMode = false
       this.productsOpen = true
       this.loadCategoryProducts()
     },
+    onProductsDialogClosed() {
+      this.productsSortEditMode = false
+    },
+    toggleProductsSortEdit() {
+      if (this.productsSortEditMode) {
+        this.productsSortEditMode = false
+        this.loadCategoryProducts()
+      } else {
+        this.productsSortEditMode = true
+      }
+    },
+    saveProductsSortAction() {
+      if (!this.productsSortEditMode || !this.currentCategoryId) return
+      const payload = this.productsList.map(r => {
+        let s = r.sort != null && r.sort !== '' ? Number(r.sort) : 0
+        if (Number.isNaN(s)) s = 0
+        s = Math.min(9999, Math.max(0, s))
+        return { productId: r.productId, sort: s }
+      })
+      saveCategoryProductsSort(this.currentCategoryId, payload).then(() => {
+        this.msgSuccess('保存成功')
+        this.productsSortEditMode = false
+        this.loadCategoryProducts()
+      })
+    },
     loadCategoryProducts() {
       if (!this.currentCategoryId) return
       this.productsLoading = true
       listCategoryProducts(this.currentCategoryId, this.productsQuery).then(response => {
-        this.productsList = response.rows || []
+        const rows = response.rows || []
+        this.productsList = rows.map(r => ({
+          ...r,
+          sort: r.sort != null && r.sort !== '' ? Number(r.sort) : 0
+        }))
         this.productsTotal = response.total || 0
         this.productsLoading = false
       })
@@ -270,4 +330,18 @@ export default {
 
 <style scoped>
 .mr4 { margin-right: 4px; }
+.products-toolbar {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 8px;
+}
+.products-count {
+  margin-left: auto;
+  color: #606266;
+  font-size: 13px;
+}
+.product-sort-input >>> .el-input__inner {
+  text-align: left;
+}
 </style>

+ 73 - 23
src/views/live/components/productAfterSalesOrder.vue

@@ -80,6 +80,16 @@
                   <el-tag  v-for="(item, index) in salesStatusOptions"    v-if="afterSales.salesStatus==item.dictValue" >{{item.dictLabel}}</el-tag>
                 </span>
           </el-descriptions-item>
+          <el-descriptions-item label="财务审核原因"  v-if="afterSales!=null && afterSales.status >= 4">
+                <span>
+                  {{afterSales.auditReasonName}}
+                </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="财务审核备注"  v-if="afterSales!=null && afterSales.status >= 4">
+                <span>
+                  {{afterSales.auditRemark}}
+                </span>
+          </el-descriptions-item>
         </el-descriptions>
         <div style="margin: 20px 0px">
           <span class="font-small">收货信息</span>
@@ -198,18 +208,38 @@
         <el-form-item label="退款金额" prop="refundAmount"  >
           <el-input-number v-model="form.refundAmount"  />
         </el-form-item>
-<!--        <el-form-item label="退货联系人" prop="consignee"  v-if="form.serviceType==1" >-->
-<!--          <el-input v-model="form.consignee" placeholder="请输入收货人" />-->
-<!--        </el-form-item>-->
-<!--        <el-form-item label="退货手机号" prop="phoneNumber"  v-if="form.serviceType==1">-->
-<!--          <el-input v-model="form.phoneNumber" placeholder="请输入手机号" />-->
-<!--        </el-form-item>-->
-<!--        <el-form-item label="退货地址" prop="address" v-if="form.serviceType==1">-->
-<!--          <el-input v-model="form.address" placeholder="请输入地址" />-->
-<!--        </el-form-item>-->
+        <el-form-item label="一级原因" prop="reasonId1">
+          <el-select v-model="form.reasonId1" placeholder="请选择一级原因" @change="handleReason1Change" style="width: 100%">
+            <el-option
+              v-for="item in reasonList"
+              :key="item.id"
+              :label="item.reasonName"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="二级原因" prop="reasonId2">
+          <el-select v-model="form.reasonId2" placeholder="请选择二级原因" style="width: 100%">
+            <el-option
+              v-for="item in reason2List"
+              :key="item.id"
+              :label="item.reasonName"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="审核备注" prop="auditRemark">
+          <el-input
+            type="textarea"
+            :rows="3"
+            placeholder="请输入审核备注"
+            v-model="form.auditRemark">
+          </el-input>
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitAuditForm">确 定</el-button>
+        <el-button @click="audit.open = false">取 消</el-button>
       </div>
     </el-dialog>
     <el-dialog :title="show.title" v-if="show.open" :visible.sync="show.open" width="1000px" append-to-body>
@@ -238,6 +268,7 @@
 
 <script>
 import {getLiveAfterSales,cancel,refund,audit,updateLiveAfterSales,audit1,audit2,handleImmediatelyRefund } from "@/api/live/liveAfterSales";
+import {getRefundReasonTree} from "@/api/hisStore/refundReason";
 
 import productOrder from "./productOrder";
 export default {
@@ -250,7 +281,7 @@ export default {
         title:"订单详情"
       },
       audit:{
-        title:"审核",
+        title:"财务审核",
         open:false,
       },
       add:{
@@ -284,7 +315,9 @@ export default {
         consignee:null,
         phoneNumber:null,
         address:null,
-
+        reasonId1: null,
+        reasonId2: null,
+        auditRemark: ''
       },
       rules:{
         status: [
@@ -293,17 +326,15 @@ export default {
         refundAmount: [
           { required: true, message: "退款金额不能为空", trigger: "blur" }
         ],
-        consignee: [
-          { required: true, message: "退货联系人不能为空", trigger: "blur" }
+        reasonId1: [
+          { required: true, message: "请选择一级原因", trigger: "change" }
         ],
-        phoneNumber: [
-          { required: true, message: "退货手机号不能为空", trigger: "blur" }
-        ],
-        address: [
-          { required: true, message: "退货地址不能为空", trigger: "blur" }
-        ],
-
-      }
+        reasonId2: [
+          { required: true, message: "请选择二级原因", trigger: "change" }
+        ]
+      },
+      reasonList: [],
+      reason2List: []
 
     };
   },
@@ -317,6 +348,7 @@ export default {
     this.getDicts("store_after_sales_service_type").then((response) => {
       this.serviceTypeOptions = response.data;
     });
+    this.getRefundReasonTree();
   },
   computed: {
     goodsTotal() {
@@ -365,8 +397,21 @@ export default {
     handleRefund(){
       this.audit.open=true;
       this.form.salesId=this.afterSales.id;
-      //this.form.refundAmount=this.afterSales.refundAmount;
-      this.form.refundAmount=this.order.payMoney; //实付金额
+      this.form.refundAmount=this.order.payMoney;
+      this.form.reasonId1 = null;
+      this.form.reasonId2 = null;
+      this.form.auditRemark = '';
+      this.reason2List = [];
+    },
+    handleReason1Change(val) {
+      this.form.reasonId2 = null;
+      this.reason2List = [];
+      if (val) {
+        const selected = this.reasonList.find(item => item.id === val);
+        if (selected && selected.children) {
+          this.reason2List = selected.children;
+        }
+      }
     },
     handleImmediatelyRefund(){
       let _this = this;
@@ -471,6 +516,11 @@ export default {
         this.user=response.user;
         this.order=response.order;
       });
+    },
+    getRefundReasonTree() {
+      getRefundReasonTree().then(response => {
+        this.reasonList = response.data;
+      });
     }
   }
 };

+ 3 - 2
src/views/live/liveAfteraSales/index.vue

@@ -211,8 +211,9 @@
       <el-table-column label="客户退货物流名称" align="center" prop="deliveryName" />
 
       <el-table-column label="提交时间" align="center" prop="createTime" />
-
-
+      <el-table-column label="售后原因一级" align="center" prop="reasonValue1" />
+      <el-table-column label="售后原因二级" align="center" prop="reasonValue2" />
+      <el-table-column label="售后备注" align="center" prop="auditRemark" />
 
       <el-table-column label="操作" align="center" fixed="right" width="100px" class-name="small-padding fixed-width">
         <template slot-scope="scope">

+ 1 - 1
src/views/live/liveAnchor/index.vue

@@ -356,7 +356,7 @@ export default {
   }
 };
 </script>
-<style >
+<style  scoped>
 .el-form-item__label {
   width: 120px !important;
 }

+ 25 - 0
src/views/live/liveConfig/liveLotteryConf.vue

@@ -248,6 +248,7 @@
                   remote
                 reserve-keyword
                 placeholder="请输入关键字搜索"
+                :remote-method="fetchProducts"
                 :loading="loadingProducts"
                 size="small"
                 style="width: 180px"
@@ -521,6 +522,30 @@ export default {
     );
   },
   methods: {
+
+    async fetchProducts(query) {
+
+      if (!query) {
+        this.productOptions = [];
+        return;
+      }
+      const queryParam = {
+        liveId : this.liveId,
+        keywords: query
+      }
+      this.loadingProducts = true;
+      try {
+        listLiveGoods(queryParam).then(response => {
+          this.productOptions = response.rows;
+        })
+      } catch (err) {
+        console.error('查询商品失败:', err);
+        this.productOptions = [];
+      } finally {
+        this.loadingProducts = false;
+      }
+    },
+
     getProducts(){
       const queryParam = {
         liveId : this.liveId

+ 2 - 2
src/views/live/liveConfig/liveRedConf.vue

@@ -177,8 +177,8 @@
         <el-form-item label="直播间ID" prop="liveId">
           <el-input v-model="form.liveId" placeholder="请输入直播间ID" :disabled="canLiveId"/>
         </el-form-item>
-        <el-form-item label="芳华币数" prop="redNum">
-          <el-input v-model="form.redNum" placeholder="请输入芳华币数量" />
+        <el-form-item label="积分" prop="redNum">
+          <el-input v-model="form.redNum" placeholder="请输入积分数量" />
         </el-form-item>
         <el-form-item label="中奖份量" prop="totalLots">
           <el-input v-model="form.totalLots" placeholder="请输入可中奖份量" />

+ 6 - 6
src/views/live/liveCoupon/index.vue

@@ -57,7 +57,7 @@
           icon="el-icon-plus"
           size="mini"
           @click="handleAdd"
-          v-hasPermi="['store:storeCoupon:add']"
+          v-hasPermi="['live:liveCoupon:add']"
         >新增</el-button>
       </el-col>
 
@@ -68,7 +68,7 @@
           size="mini"
           :disabled="multiple"
           @click="handleDelete"
-          v-hasPermi="['store:storeCoupon:remove']"
+          v-hasPermi="['live:liveCoupon:remove']"
         >删除</el-button>
       </el-col>
       <el-col :span="1.5">
@@ -77,7 +77,7 @@
           size="mini"
           :disabled="multiple"
           @click="handleBatchPublish"
-          v-hasPermi="['store:storeCoupon:batchPublish']"
+          v-hasPermi="['live:liveCoupon:batchPublish']"
         >批量发布</el-button>
       </el-col>
 
@@ -116,21 +116,21 @@
             size="mini"
             type="text"
             @click="handlePublish(scope.row)"
-            v-hasPermi="['store:storeCoupon:publish']"
+            v-hasPermi="['live:liveCoupon:publish']"
           >发布</el-button>
           <el-button
             size="mini"
             type="text"
             icon="el-icon-edit"
             @click="handleUpdate(scope.row)"
-            v-hasPermi="['store:storeCoupon:edit']"
+            v-hasPermi="['live:liveCoupon:edit']"
           >修改</el-button>
           <el-button
             size="mini"
             type="text"
             icon="el-icon-delete"
             @click="handleDelete(scope.row)"
-            v-hasPermi="['store:storeCoupon:remove']"
+            v-hasPermi="['live:liveCoupon:remove']"
           >删除</el-button>
         </template>
       </el-table-column>

+ 3 - 0
src/views/live/liveOrder/index.vue

@@ -264,6 +264,9 @@
           <dict-tag :options="orderStatusOptions" :value="scope.row.status"/>
         </template>
       </el-table-column>
+      <el-table-column label="售后原因一级" align="center" prop="reasonValue1" />
+      <el-table-column label="售后原因二级" align="center" prop="reasonValue2" />
+      <el-table-column label="售后备注" align="center" prop="auditRemark" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
         <template slot-scope="scope">
           <el-button

+ 18 - 4
src/views/live/liveOrder/liveOrderDetails.vue

@@ -73,6 +73,16 @@
           <el-descriptions-item label=" 公众号/渠道" ><span v-if="item!=null">{{item.channel}}</span></el-descriptions-item>
           <el-descriptions-item label=" 渠道" ><span v-if="item!=null"><dict-tag :options="channelOptions" :value="item.orderChannel"/></span></el-descriptions-item>
           <el-descriptions-item label=" 企微主体" ><span v-if="item!=null"><dict-tag :options="qwSubjectOptions" :value="item.qwSubject"/></span></el-descriptions-item>
+          <el-descriptions-item label="财务审核原因" v-if="afterSales!=null && afterSales.auditReasonName">
+              <span>
+                {{ afterSales.auditReasonName }}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="财务审核备注" v-if="afterSales!=null && afterSales.auditRemark">
+              <span>
+                {{ afterSales.auditRemark }}
+              </span>
+          </el-descriptions-item>
         </el-descriptions>
       </el-card>
     </div>
@@ -406,6 +416,7 @@
 <script>
 import {
   getLiveOrder,
+  getLiveOrderAll,
   syncExpress,
   updateLiveOrder,
   updateErp,
@@ -500,6 +511,7 @@ export default {
       orderBuyTypeOptions:[],
       scheduleOptions:[],
       item:null,
+      afterSales:null,
       tuiMoneyLogs:[],
       erpOrder:null,
       prod:null,
@@ -925,12 +937,13 @@ export default {
       this.storeName=storeName;
       this.item=null;
       this.tuiMoneyLogs=null;
-      getLiveOrder(orderId).then(response => {
-        this.item = response.data;
+      this.afterSales=null;
+      getLiveOrderAll(orderId).then(response => {
+        this.item = response.order;
         console.log(this.tuiMoneyLogs)
         this.tuiMoneyLogs = response.tuiMoneyLogs;
-        this.msgForm.userId=response.data.userId;
-        this.msgForm.followDoctorId=response.data.followDoctorId;
+        this.msgForm.userId=response.order.userId;
+        this.msgForm.followDoctorId=response.order.followDoctorId;
         this.getlistOrderitem(this.item.orderId);
         getOrderExpress(orderId).then(response => {
           this.deliverList = response.data;
@@ -948,6 +961,7 @@ export default {
         tuiMoneyLogs(this.item.orderId).then(response => {
           this.tuiMoneyLogs = response.data
         })
+        this.afterSales = response.afterSales;
       });
 
     },

+ 229 - 6
src/views/system/config/config.vue

@@ -31,6 +31,32 @@
               <el-input-number v-model="form40.numberCalls" :min="1" :step="1" :precision="0" placeholder="例如: 1"></el-input-number>
             </el-form-item>
           </template>
+           <el-form-item label="DeepSeekChat模型并发数" prop="DeepSeekChatConcurrency">
+            <el-input v-model="form40.concurrency" placeholder="请输入DeepSeekChat模型并发数"></el-input>
+          </el-form-item>
+          <el-form-item label="DeepSeekChat服务地址" prop="DeepSeekChatServerAddress">
+            <el-input v-model="form40.serverAddress" placeholder="请输入DeepSeekChat服务地址"></el-input>
+          </el-form-item>
+          <el-form-item label="DeepSeekChat_apiKey" prop="DeepSeekChatApiKey">
+           <el-input v-model="form40.apiKey" placeholder="请输入DeepSeekChat_apiKey"></el-input>
+          </el-form-item>
+          <el-form-item label="DeepSeekChat模型名称" prop="DeepSeekChatModelName">
+            <el-input v-model="form40.modelName" placeholder="请输入DeepSeekChat模型名称"></el-input>
+          </el-form-item>
+          <el-form-item label="是否限制外呼网关" prop="enableGateWayLimit">
+            <el-switch v-model="form40.enableGateWayLimit"></el-switch>
+          </el-form-item>
+          <el-form-item label="系统可见外呼网关" prop="showGatewayIds" v-if="!!form40.enableGateWayLimit">
+            <!-- <el-input v-model="" placeholder="请输入DeepSeekChat模型名称"></el-input> -->
+             <el-select v-model="form40.showGatewayIds" multiple filterable placeholder="请选择系统可见外呼网关">
+              <el-option
+                v-for="item in gatewayList"
+                :key="item.id"
+                :label="item.gwDesc"
+                :value="item.id">
+              </el-option>
+            </el-select>
+          </el-form-item>
 
           <div class="footer">
             <el-button type="primary" @click="submitForm40">提 交</el-button>
@@ -503,6 +529,9 @@
           <el-form-item label="SECRET_KEY" v-if="form7.isIdVerification == 1" prop="SECRET_KEY">
             <el-input   v-model="form7.SECRET_KEY"  label="请输入SECRET_KEY"></el-input>
           </el-form-item>
+          <el-form-item label="跳转商城小程序" prop="jumpStoreAppId">
+            <el-input   v-model="form7.jumpStoreAppId"  label="请输入跳转商城小程序"></el-input>
+          </el-form-item>
           <el-form-item label="检查库存" prop="checkStock">
             <el-radio-group v-model="form7.checkStock">
               <el-radio :label="true">开启</el-radio>
@@ -512,6 +541,19 @@
               <i class="el-icon-question" style="margin-left: 5px; color: #909399;"></i>
             </el-tooltip>
           </el-form-item>
+
+          <el-form-item  label="年支付金额限制" prop="yearPayPrice">
+            <el-input-number v-model="form7.yearPayPrice" :precision="2" :step="0.1"></el-input-number>
+          </el-form-item>
+          <el-form-item  label="达限后支付限制金额" prop="canPayPrice">
+            <el-input-number v-model="form7.canPayPrice" :precision="2" :step="0.1"></el-input-number>
+          </el-form-item>
+          <el-form-item  label="支付金额达限提示" prop="payPriceTip">
+            <el-tooltip class="item" effect="dark" content="支付金额达限提示" placement="top-end">
+              <el-input    v-model="form7.payPriceTip"   ></el-input>
+            </el-tooltip>
+          </el-form-item>
+
           <div class="footer">
             <el-button type="primary" @click="submitForm7">提 交</el-button>
           </div>
@@ -1277,6 +1319,14 @@
               <el-input-number v-model="form18.answerErrorCount" :min="1"></el-input-number>
             </el-tooltip>
           </el-form-item>
+          <el-form-item label="看课是否校验答案">
+            <el-tooltip class="item" effect="dark" content="默认为「是」:需校验答题正确性;选「否」则不做答案校验" placement="top-end">
+              <el-radio-group v-model="form18.validateAnswerWhenWatch">
+                <el-radio label="1">是</el-radio>
+                <el-radio label="0">否</el-radio>
+              </el-radio-group>
+            </el-tooltip>
+          </el-form-item>
           <el-form-item label="每十分钟获取积分">
             <el-tooltip class="item" effect="dark" content="每十分钟获取多少积分" placement="top-end">
               <el-input-number v-model="form18.videoIntegral" :min="1"></el-input-number>
@@ -1351,7 +1401,7 @@
             <el-radio-group v-model="form18.rewardType">
               <el-radio label="1">红包</el-radio>
               <el-radio label="2">积分</el-radio>
-              <el-radio label="3">红包+积分</el-radio>
+              <el-radio label="3" v-if="!isBeiJingZhuoMei">红包+积分</el-radio>
             </el-radio-group>
           </el-form-item>
 
@@ -1587,6 +1637,15 @@
               inactive-color="#ff4949">
             </el-switch>
           </el-form-item>
+          <el-form-item label="公告栏" prop="camelCase">
+            <el-input
+              type="textarea"
+              v-model="form18.camelCase"
+              :rows="2"
+              placeholder="请输入公告"
+              >
+            </el-input>
+          </el-form-item>
           <div class="line"></div>
           <div style="float:right;margin-right:20px">
             <el-button type="primary" @click="submitForm18">提交</el-button>
@@ -2689,6 +2748,9 @@
           <el-form-item label="直播流链接" prop="domain">
             <el-input v-model="form32.domain" label="请输入domain"></el-input>
           </el-form-item>
+          <el-form-item label="http" prop="http">
+            <el-input v-model="form32.http" label="请输入http"></el-input>
+          </el-form-item>
           <br>
           <div class="footer">
             <el-button type="primary" @click="submitForm32">提 交</el-button>
@@ -2751,6 +2813,74 @@
           <el-button type="primary" @click="submitForm35">提 交</el-button>
         </div>
       </el-tab-pane>
+      <el-tab-pane label="AI打标签行业配置" name="aiTagTradeType.config">
+        <el-form ref="form37" :model="form37" :rules="rules36" label-width="300px">
+          <el-form-item label="行业选择" prop="tradeType">
+            <el-select
+              v-model="form37.tradeType"
+              clearable
+              filterable
+              placeholder="请选择行业"
+              style="width: 400px"
+            >
+              <el-option
+                v-for="item in tradeTypeOptions"
+                :key="item.dictValue"
+                :label="item.dictLabel"
+                :value="item.dictValue"
+              />
+            </el-select>
+          </el-form-item>
+        </el-form>
+
+        <div class="footer">
+          <el-button type="primary" @click="submitForm37">提 交</el-button>
+        </div>
+      </el-tab-pane>
+
+      <el-tab-pane label="提现配置" name="his.AppRedPacket">
+        <!-- 添加 ref 和 rules -->
+        <el-form ref="form36" :model="form36" label-width="160px">
+
+          <el-form-item label="红包接口类型" prop="isNew">
+            <el-radio-group v-model="form36.isNew" >
+              <el-radio label="0">商家转账到零钱(旧)</el-radio>
+              <el-radio label="1">商家转账到零钱(新)</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="商户号" prop="mchId">
+            <el-input v-model="form36.mchId" placeholder="请输入商户号"></el-input>
+          </el-form-item>
+          <el-form-item label="商户密钥" prop="mchKey">
+            <el-input v-model="form36.mchKey" placeholder="请输入商户密钥" show-password></el-input>
+          </el-form-item>
+          <el-form-item label="p12证书路径" prop="keyPath">
+            <el-input v-model="form36.keyPath" placeholder="请输入p12证书路径"></el-input>
+          </el-form-item>
+          <el-form-item label="apiV3密钥" prop="apiV3Key">
+            <el-input v-model="form36.apiV3Key" placeholder="请输入apiV3密钥" show-password></el-input>
+          </el-form-item>
+          <el-form-item label="公钥ID" prop="publicKeyId">
+            <el-input v-model="form36.publicKeyId" placeholder="请输入公钥ID"></el-input>
+          </el-form-item>
+          <el-form-item label="公钥证书路径" prop="publicKeyPath">
+            <el-input v-model="form36.publicKeyPath" placeholder="请输入公钥证书路径"></el-input>
+          </el-form-item>
+          <el-form-item label="私钥路径" prop="privateKeyPath">
+            <el-input v-model="form36.privateKeyPath" placeholder="请输入私钥路径"></el-input>
+          </el-form-item>
+          <el-form-item label="证书路径" prop="privateCertPath">
+            <el-input v-model="form36.privateCertPath" placeholder="请输入证书路径"></el-input>
+          </el-form-item>
+          <el-form-item label="回调地址" prop="notifyUrl">
+            <el-input v-model="form36.notifyUrl" placeholder="请输入回调地址"></el-input>
+          </el-form-item>
+
+          <div class="footer">
+            <el-button type="primary" @click="submitForm36">提 交</el-button>
+          </div>
+        </el-form>
+      </el-tab-pane>
 
     </el-tabs>
 
@@ -2767,7 +2897,7 @@
 </template>
 
 <script>
-import { getConfigByKey, updateConfigByKey, clearCache, updateIsTownOn } from '@/api/system/config'
+import { getConfigByKey, updateConfigByKey, clearCache, updateIsTownOn,getGatewayList } from '@/api/system/config'
 import { listStore } from '@/api/his/storeProduct'
 import { js } from 'js-beautify'
 import Material from '@/components/Material'
@@ -2782,6 +2912,7 @@ import { getCitys } from '@/api/store/city'
 import { listCompany } from '@/api/company/company'
 import { getStoreProductColumns } from '@/api/hisStore/storeProduct'
 import { getStoreColumns } from '@/api/hisStore/store'
+import{ getSignProjectName } from '@/api/course/qw/courseWatchLog'
 
 export default {
   name: 'Config',
@@ -2792,6 +2923,8 @@ export default {
   },
   data() {
     return {
+      gatewayList:[],
+      isBeiJingZhuoMei:false,
       citys: [],
       images: [],
       appImages: [],
@@ -2845,6 +2978,9 @@ export default {
         roomLinkAllow:true,
         smsDomainName: '', // 初始化为空字符串或其他默认值
         completionCountdown: false,//是否开启点播完课倒计时
+        camelCase:'',
+        /** 看课是否校验答案:1-是(默认) 0-否,随 course.config JSON 保存 */
+        validateAnswerWhenWatch: '1',
       },
       form19: {},
       form20: {
@@ -2954,6 +3090,10 @@ export default {
       form33:{},
       form34:{},
       form35:{},
+      form36:{},
+      form37:{
+        tradeType: ''
+      },
       form40: {
         enablePhoneConfig: false,
         enablePhoneLimitConfig:false,
@@ -3035,10 +3175,19 @@ export default {
       rules33: {},
       rules34: {},
       rules35: {},
+      rules36: {
+        tradeType: [
+          { required: true, message: '请选择行业', trigger: 'change' }
+        ]
+      },
+      tradeTypeOptions: [],
     }
   },
   created() {
-
+    this.getDicts("trade_type").then((response) => {
+      this.tradeTypeOptions = response.data;
+    });
+    this.checkProjectName();
     this.getConfigByKey(this.activeName)
     listStore().then(response => {
       this.storeOPtions = response.rows
@@ -3309,7 +3458,8 @@ export default {
       this.form2.inquirySubType.splice(index, 1)
     },
     handleClick(tab, event) {
-      this.getConfigByKey(tab.name)
+    this.getConfigByKey(tab.name)
+
 
     },
     handleAddProduct() {
@@ -3365,6 +3515,8 @@ export default {
           this.configKey = key;
           if (key == 'cId.config') {
             this.form40 = { enablePhoneConfig: false, generateCount: 1 };
+          } else if (key == 'aiTagTradeType.config') {
+            this.form36 = { tradeType: '' };
           }
         }
         if (key == 'sys.oss.cloudStorage') {
@@ -3445,7 +3597,11 @@ export default {
           console.log(this.form16)
         }
         if (key == 'course.config') {
-          this.form18 = JSON.parse(response.data.configValue)
+          const parsed = JSON.parse(response.data.configValue)
+          if (parsed.validateAnswerWhenWatch == null || parsed.validateAnswerWhenWatch === '') {
+            parsed.validateAnswerWhenWatch = '1'
+          }
+          this.form18 = parsed
         }
         if (key == 'redPacket.config') {
           this.form19 = JSON.parse(response.data.configValue)
@@ -3526,6 +3682,45 @@ export default {
         if(key=="courseAppConfig.config"){
           this.form35 =JSON.parse(response.data.configValue);
         }
+        if (key == 'his.AppRedPacket') {
+          if (response.data && response.data.configValue) {
+            this.form36 = JSON.parse(response.data.configValue);
+          } else {
+            // 如果没有配置,使用默认值
+            this.form36 = {
+              isNew: '1',
+              mchId: '',
+              notifyUrl: '',
+              mchKey: '',
+              keyPath: '',
+              apiV3Key: '',
+              serialNo: '',
+              privateCertPath: '',
+              privateKeyPath: '',
+              publicKeyPath: ''
+            };
+          }
+          this.configId = response.data?.configId || null;
+          this.configKey = response.data?.configKey || 'his.AppRedPacket';
+        }
+        if(key=="aiTagTradeType.config"){
+          if (response.data && response.data.configValue) {
+            const parsed = JSON.parse(response.data.configValue);
+            let tradeType = '';
+            if (Array.isArray(parsed)) {
+              tradeType = parsed[0] != null ? String(parsed[0]) : '';
+            } else if (parsed && typeof parsed === 'object') {
+              if (parsed.tradeType != null && parsed.tradeType !== '') {
+                tradeType = String(parsed.tradeType);
+              } else if (Array.isArray(parsed.tradeTypes) && parsed.tradeTypes.length) {
+                tradeType = String(parsed.tradeTypes[0]);
+              }
+            }
+            this.form37 = { tradeType };
+          } else {
+            this.form37 = { tradeType: '' };
+          }
+        }
         if(key == 'vc.config'){
           if(!!response.data){
             this.configId = response.data.configId
@@ -3541,6 +3736,13 @@ export default {
             console.log(this.form32 );
           }
         }
+         if(key == "cId.config" && !!this.form40.enableGateWayLimit){
+        getGatewayList().then(res=>{
+          this.gatewayList = res.data;
+          console.log(this.gatewayList);
+        }).catch(res=>{
+        })
+      }
       })
     },
     /** 提交按钮 */
@@ -3858,6 +4060,22 @@ export default {
         }
       });
     },
+    submitForm36(){
+      var param={configId:this.configId,configName : "APP提现配置", configKey: this.configKey,configValue:JSON.stringify(this.form36)}
+      updateConfigByKey(param).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+        }
+      });
+    },
+    submitForm37(){
+      var param={configId:this.configId,configName : "AI打标签行业配置", configKey: this.configKey,configValue:JSON.stringify(this.form37)}
+      updateConfigByKey(param).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+        }
+      });
+    },
     submitForm32(){
       const param = { configId: this.configId, configName : "直播源配置", configKey: this.configKey, configValue: JSON.stringify(this.form32) }
       console.log(param)
@@ -4116,7 +4334,12 @@ export default {
       // 转换回数字并更新
       rule[field] = parseFloat(str) || 0;
     },
-  }
+     checkProjectName() {
+       getSignProjectName().then(r=>{
+         this.isBeiJingZhuoMei = r.signProjectName === '北京卓美';
+       });
+    },
+  },
 }
 </script>
 <style scoped>

+ 10 - 0
src/views/system/config/integralConfig.vue

@@ -130,6 +130,15 @@
           </el-form-item>
         </el-col>
       </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item  label="首次下载App获取积分" prop="downloadAppIntegral">
+            <el-tooltip class="item" effect="dark" content="首次下载App获取积分" placement="top-end">
+              <el-input-number  v-model="form11.downloadAppIntegral"   ></el-input-number>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+      </el-row>
       <el-row>
         <el-col :span="12">
           <el-form-item  label="单日可获总积分" prop="integralByOneDay">
@@ -183,6 +192,7 @@ export default {
         integralFirstOrderPoint: null,
         integralTypeByOneDay: null,
         integralSubscriptCourse: null,
+        downloadAppIntegral: null,
       },
       saveLoading: false,
     }