Commit 0102df95a17160596aec6f7c0d2a32f5ffdfae07

Authored by 李宇
1 parent f390c57f

feat(lqTkjlb): 优化门店排行榜表格展示与截图功能

- 移除统计概览模块,聚焦于门店排行榜展示
- 调整表格列宽和样式,新增总计行并优化样式显示
- 格式化完成率展示,保留一位小数
- 优化截图逻辑,针对门店排行榜生成独立截图容器
- 改进表格导出时的宽度计算与样式处理,确保内容完整显示
- 统一表格字体大小,增强可读性与视觉一致性
antis-ncc-admin/src/views/lqTkjlb/Report.vue
... ... @@ -295,62 +295,37 @@
295 295 暂无门店数据
296 296 </div>
297 297 <div v-else>
298   - <!-- 统计概览 -->
299   - <div class="store-ranking-summary">
300   - <div class="summary-item">
301   - <span class="summary-label">参与门店总数:</span>
302   - <span class="summary-value">{{ storeData.length }}家</span>
303   - </div>
304   - <div class="summary-item">
305   - <span class="summary-label">总目标数:</span>
306   - <span class="summary-value">{{ getTotalStoreTarget() }}</span>
307   - </div>
308   - <div class="summary-item">
309   - <span class="summary-label">总完成数:</span>
310   - <span class="summary-value">{{ getTotalStoreCompleted() }}</span>
311   - </div>
312   - <div class="summary-item">
313   - <span class="summary-label">总完成率:</span>
314   - <span class="summary-value" :class="getTotalCompletionRateClass()">{{ getTotalCompletionRate() }}%</span>
315   - </div>
316   - </div>
317   -
318 298 <!-- 门店排行榜表格 -->
319 299 <div class="store-ranking-table">
320   - <el-table :data="sortedStoreData" stripe style="width: 100%">
321   - <el-table-column type="index" label="序号" width="80" align="center">
  300 + <el-table :data="sortedStoreData" :show-header="true" style="width: auto">
  301 + <el-table-column prop="StoreName" label="门店" width="110" align="center">
322 302 <template slot-scope="scope">
323   - <span class="ranking-number" :class="getRankingClass(scope.$index + 1)">
324   - {{ scope.$index + 1 }}
325   - </span>
  303 + <span :class="{ 'total-label': scope.row.isTotal }">{{ scope.row.StoreName }}</span>
326 304 </template>
327 305 </el-table-column>
328   - <el-table-column prop="StoreName" label="门店" min-width="120" align="center"/>
329   - <el-table-column prop="TotalTarget" label="目标张数" width="100" align="center" />
330   - <el-table-column prop="CompletedTarget" label="总张数" width="100" align="center" />
331   - <el-table-column label="完成率" min-width="180" align="center">
  306 + <el-table-column prop="TotalTarget" label="目标张数" width="85" align="center">
332 307 <template slot-scope="scope">
333   - <div class="progress-container">
334   - <div class="progress-bar">
335   - <div
336   - class="progress-fill"
337   - :style="{ width: Math.min(scope.row.CompletionRate, 100) + '%' }"
338   - ></div>
339   - </div>
340   - <span class="progress-text">
341   - {{ scope.row.CompletionRate }}%
342   - </span>
343   - </div>
  308 + <span :class="{ 'total-value': scope.row.isTotal }">{{ scope.row.TotalTarget }}</span>
  309 + </template>
  310 + </el-table-column>
  311 + <el-table-column prop="CompletedTarget" label="总张数" width="85" align="center">
  312 + <template slot-scope="scope">
  313 + <span :class="{ 'total-value': scope.row.isTotal }">{{ scope.row.CompletedTarget }}</span>
344 314 </template>
345 315 </el-table-column>
346   - <el-table-column label="排名" width="100" align="center">
  316 + <el-table-column label="完成率" width="85" align="center">
347 317 <template slot-scope="scope">
348   - <span class="ranking" :class="getRankingClass(scope.$index + 1)">
  318 + <span :class="{ 'total-value': scope.row.isTotal }">{{ formatCompletionRate(scope.row.CompletionRate) }}%</span>
  319 + </template>
  320 + </el-table-column>
  321 + <el-table-column label="排名" width="70" align="center">
  322 + <template slot-scope="scope">
  323 + <span v-if="!scope.row.isTotal" class="ranking" :class="getRankingClass(scope.$index + 1)">
349 324 {{ scope.$index + 1 }}
350 325 </span>
  326 + <span v-else></span>
351 327 </template>
352 328 </el-table-column>
353   - <el-table-column width="30"></el-table-column>
354 329 </el-table>
355 330 </div>
356 331 </div>
... ... @@ -776,15 +751,24 @@ export default {
776 751 return rateB - rateA // 降序排列
777 752 })
778 753 },
779   - // 门店前三名
  754 + // 门店前三名(按完成率排序)
780 755 topStoreData() {
781 756 return this.storeData.slice(0, 3)
782 757 },
783 758 // 按完成率排序的门店数据(用于总门店排行榜)
784 759 sortedStoreData() {
785   - return [...this.storeData].sort((a, b) => {
  760 + const sorted = [...this.storeData].sort((a, b) => {
786 761 return b.CompletionRate - a.CompletionRate // 按完成率降序排列
787 762 })
  763 + // 添加总计行
  764 + sorted.push({
  765 + StoreName: '总计',
  766 + TotalTarget: this.getTotalStoreTarget(),
  767 + CompletedTarget: this.getTotalStoreCompleted(),
  768 + CompletionRate: this.getTotalCompletionRate(),
  769 + isTotal: true
  770 + })
  771 + return sorted
788 772 },
789 773 // 个人前三名
790 774 topPersonData() {
... ... @@ -1151,6 +1135,12 @@ export default {
1151 1135 return date.toLocaleString('zh-CN')
1152 1136 },
1153 1137  
  1138 + // 格式化完成率,保留1位小数
  1139 + formatCompletionRate(rate) {
  1140 + if (rate === null || rate === undefined) return '0.0'
  1141 + return parseFloat(rate).toFixed(1)
  1142 + },
  1143 +
1154 1144 // 计算门店总目标
1155 1145 getStoreTotalTarget(store) {
1156 1146 if (!store.TeamList) return 0
... ... @@ -1259,7 +1249,7 @@ export default {
1259 1249 const totalTarget = this.getTotalStoreTarget()
1260 1250 const totalCompleted = this.getTotalStoreCompleted()
1261 1251 if (totalTarget === 0) return 0
1262   - return Math.round((totalCompleted / totalTarget) * 100)
  1252 + return (totalCompleted / totalTarget) * 100
1263 1253 },
1264 1254  
1265 1255 // 获取总完成率样式类
... ... @@ -1512,28 +1502,40 @@ export default {
1512 1502 header.style.backgroundColor = '#f8f9fa'
1513 1503 })
1514 1504  
1515   - // 确保表格容器宽度足够
  1505 + // 确保表格容器和表格保持实际渲染宽度,避免被拉伸
1516 1506 const tableContainers = element.querySelectorAll('.store-ranking-table, .no-expansion-table, .store-table, .person-table')
1517 1507 tableContainers.forEach(container => {
1518   - container.style.width = '100%'
1519   - container.style.minWidth = '100%'
1520   - container.style.maxWidth = 'none'
  1508 + // 保持表格容器的原始宽度,避免被拉伸
  1509 + const originalWidth = container.offsetWidth || container.clientWidth
  1510 + if (originalWidth) {
  1511 + container.style.width = originalWidth + 'px'
  1512 + container.style.minWidth = originalWidth + 'px'
  1513 + container.style.maxWidth = originalWidth + 'px'
  1514 + }
1521 1515 container.style.overflow = 'visible'
1522 1516 })
1523 1517  
1524 1518 // 等待样式应用
1525 1519 await new Promise(resolve => setTimeout(resolve, 500))
1526 1520  
1527   - // 强制重新计算表格布局
  1521 + // 对于门店排行榜表格,保持auto宽度,不要拉伸
1528 1522 elementTables.forEach(table => {
1529   - // 强制重新计算表格宽度
1530   - table.style.width = 'auto'
1531   - table.style.minWidth = 'auto'
1532   - // 触发重新布局
1533   - table.offsetHeight
1534   - // 重新设置宽度
1535   - table.style.width = '100%'
1536   - table.style.minWidth = '100%'
  1523 + const tableContainer = table.closest('.store-ranking-table')
  1524 + if (tableContainer) {
  1525 + // 门店排行榜表格保持auto宽度
  1526 + table.style.width = 'auto'
  1527 + table.style.minWidth = 'auto'
  1528 + table.style.maxWidth = 'none'
  1529 + } else {
  1530 + // 其他表格保持原有逻辑
  1531 + table.style.width = 'auto'
  1532 + table.style.minWidth = 'auto'
  1533 + // 触发重新布局
  1534 + table.offsetHeight
  1535 + // 重新设置宽度
  1536 + table.style.width = '100%'
  1537 + table.style.minWidth = '100%'
  1538 + }
1537 1539 })
1538 1540  
1539 1541 // 计算实际内容高度和宽度
... ... @@ -1543,25 +1545,39 @@ export default {
1543 1545 element.clientHeight
1544 1546 )
1545 1547  
1546   - // 计算实际内容宽度,特别处理表格
1547   - let actualWidth = Math.max(
1548   - element.scrollWidth,
1549   - element.offsetWidth,
1550   - element.clientWidth
1551   - )
1552   -
1553   - // 检查表格是否需要更宽的宽度
1554   - const widthCheckTables = element.querySelectorAll('.el-table')
1555   - widthCheckTables.forEach(table => {
1556   - const tableWidth = Math.max(
1557   - table.scrollWidth,
1558   - table.offsetWidth,
1559   - table.clientWidth
1560   - )
1561   - if (tableWidth > actualWidth) {
1562   - actualWidth = tableWidth
  1548 + // 对于门店排行榜,使用表格的实际宽度,而不是整个容器宽度
  1549 + const storeRankingTable = element.querySelector('.store-ranking-table .el-table')
  1550 + let actualWidth
  1551 + let isStoreRanking = false
  1552 +
  1553 + if (storeRankingTable) {
  1554 + // 门店排行榜:使用表格的实际宽度,不添加额外边距
  1555 + actualWidth = storeRankingTable.offsetWidth || storeRankingTable.clientWidth || storeRankingTable.scrollWidth
  1556 + isStoreRanking = true
  1557 + } else {
  1558 + // 其他情况:使用元素的实际渲染宽度
  1559 + actualWidth = element.offsetWidth || element.clientWidth
  1560 +
  1561 + // 对于其他表格,使用表格的实际宽度
  1562 + const widthCheckTables = element.querySelectorAll('.el-table')
  1563 + widthCheckTables.forEach(table => {
  1564 + const tableWidth = table.offsetWidth || table.clientWidth
  1565 + if (tableWidth > actualWidth) {
  1566 + actualWidth = tableWidth
  1567 + }
  1568 + })
  1569 +
  1570 + // 确保宽度不超过容器的实际宽度
  1571 + const containerWidth = (element.parentElement && element.parentElement.offsetWidth) || element.offsetWidth
  1572 + if (actualWidth > containerWidth) {
  1573 + actualWidth = containerWidth
1563 1574 }
1564   - })
  1575 + }
  1576 +
  1577 + // 进一步限制宽度,避免截图过大
  1578 + const maxWidth = Math.min(actualWidth, window.innerWidth || 1200)
  1579 + // 只有门店排行榜除以2
  1580 + actualWidth = isStoreRanking ? maxWidth / 2 : maxWidth
1565 1581  
1566 1582 console.log('容器尺寸信息:', {
1567 1583 scrollWidth: element.scrollWidth,
... ... @@ -1574,22 +1590,49 @@ export default {
1574 1590 actualWidth: actualWidth
1575 1591 })
1576 1592  
  1593 + // 对于门店排行榜,创建一个只包含表格的临时容器用于截图
  1594 + let screenshotElement = element
  1595 + let shouldRemoveTempContainer = false
  1596 +
  1597 + if (storeRankingTable) {
  1598 + // 创建临时容器,只包含表格
  1599 + const tempContainer = document.createElement('div')
  1600 + tempContainer.style.position = 'absolute'
  1601 + tempContainer.style.left = '-9999px'
  1602 + tempContainer.style.width = actualWidth + 'px'
  1603 + tempContainer.style.padding = '0'
  1604 + tempContainer.style.margin = '0'
  1605 + tempContainer.style.backgroundColor = '#ffffff'
  1606 +
  1607 + // 克隆表格容器
  1608 + const tableContainer = storeRankingTable.closest('.store-ranking-table')
  1609 + if (tableContainer) {
  1610 + const clonedContainer = tableContainer.cloneNode(true)
  1611 + // 确保克隆的容器宽度也是auto
  1612 + clonedContainer.style.width = 'auto'
  1613 + clonedContainer.style.minWidth = 'auto'
  1614 + clonedContainer.style.maxWidth = 'none'
  1615 + tempContainer.appendChild(clonedContainer)
  1616 + document.body.appendChild(tempContainer)
  1617 + screenshotElement = tempContainer
  1618 + shouldRemoveTempContainer = true
  1619 + }
  1620 + }
  1621 +
1577 1622 // 配置截图选项
1578 1623 const options = {
1579 1624 allowTaint: true,
1580 1625 useCORS: true,
1581   - scale: 1.0, // 降低缩放比例提高兼容性
  1626 + scale: 2,
1582 1627 backgroundColor: '#ffffff',
1583   - logging: true, // 开启日志便于调试
  1628 + logging: true,
1584 1629 imageTimeout: 30000,
1585 1630 removeContainer: true,
1586   - foreignObjectRendering: false, // 关闭foreignObject渲染提高兼容性
  1631 + foreignObjectRendering: false,
1587 1632 scrollX: 0,
1588 1633 scrollY: 0,
1589 1634 width: actualWidth,
1590   - height: actualHeight,
1591   - windowWidth: actualWidth,
1592   - windowHeight: actualHeight,
  1635 + height: shouldRemoveTempContainer ? screenshotElement.scrollHeight : actualHeight,
1593 1636 ignoreElements: (element) => {
1594 1637 // 忽略可能影响截图的元素
1595 1638 return element.classList.contains('el-loading-mask') ||
... ... @@ -1644,12 +1687,28 @@ export default {
1644 1687 // 确保表格容器宽度足够
1645 1688 const clonedTableContainers = clonedDoc.querySelectorAll('.store-ranking-table, .no-expansion-table, .store-table, .person-table')
1646 1689 clonedTableContainers.forEach(container => {
1647   - container.style.width = '100%'
1648   - container.style.minWidth = '100%'
1649   - container.style.maxWidth = 'none'
  1690 + // 保持表格容器的原始宽度,避免被拉伸
  1691 + const originalWidth = container.offsetWidth || container.clientWidth || container.scrollWidth
  1692 + if (originalWidth) {
  1693 + container.style.width = originalWidth + 'px'
  1694 + container.style.minWidth = originalWidth + 'px'
  1695 + container.style.maxWidth = originalWidth + 'px'
  1696 + } else {
  1697 + container.style.width = '100%'
  1698 + container.style.minWidth = '100%'
  1699 + container.style.maxWidth = 'none'
  1700 + }
1650 1701 container.style.overflow = 'visible'
1651 1702 })
1652 1703  
  1704 + // 对于门店排行榜表格,确保表格本身保持auto宽度
  1705 + const clonedStoreRankingTables = clonedDoc.querySelectorAll('.store-ranking-table .el-table')
  1706 + clonedStoreRankingTables.forEach(table => {
  1707 + table.style.width = 'auto'
  1708 + table.style.minWidth = 'auto'
  1709 + table.style.maxWidth = 'none'
  1710 + })
  1711 +
1653 1712 // 确保所有报表区域完整显示
1654 1713 const reportSections = clonedDoc.querySelectorAll('.report-section')
1655 1714 reportSections.forEach(section => {
... ... @@ -1700,6 +1759,37 @@ export default {
1700 1759 col.style.maxHeight = 'none'
1701 1760 })
1702 1761  
  1762 + // 确保排行榜卡片完整显示
  1763 + const rankingSections = clonedDoc.querySelectorAll('.ranking-section')
  1764 + rankingSections.forEach(section => {
  1765 + section.style.height = 'auto'
  1766 + section.style.overflow = 'visible'
  1767 + section.style.maxHeight = 'none'
  1768 + section.style.display = 'block'
  1769 + section.style.visibility = 'visible'
  1770 + section.style.opacity = '1'
  1771 + })
  1772 +
  1773 + const rankingCards = clonedDoc.querySelectorAll('.ranking-card')
  1774 + rankingCards.forEach(card => {
  1775 + card.style.height = 'auto'
  1776 + card.style.overflow = 'visible'
  1777 + card.style.maxHeight = 'none'
  1778 + card.style.display = 'flex'
  1779 + card.style.visibility = 'visible'
  1780 + card.style.opacity = '1'
  1781 + card.style.position = 'static'
  1782 + })
  1783 +
  1784 + const rankingCardsContainer = clonedDoc.querySelectorAll('.ranking-cards')
  1785 + rankingCardsContainer.forEach(container => {
  1786 + container.style.display = 'flex'
  1787 + container.style.visibility = 'visible'
  1788 + container.style.opacity = '1'
  1789 + container.style.height = 'auto'
  1790 + container.style.overflow = 'visible'
  1791 + })
  1792 +
1703 1793 // 确保瀑布流容器完整显示
1704 1794 const waterfallContainer = clonedDoc.querySelector('.waterfall-container')
1705 1795 if (waterfallContainer) {
... ... @@ -1786,13 +1876,18 @@ export default {
1786 1876 console.log('开始生成截图,配置选项:', options)
1787 1877  
1788 1878 // 生成截图
1789   - const canvas = await html2canvas.default(element, options)
  1879 + const canvas = await html2canvas.default(screenshotElement, options)
1790 1880  
1791 1881 console.log('截图生成完成,画布尺寸:', {
1792 1882 width: canvas.width,
1793 1883 height: canvas.height
1794 1884 })
1795 1885  
  1886 + // 清理临时容器
  1887 + if (shouldRemoveTempContainer && screenshotElement.parentElement) {
  1888 + screenshotElement.parentElement.removeChild(screenshotElement)
  1889 + }
  1890 +
1796 1891 // 恢复原始样式
1797 1892 element.style.height = originalStyles.height
1798 1893 element.style.overflow = originalStyles.overflow
... ... @@ -1815,6 +1910,10 @@ export default {
1815 1910 this.$message.success('截图生成成功')
1816 1911 } catch (error) {
1817 1912 console.error('截图生成失败:', error)
  1913 + // 清理临时容器(如果存在)
  1914 + if (shouldRemoveTempContainer && screenshotElement && screenshotElement.parentElement) {
  1915 + screenshotElement.parentElement.removeChild(screenshotElement)
  1916 + }
1818 1917 this.$message.error('截图生成失败: ' + error.message)
1819 1918 } finally {
1820 1919 this.screenshotLoading = false
... ... @@ -1859,9 +1958,9 @@ export default {
1859 1958 .app-container {
1860 1959 padding: 12px;
1861 1960 // background-color: #f5f5f5;
1862   - min-height: 100vh;
  1961 + // height: 100vh;
1863 1962 overflow-y: scroll;
1864   - box-sizing: border-box;
  1963 + // box-sizing: border-box;
1865 1964 }
1866 1965  
1867 1966 .page-header {
... ... @@ -2833,35 +2932,66 @@ export default {
2833 2932  
2834 2933 .store-ranking-table {
2835 2934 .el-table {
2836   - border-radius: 8px;
2837   - overflow: hidden;
2838   - font-size: 14px;
2839   - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2840   - background: #ffffff; // 确保表格背景为纯白色
  2935 + border: 1px solid #e4e7ed;
  2936 + font-size: 12px;
  2937 + background: #ffffff;
  2938 + border-collapse: separate;
  2939 + border-spacing: 0;
2841 2940 }
2842 2941  
2843 2942 .el-table th {
2844   - background: #f8f9fa; // 表头使用浅灰色背景
2845   - color: #606266;
2846   - font-weight: 600;
2847   - padding: 12px 0;
2848   - font-size: 14px;
  2943 + background: #92d04f !important;
  2944 + color: #000 !important;
  2945 + font-weight: 500;
  2946 + padding: 6px 4px;
  2947 + font-size: 12px;
  2948 + border-bottom: 1px solid #7ab83a;
  2949 + border-right: 1px solid #7ab83a;
  2950 + line-height: 1.4;
  2951 +
  2952 + &:last-child {
  2953 + border-right: none;
  2954 + }
  2955 + }
  2956 +
  2957 + // 确保表头背景色正确应用
  2958 + ::v-deep .el-table__header-wrapper {
  2959 + .el-table__header {
  2960 + th {
  2961 + background-color: #92d04f !important;
  2962 + color: #000 !important;
  2963 + padding: 6px 4px;
  2964 + font-size: 12px;
  2965 + line-height: 1.4;
  2966 + }
  2967 + }
2849 2968 }
2850 2969  
2851 2970 .el-table td {
2852   - padding: 10px 0;
2853   - font-size: 14px;
2854   - background: #ffffff; // 确保表格单元格背景为纯白色
  2971 + padding: 6px 4px;
  2972 + font-size: 12px;
  2973 + background: #ffffff;
  2974 + border-bottom: 1px solid #f0f0f0;
  2975 + border-right: 1px solid #f0f0f0;
  2976 + color: #303133;
  2977 + line-height: 1.4;
  2978 +
  2979 + &:last-child {
  2980 + border-right: none;
  2981 + }
2855 2982 }
2856 2983  
2857   - // 确保斑马纹效果正常显示
2858   - .el-table--striped .el-table__body tr.el-table__row--striped td {
2859   - background: #fafafa; // 斑马纹使用更浅的灰色
  2984 + .el-table__body tr:hover > td {
  2985 + background-color: #ffffff !important;
  2986 + }
  2987 +
  2988 + .el-table__body tr:last-child td {
  2989 + border-bottom: none;
2860 2990 }
2861 2991  
2862 2992 .ranking-number {
2863 2993 font-weight: 600;
2864   - font-size: 14px;
  2994 + font-size: 12px;
2865 2995  
2866 2996 &.ranking-top {
2867 2997 color: #F56C6C;
... ... @@ -2907,7 +3037,7 @@ export default {
2907 3037  
2908 3038 .ranking {
2909 3039 font-weight: 600;
2910   - font-size: 14px;
  3040 + font-size: 12px;
2911 3041  
2912 3042 &.ranking-top {
2913 3043 color: #F56C6C;
... ... @@ -2921,6 +3051,35 @@ export default {
2921 3051 color: #606266;
2922 3052 }
2923 3053 }
  3054 +
  3055 + // 总计行样式
  3056 + ::v-deep .el-table__body-wrapper {
  3057 + .el-table__body {
  3058 + tbody tr:last-child {
  3059 + background-color: #92d04f !important;
  3060 +
  3061 + td {
  3062 + background-color: #92d04f !important;
  3063 + border-bottom: none !important;
  3064 + padding: 6px 4px;
  3065 + font-size: 12px;
  3066 + line-height: 1.4;
  3067 +
  3068 + .total-label {
  3069 + font-weight: 600;
  3070 + color: #000;
  3071 + font-size: 12px;
  3072 + }
  3073 +
  3074 + .total-value {
  3075 + font-weight: 600;
  3076 + color: #000;
  3077 + font-size: 12px;
  3078 + }
  3079 + }
  3080 + }
  3081 + }
  3082 + }
2924 3083 }
2925 3084  
2926 3085 // 未拓客人员表格样式
... ...
antis-ncc-admin/src/views/statisticsList/form10.vue
... ... @@ -236,7 +236,7 @@
236 236 width="120">
237 237 <template slot-scope="scope">
238 238 <span v-if="!scope.row.isStore">{{ scope.row.SalesQuantity || 0 }}</span>
239   - <span v-else class="summary-text">-</span>
  239 + <span v-else class="summary-text">{{ scope.row.SalesQuantity || 0 }}</span>
240 240 </template>
241 241 </el-table-column>
242 242 <el-table-column
... ... @@ -245,7 +245,7 @@
245 245 width="120">
246 246 <template slot-scope="scope">
247 247 <span v-if="!scope.row.isStore" class="amount-value">¥{{ formatMoney(scope.row.SalesAmount) }}</span>
248   - <span v-else class="summary-text">-</span>
  248 + <span v-else class="summary-text amount-value">¥{{ formatMoney(scope.row.SalesAmount) }}</span>
249 249 </template>
250 250 </el-table-column>
251 251 <el-table-column
... ... @@ -254,7 +254,7 @@
254 254 width="120">
255 255 <template slot-scope="scope">
256 256 <span v-if="!scope.row.isStore">{{ scope.row.BillingCount || 0 }}</span>
257   - <span v-else class="summary-text">-</span>
  257 + <span v-else class="summary-text">{{ scope.row.BillingCount || 0 }}</span>
258 258 </template>
259 259 </el-table-column>
260 260 <el-table-column
... ... @@ -263,7 +263,7 @@
263 263 width="120">
264 264 <template slot-scope="scope">
265 265 <span v-if="!scope.row.isStore">{{ scope.row.SalesCount || 0 }}</span>
266   - <span v-else class="summary-text">-</span>
  266 + <span v-else class="summary-text">{{ scope.row.SalesCount || 0 }}</span>
267 267 </template>
268 268 </el-table-column>
269 269 </el-table>
... ... @@ -492,22 +492,34 @@ export default {
492 492 isStore: true,
493 493 StoreId: store.StoreId,
494 494 StoreName: store.StoreName,
495   - children: []
  495 + children: [],
  496 + // 门店合计字段(初始化为0)
  497 + SalesQuantity: 0,
  498 + SalesAmount: 0,
  499 + BillingCount: 0,
  500 + SalesCount: 0
496 501 }
497 502  
498   - // 如果有品项列表,创建品项子节点
  503 + // 如果有品项列表,创建品项子节点并计算合计
499 504 if (store.ItemList && store.ItemList.length > 0) {
500 505 store.ItemList.forEach(item => {
501   - storeNode.children.push({
  506 + const itemNode = {
502 507 id: `item_${idCounter++}`,
503 508 isStore: false,
504 509 ItemId: item.ItemId,
505 510 ItemName: item.ItemName,
506   - SalesQuantity: item.SalesQuantity,
507   - SalesAmount: item.SalesAmount,
508   - BillingCount: item.BillingCount,
509   - SalesCount: item.SalesCount
510   - })
  511 + SalesQuantity: item.SalesQuantity || 0,
  512 + SalesAmount: item.SalesAmount || 0,
  513 + BillingCount: item.BillingCount || 0,
  514 + SalesCount: item.SalesCount || 0
  515 + }
  516 + storeNode.children.push(itemNode)
  517 +
  518 + // 累加门店合计值
  519 + storeNode.SalesQuantity += Number(itemNode.SalesQuantity) || 0
  520 + storeNode.SalesAmount += Number(itemNode.SalesAmount) || 0
  521 + storeNode.BillingCount += Number(itemNode.BillingCount) || 0
  522 + storeNode.SalesCount += Number(itemNode.SalesCount) || 0
511 523 })
512 524 }
513 525  
... ...
绿纤uni-app/pages/index/index.vue
... ... @@ -7,32 +7,32 @@
7 7 <view class="loading-text">{{ loadingText }}</view>
8 8 </view>
9 9 </view>
10   -
  10 +
11 11 <!-- 状态栏占位 -->
12 12 <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
13   -
  13 +
14 14 <!-- 头部区域 -->
15 15 <view class="header">
16   - <view class="header-content">
17   - <view style="height: 100rpx;"></view>
18   - <view class="header-title">绿纤协同办公平台</view>
19   - <view class="header-subtitle">高效协同,移动办公</view>
20   - <view class="info-section">
21   - <view class="info-item">
22   - <text class="info-label">门店:</text>
23   - <text class="info-value">{{ jsjinfo && jsjinfo.storeName?jsjinfo.storeName:'暂无'}}</text>
24   - </view>
25   - <view class="info-item">
26   - <text class="info-label">本月金三角:</text>
27   - <text class="info-value">{{ jsjinfo && jsjinfo.jsjName?jsjinfo.jsjName:'暂无'}}</text>
  16 + <view class="header-content">
  17 + <view style="height: 100rpx;"></view>
  18 + <view class="header-title">绿纤协同办公平台</view>
  19 + <view class="header-subtitle">高效协同,移动办公</view>
  20 + <view class="info-section">
  21 + <view class="info-item">
  22 + <text class="info-label">门店:</text>
  23 + <text class="info-value">{{ jsjinfo && jsjinfo.storeName?jsjinfo.storeName:'暂无'}}</text>
  24 + </view>
  25 + <view class="info-item">
  26 + <text class="info-label">本月金三角:</text>
  27 + <text class="info-value">{{ jsjinfo && jsjinfo.jsjName?jsjinfo.jsjName:'暂无'}}</text>
  28 + </view>
28 29 </view>
29   - </view>
30   - <!-- <view class="header-illustration">
  30 + <!-- <view class="header-illustration">
31 31 <u-icon name="clock" size="60" color="#fff" opacity="0.3"></u-icon>
32 32 </view> -->
  33 + </view>
33 34 </view>
34   - </view>
35   -
  35 +
36 36 <!-- 统计卡片 -->
37 37 <view class="summary-card">
38 38 <view v-if="iskjb" class="summary-item" @click="goToPage('/pages/expansion-list/expansion-list')">
... ... @@ -43,7 +43,8 @@
43 43 <view class="num">{{ summaryData.inviteCount || 0 }}</view>
44 44 <view class="label">邀约数</view>
45 45 </view>
46   - <view v-if="iskjb" class="summary-item" @click="goToPage('/pages/user-appointment-list/user-appointment-list')">
  46 + <view v-if="iskjb" class="summary-item"
  47 + @click="goToPage('/pages/user-appointment-list/user-appointment-list')">
47 48 <view class="num">{{ summaryData.appointmentCount || 0 }}</view>
48 49 <view class="label">预约数</view>
49 50 </view>
... ... @@ -82,7 +83,7 @@
82 83 </view>
83 84 <view class="icon-label">耗卡</view>
84 85 </view>
85   -
  86 +
86 87 <view v-if="iskjb" class="icon-btn" @click="goToPage('/pages/refund/refund')">
87 88 <view class="icon">
88 89 <u-icon name="minus-circle" size="32" color="#43a047"></u-icon>
... ... @@ -101,14 +102,14 @@
101 102 </view>
102 103 <view class="icon-label">建档</view>
103 104 </view>
104   -
  105 +
105 106 <view class="icon-btn" @click="goToPage('/pages/clue-list/clue-list')">
106 107 <view class="icon">
107 108 <u-icon name="account" size="32" color="#43a047"></u-icon>
108 109 </view>
109 110 <view class="icon-label">会员</view>
110 111 </view>
111   -
  112 +
112 113 <view v-if="iskjb" class="icon-btn" @click="goToPage('/pages/expansion/expansion')">
113 114 <view class="icon">
114 115 <u-icon name="man-add" size="32" color="#43a047"></u-icon>
... ... @@ -127,7 +128,7 @@
127 128 </view>
128 129 <view class="icon-label">报表</view>
129 130 </view>
130   -
  131 +
131 132 <view class="icon-btn" @click="handleLogout">
132 133 <view class="icon">
133 134 <u-icon name="arrow-rightward" size="32" color="#43a047"></u-icon>
... ... @@ -137,34 +138,29 @@
137 138 </view>
138 139 </view>
139 140  
140   - <!-- 业绩数据板块 -->
141   - <view class="section-title">业绩数据</view>
142   - <view class="performance-card">
143   - <!-- <view class="performance-row">
144   - <view class="performance-item">
145   - <view class="performance-value">{{ performanceData.InvitationCount || 0 }}</view>
146   - <view class="performance-label">拓客人数</view>
147   - </view>
148   - <view class="performance-item">
149   - <view class="performance-value">{{ performanceData.InviteCount || 0 }}</view>
150   - <view class="performance-label">邀约人数</view>
  141 + <!-- 业绩数据板块 -->
  142 + <view class="section-title">业绩数据</view>
  143 + <view class="performance-card" v-if="newuserInfo.gw == '科技老师'">
  144 + <view class="performance-row">
  145 + <view class="performance-item highlight">
  146 + <view class="performance-value">¥{{ (performanceData.OrderAchievement || 0).toFixed(2) }}</view>
  147 + <view class="performance-label">开卡业绩</view>
151 148 </view>
152   - <view class="performance-item">
153   - <view class="performance-value">{{ performanceData.AppointmentCount || 0 }}</view>
154   - <view class="performance-label">预约人数</view>
  149 + <view class="performance-item highlight">
  150 + <view class="performance-value">¥{{ (performanceData.ConsumeAchievement || 0).toFixed(2) }}</view>
  151 + <view class="performance-label">消耗业绩</view>
155 152 </view>
156   - </view> -->
157   - <!-- <view class="performance-row">
158   - <view class="performance-item">
159   - <view class="performance-value">{{ performanceData.BillingCount || 0 }}</view>
160   - <view class="performance-label">开单数量</view>
  153 + <view class="performance-item highlight">
  154 + <view class="performance-value">¥{{ (performanceData.ConsumeProjectCount || 0).toFixed(2) }}</view>
  155 + <view class="performance-label">项目数</view>
161 156 </view>
162   -
163   - <view class="performance-item">
164   - <view class="performance-value">{{ performanceData.ConsumeCount || 0 }}</view>
165   - <view class="performance-label">消耗数量</view>
  157 + <view class="performance-item highlight">
  158 + <view class="performance-value">{{ performanceData.ConsumeLaborCost || 0 }}</view>
  159 + <view class="performance-label">手工</view>
166 160 </view>
167   - </view> -->
  161 + </view>
  162 + </view>
  163 + <view class="performance-card" v-else>
168 164 <view class="performance-row">
169 165 <view class="performance-item highlight">
170 166 <view class="performance-value">¥{{ (performanceData.BillingAmount || 0).toFixed(2) }}</view>
... ... @@ -174,21 +170,16 @@
174 170 <view class="performance-value">¥{{ (performanceData.ConsumeAmount || 0).toFixed(2) }}</view>
175 171 <view class="performance-label">消耗金额</view>
176 172 </view>
177   -
178   - <!-- <view class="performance-item">
179   - <view class="performance-value">{{ performanceData.RefundCount || 0 }}</view>
180   - <view class="performance-label">退卡数量</view>
181   - </view> -->
182 173 <view class="performance-item highlight">
183 174 <view class="performance-value">¥{{ (performanceData.RefundAmount || 0).toFixed(2) }}</view>
184 175 <view class="performance-label">退卡金额</view>
185 176 </view>
186   - </view>
187   - <view class="performance-row">
188   - <view class="performance-item">
189   - <view class="performance-value">{{ performanceData.BillingCount || 0 }}</view>
  177 + <view class="performance-item highlight">
  178 + <view class="performance-value">{{ performanceData.ConsumeProjectCount || 0 }}</view>
190 179 <view class="performance-label">项目数</view>
191 180 </view>
  181 + </view>
  182 + <view class="performance-row">
192 183 <view class="performance-item">
193 184 <view class="performance-value">{{ performanceData.HeadCount || 0 }}</view>
194 185 <view class="performance-label">人头</view>
... ... @@ -197,596 +188,610 @@
197 188 <view class="performance-value">{{ performanceData.PersonCount || 0 }}</view>
198 189 <view class="performance-label">人次</view>
199 190 </view>
  191 + <view class="performance-item">
  192 + <view class="performance-value">{{ performanceData.LaborCost || 0 }}</view>
  193 + <view class="performance-label">手工费</view>
  194 + </view>
200 195 </view>
201 196 </view>
202   -
203 197 </view>
204 198 </template>
205 199  
206 200 <script>
207   -import { mapState, mapMutations } from 'vuex'
208   -import memberApi from '@/apis/modules/member.js'
209   -import performanceApi from '@/apis/modules/performance.js'
210   -export default {
  201 + import {
  202 + mapState,
  203 + mapMutations
  204 + } from 'vuex'
  205 + import memberApi from '@/apis/modules/member.js'
  206 + import performanceApi from '@/apis/modules/performance.js'
  207 + export default {
211 208 data() {
212   - return {
213   - newuserInfo:{
214   -
215   - },
216   - statusBarHeight: 0,
217   - isLoading: false,
218   - loadingText: '加载中...',
219   - summaryData: {
220   - appointmentCount: 0,
221   - inviteCount: 0,
222   - expansionCount: 0,
223   - refundCount: 0
224   - },
225   - userInfo: {},
226   - jsjinfo:null,
227   - performanceData: {
228   - InvitationCount: 0,
229   - InviteCount: 0,
230   - AppointmentCount: 0,
231   - BillingCount: 0,
232   - BillingAmount: 0,
233   - ConsumeCount: 0,
234   - ConsumeAmount: 0,
235   - RefundCount: 0,
236   - RefundAmount: 0,
237   - HeadCount: 0,
238   - PersonCount: 0
239   - }
240   - }
241   - },
242   -
243   - computed: {
244   - iskjb(){
245   - if(this.newuserInfo.gw == '科技老师'){
246   - return false
247   - }
248   - return true
249   - },
250   - // 根据用户权限显示报表按钮
251   - showReport() {
252   - if (!this.userInfo || !this.userInfo.userAccount) return false
253   - const adminAccounts = ['admin', '13198568627', '18884847552', '13608016021', '18628973287']
254   - return adminAccounts.includes(this.userInfo.userAccount)
255   - }
256   - },
257   -
258   - async onLoad() {
259   - // this.getSystemInfo()
260   - this.showLoading('加载中...')
261   - this.checkLoginStatus()
262   - await this.int()
263   - },
264   -
265   - async onShow() {
266   -
267   -
268   - },
269   -
270   - methods: {
271   - int(){
272   - this.API.getUsers(this.userInfo.userId).then(res=>{
273   - console.error(res)
274   - if(res.code === 200){
275   - this.newuserInfo = res.data
276   - let date = new Date();
277   - let formattedDate = this.utils.gettime();
278   - console.log('formattedDate:', formattedDate);
279   - memberApi.getJsjInfoByUserMonth(this.userInfo.userId, formattedDate).then((resjsj) => {
280   - console.error(resjsj)
281   - if(resjsj.code == 200){
282   - this.jsjinfo = resjsj.data
283   - }
284   - })
285   - uni.setStorageSync('newuserInfo', this.newuserInfo)
286   - this.getSummaryData()
287   - this.getPerformanceData()
  209 + return {
  210 + newuserInfo: {
  211 +
  212 + },
  213 + statusBarHeight: 0,
  214 + isLoading: false,
  215 + loadingText: '加载中...',
  216 + summaryData: {
  217 + appointmentCount: 0,
  218 + inviteCount: 0,
  219 + expansionCount: 0,
  220 + refundCount: 0
  221 + },
  222 + userInfo: {},
  223 + jsjinfo: null,
  224 + performanceData: {
  225 + InvitationCount: 0,
  226 + InviteCount: 0,
  227 + AppointmentCount: 0,
  228 + BillingCount: 0,
  229 + BillingAmount: 0,
  230 + ConsumeCount: 0,
  231 + ConsumeAmount: 0,
  232 + RefundCount: 0,
  233 + RefundAmount: 0,
  234 + HeadCount: 0,
  235 + PersonCount: 0
288 236 }
289   - })
  237 + }
290 238 },
291   - // 获取系统信息
292   - getSystemInfo() {
293   - uni.getSystemInfo({
294   - success: (res) => {
295   - this.statusBarHeight = res.statusBarHeight
  239 +
  240 + computed: {
  241 + iskjb() {
  242 + if (this.newuserInfo.gw == '科技老师') {
  243 + return false
296 244 }
297   - })
298   - },
299   -
300   - // 检查登录状态
301   - checkLoginStatus() {
302   - const token = uni.getStorageSync('token')
303   -
304   - if (!token ) {
305   - uni.reLaunch({
306   - url: '/pages/login/login'
307   - })
308   - return
309   - } else{
310   - this.userInfo = uni.getStorageSync('userInfo');
  245 + return true
  246 + },
  247 + // 根据用户权限显示报表按钮
  248 + showReport() {
  249 + if (!this.userInfo || !this.userInfo.userAccount) return false
  250 + const adminAccounts = ['admin', '13198568627', '18884847552', '13608016021', '18628973287']
  251 + return adminAccounts.includes(this.userInfo.userAccount)
311 252 }
312 253 },
313   -
314   - // 显示加载效果
315   - showLoading(text = '加载中...') {
316   - this.isLoading = true
317   - this.loadingText = text
  254 +
  255 + async onLoad() {
  256 + // this.getSystemInfo()
  257 + this.showLoading('加载中...')
  258 + this.checkLoginStatus()
  259 + await this.int()
318 260 },
319   -
320   - // 隐藏加载效果
321   - hideLoading() {
322   - this.isLoading = false
  261 +
  262 + async onShow() {
  263 +
  264 +
323 265 },
324   -
325   - // 获取统计数据
326   - async getSummaryData() {
327   - try {
328   - const currentMonthRange = this.getCurrentMonthRange()
329   - if(this.iskjb) {
330   - let yyinfo = {
  266 +
  267 + methods: {
  268 + int() {
  269 + this.API.getUsers(this.userInfo.userId).then(res => {
  270 + console.error(res)
  271 + if (res.code === 200) {
  272 + this.newuserInfo = res.data
  273 + let date = new Date();
  274 + let formattedDate = this.utils.gettime();
  275 + console.log('formattedDate:', formattedDate);
  276 + memberApi.getJsjInfoByUserMonth(this.userInfo.userId, formattedDate).then((resjsj) => {
  277 + console.error(resjsj)
  278 + if (resjsj.code == 200) {
  279 + this.jsjinfo = resjsj.data
  280 + }
  281 + })
  282 + uni.setStorageSync('newuserInfo', this.newuserInfo)
  283 + this.getSummaryData()
  284 + this.getPerformanceData()
  285 + }
  286 + })
  287 + },
  288 + // 获取系统信息
  289 + getSystemInfo() {
  290 + uni.getSystemInfo({
  291 + success: (res) => {
  292 + this.statusBarHeight = res.statusBarHeight
  293 + }
  294 + })
  295 + },
  296 +
  297 + // 检查登录状态
  298 + checkLoginStatus() {
  299 + const token = uni.getStorageSync('token')
  300 +
  301 + if (!token) {
  302 + uni.reLaunch({
  303 + url: '/pages/login/login'
  304 + })
  305 + return
  306 + } else {
  307 + this.userInfo = uni.getStorageSync('userInfo');
  308 + }
  309 + },
  310 +
  311 + // 显示加载效果
  312 + showLoading(text = '加载中...') {
  313 + this.isLoading = true
  314 + this.loadingText = text
  315 + },
  316 +
  317 + // 隐藏加载效果
  318 + hideLoading() {
  319 + this.isLoading = false
  320 + },
  321 +
  322 + // 获取统计数据
  323 + async getSummaryData() {
  324 + try {
  325 + const currentMonthRange = this.getCurrentMonthRange()
  326 + if (this.iskjb) {
  327 + let yyinfo = {
  328 + pageSize: 1,
  329 + yysj: `${currentMonthRange[0]},${currentMonthRange[1]}`
  330 + }
  331 + let yayinfo = {
  332 + pageSize: 1,
  333 + yysj: `${currentMonthRange[0]},${currentMonthRange[1]}`
  334 + }
  335 + let tkinfo = {
  336 + pageSize: 1,
  337 + expansionTime: `${currentMonthRange[0]},${currentMonthRange[1]}`
  338 + }
  339 + if (this.newuserInfo.gw == '店助' || this.newuserInfo.gw == '店长') {
  340 + yyinfo.djmd = this.newuserInfo.mdid || '暂无'
  341 + yayinfo.storeId = this.newuserInfo.mdid || '暂无'
  342 + tkinfo.storeId = this.newuserInfo.mdid || '暂无'
  343 + } else {
  344 + yyinfo.yyr = this.userInfo.userId
  345 + yayinfo.yyr = this.userInfo.userId
  346 + tkinfo.expansionUserId = this.userInfo.userId
  347 + }
  348 + // 获取预约数
  349 + const appointmentRes = await this.API.getAppointmentList(yyinfo)
  350 +
  351 + if (appointmentRes.code === 200) {
  352 + this.summaryData.appointmentCount = appointmentRes.data.pagination?.total || 0
  353 + }
  354 +
  355 + // 获取邀请数
  356 + const inviteRes = await this.API.getInviteList(yayinfo)
  357 +
  358 + if (inviteRes.code === 200) {
  359 + this.summaryData.inviteCount = inviteRes.data.pagination?.total || 0
  360 + }
  361 +
  362 + // 获取拓客数
  363 + const expansionRes = await this.API.getExpansionList(tkinfo)
  364 +
  365 + if (expansionRes.code === 200) {
  366 + this.summaryData.expansionCount = expansionRes.data.pagination?.total || 0
  367 + }
  368 + }
  369 + let kainfo = {
331 370 pageSize: 1,
332   - yysj: `${currentMonthRange[0]},${currentMonthRange[1]}`
  371 + kdrq: `${currentMonthRange[0]},${currentMonthRange[1]}`
333 372 }
334   - let yayinfo = {
  373 + let hkinfo = {
335 374 pageSize: 1,
336   - yysj: `${currentMonthRange[0]},${currentMonthRange[1]}`
  375 + hksj: `${currentMonthRange[0]},${currentMonthRange[1]}`
337 376 }
338 377 let tkinfo = {
339 378 pageSize: 1,
340   - expansionTime: `${currentMonthRange[0]},${currentMonthRange[1]}`
  379 + tksj: `${currentMonthRange[0]},${currentMonthRange[1]}`
341 380 }
342   - if(this.newuserInfo.gw == '店助' || this.newuserInfo.gw == '店长') {
343   - yyinfo.djmd = this.newuserInfo.mdid || '暂无'
344   - yayinfo.storeId = this.newuserInfo.mdid || '暂无'
345   - tkinfo.storeId = this.newuserInfo.mdid || '暂无'
  381 + if (this.newuserInfo.gw == '科技老师') {
  382 + kainfo.kjblsId = this.userInfo.userId
  383 + hkinfo.kjblsId = this.userInfo.userId
  384 + tkinfo.kjblsId = this.userInfo.userId
  385 + } else if (this.newuserInfo.gw == '健康师') {
  386 + kainfo.jksId = this.userInfo.userId
  387 + hkinfo.jksId = this.userInfo.userId
  388 + tkinfo.jksId = this.userInfo.userId
  389 + } else if (this.newuserInfo.gw == '店助' || this.newuserInfo.gw == '店长') {
  390 + kainfo.djmd = this.newuserInfo.mdid || '暂无'
  391 + hkinfo.md = this.newuserInfo.mdid || '暂无'
  392 + tkinfo.md = this.newuserInfo.mdid || '暂无'
346 393 } else {
347   - yyinfo.yyr = this.userInfo.userId
348   - yayinfo.yyr = this.userInfo.userId
349   - tkinfo.expansionUserId = this.userInfo.userId
  394 + kainfo.CreateUser = this.userInfo.userId
  395 + hkinfo.czry = this.userInfo.userId
  396 + tkinfo.czry = this.userInfo.userId
350 397 }
351   - // 获取预约数
352   - const appointmentRes = await this.API.getAppointmentList(yyinfo)
353   -
354   - if (appointmentRes.code === 200) {
355   - this.summaryData.appointmentCount = appointmentRes.data.pagination?.total || 0
356   - }
357   -
358   - // 获取邀请数
359   - const inviteRes = await this.API.getInviteList(yayinfo)
360   -
361   - if (inviteRes.code === 200) {
362   - this.summaryData.inviteCount = inviteRes.data.pagination?.total || 0
  398 + // 获取开单数(本月)
  399 + const hkRes = await this.API.getLxList(kainfo)
  400 +
  401 + if (hkRes.code === 200) {
  402 + this.summaryData.hkCount = hkRes.data.pagination?.total || 0
363 403 }
364   -
365   - // 获取拓客数
366   - const expansionRes = await this.API.getExpansionList(tkinfo)
367   -
368   - if (expansionRes.code === 200) {
369   - this.summaryData.expansionCount = expansionRes.data.pagination?.total || 0
  404 + // 获取耗卡数
  405 +
  406 + const consumeRes = await this.API.getConsumeList(hkinfo)
  407 +
  408 + if (consumeRes.code === 200) {
  409 + this.summaryData.consumeCount = consumeRes.data.pagination?.total || 0
370 410 }
371   - }
372   - let kainfo = {
373   - pageSize: 1,
374   - kdrq: `${currentMonthRange[0]},${currentMonthRange[1]}`
375   - }
376   - let hkinfo = {
377   - pageSize: 1,
378   - hksj: `${currentMonthRange[0]},${currentMonthRange[1]}`
379   - }
380   - let tkinfo = {
381   - pageSize: 1,
382   - tksj: `${currentMonthRange[0]},${currentMonthRange[1]}`
383   - }
384   - if(this.newuserInfo.gw == '科技老师'){
385   - kainfo.kjblsId = this.userInfo.userId
386   - hkinfo.kjblsId = this.userInfo.userId
387   - tkinfo.kjblsId = this.userInfo.userId
388   - } else if(this.newuserInfo.gw == '健康师') {
389   - kainfo.jksId = this.userInfo.userId
390   - hkinfo.jksId = this.userInfo.userId
391   - tkinfo.jksId = this.userInfo.userId
392   - } else if(this.newuserInfo.gw == '店助' || this.newuserInfo.gw == '店长') {
393   - kainfo.djmd = this.newuserInfo.mdid || '暂无'
394   - hkinfo.md = this.newuserInfo.mdid || '暂无'
395   - tkinfo.md = this.newuserInfo.mdid || '暂无'
396   - } else {
397   - kainfo.CreateUser = this.userInfo.userId
398   - hkinfo.czry = this.userInfo.userId
399   - tkinfo.czry = this.userInfo.userId
400   - }
401   - // 获取开单数(本月)
402   - const hkRes = await this.API.getLxList(kainfo)
403   -
404   - if (hkRes.code === 200) {
405   - this.summaryData.hkCount = hkRes.data.pagination?.total || 0
406   - }
407   - // 获取耗卡数
408   -
409   - const consumeRes = await this.API.getConsumeList(hkinfo)
410   -
411   - if (consumeRes.code === 200) {
412   - this.summaryData.consumeCount = consumeRes.data.pagination?.total || 0
413   - }
414   - // 获取退卡数
415   -
416   - const refundRes = await this.API.getRefundList(tkinfo)
417   -
418   - if (refundRes.code === 200) {
419   - this.summaryData.refundCount = refundRes.data.pagination?.total || 0
420   - }
421   -
422   - } catch (error) {
423   - console.error('获取统计数据失败:', error)
424   - } finally {
425   - this.hideLoading()
426   - }
427   - },
428   -
429   - // 页面跳转
430   - goToPage(url) {
431   - uni.navigateTo({
432   - url: url
433   - })
434   - },
435   -
436   - // 退出登录
437   - handleLogout() {
438   - uni.showModal({
439   - title: '提示',
440   - content: '确定要退出登录吗?',
441   - success: (res) => {
442   - if (res.confirm) {
443   - this.API.logout().then(res=>{
444   - if(res.code == 200){
445   - // 清除本地存储
446   - uni.clearStorageSync()
447   - // 跳转到登录页
448   - uni.reLaunch({
449   - url: '/pages/login/login'
450   - })
451   - }
452   - })
  411 + // 获取退卡数
  412 +
  413 + const refundRes = await this.API.getRefundList(tkinfo)
  414 +
  415 + if (refundRes.code === 200) {
  416 + this.summaryData.refundCount = refundRes.data.pagination?.total || 0
453 417 }
  418 +
  419 + } catch (error) {
  420 + console.error('获取统计数据失败:', error)
  421 + } finally {
  422 + this.hideLoading()
454 423 }
455   - })
456   - },
457   -
458   - // 获取本月时间范围
459   - getCurrentMonthRange() {
460   - const now = new Date()
461   - const year = now.getFullYear()
462   - const month = now.getMonth()
463   -
464   - // 本月第一天 00:00:00
465   - const firstDay = new Date(year, month, 1)
466   - // 本月最后一天 23:59:59
467   - const lastDay = new Date(year, month + 1, 0, 23, 59, 59, 999)
468   - return [firstDay.getTime(), lastDay.getTime()]
469   - },
470   -
471   - // 获取当前月份字符串(格式:YYYYMM)
472   - getCurrentMonthStr() {
473   - const now = new Date()
474   - const year = now.getFullYear()
475   - const month = now.getMonth() + 1
476   - return `${year}${month.toString().padStart(2, '0')}`
477   - },
478   -
479   - // 格式化日期为 ISO 8601 格式(YYYY-MM-DDTHH:mm:ss)
480   - formatDateToISO(timestamp) {
481   - const date = new Date(timestamp)
482   - const year = date.getFullYear()
483   - const month = String(date.getMonth() + 1).padStart(2, '0')
484   - const day = String(date.getDate()).padStart(2, '0')
485   - const hours = String(date.getHours()).padStart(2, '0')
486   - const minutes = String(date.getMinutes()).padStart(2, '0')
487   - const seconds = String(date.getSeconds()).padStart(2, '0')
488   - return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`
489   - },
490   -
491   - // 获取业绩数据
492   - async getPerformanceData() {
493   - try {
494   - const statisticsMonth = this.getCurrentMonthStr()
495   - const currentMonthRange = this.getCurrentMonthRange()
496   - if(this.newuserInfo.gw == '科技老师'){
497   - const res = await performanceApi.GetTechTeacherStatistics({
498   - teacherId: this.userInfo.userId,
499   - startDate: this.formatDateToISO(currentMonthRange[0]),
500   - endDate: this.formatDateToISO(currentMonthRange[1])
501   - })
502   - if (res.code === 200 && res.data) {
503   - this.performanceData = res.data
  424 + },
  425 +
  426 + // 页面跳转
  427 + goToPage(url) {
  428 + uni.navigateTo({
  429 + url: url
  430 + })
  431 + },
  432 +
  433 + // 退出登录
  434 + handleLogout() {
  435 + uni.showModal({
  436 + title: '提示',
  437 + content: '确定要退出登录吗?',
  438 + success: (res) => {
  439 + if (res.confirm) {
  440 + this.API.logout().then(res => {
  441 + if (res.code == 200) {
  442 + // 清除本地存储
  443 + uni.clearStorageSync()
  444 + // 跳转到登录页
  445 + uni.reLaunch({
  446 + url: '/pages/login/login'
  447 + })
  448 + }
  449 + })
  450 + }
504 451 }
505   - } else {
506   - const res = await performanceApi.getEmployeePerformanceStatistics({
507   - userId: this.userInfo.userId,
508   - statisticsMonth: statisticsMonth
509   - })
510   - if (res.code === 200 && res.data) {
511   - this.performanceData = res.data
  452 + })
  453 + },
  454 +
  455 + // 获取本月时间范围
  456 + getCurrentMonthRange() {
  457 + const now = new Date()
  458 + const year = now.getFullYear()
  459 + const month = now.getMonth()
  460 +
  461 + // 本月第一天 00:00:00
  462 + const firstDay = new Date(year, month, 1)
  463 + // 本月最后一天 23:59:59
  464 + const lastDay = new Date(year, month + 1, 0, 23, 59, 59, 999)
  465 + return [firstDay.getTime(), lastDay.getTime()]
  466 + },
  467 +
  468 + // 获取当前月份字符串(格式:YYYYMM)
  469 + getCurrentMonthStr() {
  470 + const now = new Date()
  471 + const year = now.getFullYear()
  472 + const month = now.getMonth() + 1
  473 + return `${year}${month.toString().padStart(2, '0')}`
  474 + },
  475 +
  476 + // 格式化日期为 ISO 8601 格式(YYYY-MM-DDTHH:mm:ss)
  477 + formatDateToISO(timestamp) {
  478 + const date = new Date(timestamp)
  479 + const year = date.getFullYear()
  480 + const month = String(date.getMonth() + 1).padStart(2, '0')
  481 + const day = String(date.getDate()).padStart(2, '0')
  482 + const hours = String(date.getHours()).padStart(2, '0')
  483 + const minutes = String(date.getMinutes()).padStart(2, '0')
  484 + const seconds = String(date.getSeconds()).padStart(2, '0')
  485 + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`
  486 + },
  487 +
  488 + // 获取业绩数据
  489 + async getPerformanceData() {
  490 + try {
  491 + const statisticsMonth = this.getCurrentMonthStr()
  492 + const currentMonthRange = this.getCurrentMonthRange()
  493 + if (this.newuserInfo.gw == '科技老师') {
  494 + const res = await performanceApi.GetTechTeacherStatistics({
  495 + teacherId: this.userInfo.userId,
  496 + startDate: this.formatDateToISO(currentMonthRange[0]),
  497 + endDate: this.formatDateToISO(currentMonthRange[1])
  498 + })
  499 + if (res.code === 200 && res.data) {
  500 + this.performanceData = res.data.length>0?res.data[0]:{}
  501 + console.error({
  502 + ...this.performanceData
  503 + })
  504 + }
  505 + } else {
  506 + const res = await performanceApi.getEmployeePerformanceStatistics({
  507 + userId: this.userInfo.userId,
  508 + statisticsMonth: statisticsMonth
  509 + })
  510 + if (res.code === 200 && res.data) {
  511 + this.performanceData = res.data
  512 + }
512 513 }
  514 +
  515 + } catch (error) {
  516 + console.error('获取业绩数据失败:', error)
513 517 }
514   -
515   - } catch (error) {
516   - console.error('获取业绩数据失败:', error)
517 518 }
518 519 }
519 520 }
520   -}
521 521 </script>
522 522  
523 523 <style lang="scss" scoped>
524   -.container {
525   - min-height: 100vh;
526   - background: #e8f5e9;
527   -}
528   -
529   -.status-bar {
530   - background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
531   -}
532   -
533   -.header {
534   - background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
535   - padding: 32rpx 0 48rpx 0;
536   - position: relative;
537   - box-shadow: 0 4rpx 24rpx 0 rgba(67, 233, 123, 0.08);
538   -}
539   -
540   -.header-content {
541   - text-align: center;
542   - padding: 0 40rpx;
543   -}
544   -
545   -.header-title {
546   - color: #fff;
547   - font-size: 36rpx;
548   - font-weight: bold;
549   - letter-spacing: 4rpx;
550   - margin-bottom: 16rpx;
551   -}
552   -
553   -.header-subtitle {
554   - color: #e0f2f1;
555   - font-size: 28rpx;
556   - margin-bottom: 32rpx;
557   - font-weight: 500;
558   -}
559   -
560   -.info-section {
561   - background: rgba(255, 255, 255, 0.15);
562   - border-radius: 16rpx;
563   - padding: 24rpx 32rpx;
564   - margin: 0 auto;
565   - max-width: 600rpx;
566   - backdrop-filter: blur(10rpx);
567   -}
568   -
569   -.info-item {
570   - display: flex;
571   - align-items: center;
572   - justify-content: space-between;
573   - margin-bottom: 16rpx;
574   -}
575   -
576   -.info-item:last-child {
577   - margin-bottom: 0;
578   -}
579   -
580   -.info-label {
581   - color: #e0f2f1;
582   - font-size: 26rpx;
583   - font-weight: 500;
584   -}
585   -
586   -.info-value {
587   - color: #fff;
588   - font-size: 26rpx;
589   - font-weight: 600;
590   - text-align: right;
591   - flex: 1;
592   - margin-left: 16rpx;
593   -}
594   -
595   -.header-illustration {
596   - width: 120rpx;
597   - height: 120rpx;
598   - margin: 0 auto;
599   - background: rgba(255, 255, 255, 0.12);
600   - border-radius: 32rpx;
601   - display: flex;
602   - align-items: center;
603   - justify-content: center;
604   -}
605   -
606   -.summary-card {
607   - background: #fff;
608   - border-radius: 28rpx;
609   - box-shadow: 0 4rpx 24rpx 0 rgba(67, 233, 123, 0.08);
610   - margin: -36rpx 40rpx 36rpx 40rpx;
611   - display: flex;
612   - justify-content: space-between;
613   - padding: 28rpx 20rpx;
614   - position: relative;
615   - z-index: 2;
616   -}
617   -
618   -.summary-item {
619   - flex: 1;
620   - text-align: center;
621   - color: #388e3c;
622   - cursor: pointer;
623   -}
624   -
625   -.summary-item .num {
626   - font-size: 36rpx;
627   - font-weight: bold;
628   - color: #43a047;
629   - margin-bottom: 4rpx;
630   - display: block;
631   -}
632   -
633   -.summary-item .label {
634   - font-size: 24rpx;
635   - color: #6a9c6a;
636   -}
637   -
638   -.section-title {
639   - margin: 0 40rpx 16rpx 40rpx;
640   - font-size: 32rpx;
641   - color: #388e3c;
642   - font-weight: bold;
643   - letter-spacing: 2rpx;
644   -}
645   -
646   -.func-card {
647   - background: #fff;
648   - border-radius: 28rpx;
649   - box-shadow: 0 4rpx 24rpx 0 rgba(67, 233, 123, 0.08);
650   - padding: 36rpx 0 16rpx 0;
651   - margin: 0 40rpx 36rpx 40rpx;
652   -}
653   -
654   -.icon-grid {
655   - display: grid;
656   - grid-template-columns: repeat(3, 1fr);
657   - gap: 36rpx 0;
658   - width: 100%;
659   - justify-items: center;
660   -}
661   -
662   -.icon-btn {
663   - display: flex;
664   - flex-direction: column;
665   - align-items: center;
666   - justify-content: center;
667   - background: linear-gradient(135deg, #e8f5e9 60%, #c8e6c9 100%);
668   - border-radius: 32rpx;
669   - box-shadow: 0 4rpx 16rpx #c8e6c9;
670   - padding: 28rpx 0 16rpx 0;
671   - width: 140rpx;
672   - height: 160rpx;
673   - transition: box-shadow 0.18s, background 0.18s;
674   -}
675   -
676   -.icon-btn:active {
677   - box-shadow: 0 8rpx 32rpx #a5d6a7;
678   - background: linear-gradient(135deg, #b2dfdb 60%, #e8f5e9 100%);
679   -}
680   -
681   -.icon {
682   - width: 64rpx;
683   - height: 64rpx;
684   - margin-bottom: 12rpx;
685   - display: flex;
686   - align-items: center;
687   - justify-content: center;
688   -}
689   -
690   -.icon-label {
691   - font-size: 28rpx;
692   - color: #388e3c;
693   - margin-top: 4rpx;
694   - letter-spacing: 2rpx;
695   -}
696   -
697   -/* 加载效果样式 */
698   -.loading-overlay {
699   - position: fixed;
700   - top: 0;
701   - left: 0;
702   - width: 100%;
703   - height: 100%;
704   - background: rgba(255, 255, 255, 0.95);
705   - display: flex;
706   - justify-content: center;
707   - align-items: center;
708   - z-index: 9999;
709   - backdrop-filter: blur(4px);
710   -}
711   -
712   -.loading-content {
713   - text-align: center;
714   -}
715   -
716   -.loading-spinner {
717   - width: 60rpx;
718   - height: 60rpx;
719   - border: 4rpx solid #e8f5e9;
720   - border-top: 4rpx solid #43a047;
721   - border-radius: 50%;
722   - animation: spin 1s linear infinite;
723   - margin: 0 auto;
724   -}
725   -
726   -.loading-text {
727   - margin-top: 20rpx;
728   - color: #388e3c;
729   - font-size: 32rpx;
730   - font-weight: 500;
731   - text-align: center;
732   -}
733   -
734   -@keyframes spin {
735   - 0% { transform: rotate(0deg); }
736   - 100% { transform: rotate(360deg); }
737   -}
738   -
739   -/* 业绩数据板块样式 */
740   -.performance-card {
741   - background: #fff;
742   - border-radius: 28rpx;
743   - box-shadow: 0 4rpx 24rpx 0 rgba(67, 233, 123, 0.08);
744   - padding: 32rpx 24rpx;
745   - margin: 0 40rpx 36rpx 40rpx;
746   -}
747   -
748   -.performance-row {
749   - display: flex;
750   - justify-content: space-between;
751   - margin-bottom: 24rpx;
752   -}
753   -
754   -.performance-row:last-child {
755   - margin-bottom: 0;
756   -}
757   -
758   -.performance-item {
759   - flex: 1;
760   - text-align: center;
761   - padding: 16rpx 8rpx;
762   - background: linear-gradient(135deg, #e8f5e9 60%, #c8e6c9 100%);
763   - border-radius: 20rpx;
764   - margin: 0 8rpx;
765   - box-shadow: 0 2rpx 8rpx rgba(67, 233, 123, 0.1);
766   -}
767   -
768   -.performance-item.highlight {
769   - background: linear-gradient(135deg, #fff3e0 60%, #ffe0b2 100%);
770   - box-shadow: 0 2rpx 8rpx rgba(255, 152, 0, 0.2);
771   -}
772   -
773   -.performance-value {
774   - font-size: 32rpx;
775   - font-weight: bold;
776   - color: #43a047;
777   - margin-bottom: 8rpx;
778   -}
779   -
780   -.performance-item.highlight .performance-value {
781   - color: #f57c00;
782   -}
783   -
784   -.performance-label {
785   - font-size: 22rpx;
786   - color: #6a9c6a;
787   -}
788   -
789   -.performance-item.highlight .performance-label {
790   - color: #e65100;
791   -}
792   -</style>
  524 + .container {
  525 + min-height: 100vh;
  526 + background: #e8f5e9;
  527 + }
  528 +
  529 + .status-bar {
  530 + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
  531 + }
  532 +
  533 + .header {
  534 + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
  535 + padding: 32rpx 0 48rpx 0;
  536 + position: relative;
  537 + box-shadow: 0 4rpx 24rpx 0 rgba(67, 233, 123, 0.08);
  538 + }
  539 +
  540 + .header-content {
  541 + text-align: center;
  542 + padding: 0 40rpx;
  543 + }
  544 +
  545 + .header-title {
  546 + color: #fff;
  547 + font-size: 36rpx;
  548 + font-weight: bold;
  549 + letter-spacing: 4rpx;
  550 + margin-bottom: 16rpx;
  551 + }
  552 +
  553 + .header-subtitle {
  554 + color: #e0f2f1;
  555 + font-size: 28rpx;
  556 + margin-bottom: 32rpx;
  557 + font-weight: 500;
  558 + }
  559 +
  560 + .info-section {
  561 + background: rgba(255, 255, 255, 0.15);
  562 + border-radius: 16rpx;
  563 + padding: 24rpx 32rpx;
  564 + margin: 0 auto;
  565 + max-width: 600rpx;
  566 + backdrop-filter: blur(10rpx);
  567 + }
  568 +
  569 + .info-item {
  570 + display: flex;
  571 + align-items: center;
  572 + justify-content: space-between;
  573 + margin-bottom: 16rpx;
  574 + }
  575 +
  576 + .info-item:last-child {
  577 + margin-bottom: 0;
  578 + }
  579 +
  580 + .info-label {
  581 + color: #e0f2f1;
  582 + font-size: 26rpx;
  583 + font-weight: 500;
  584 + }
  585 +
  586 + .info-value {
  587 + color: #fff;
  588 + font-size: 26rpx;
  589 + font-weight: 600;
  590 + text-align: right;
  591 + flex: 1;
  592 + margin-left: 16rpx;
  593 + }
  594 +
  595 + .header-illustration {
  596 + width: 120rpx;
  597 + height: 120rpx;
  598 + margin: 0 auto;
  599 + background: rgba(255, 255, 255, 0.12);
  600 + border-radius: 32rpx;
  601 + display: flex;
  602 + align-items: center;
  603 + justify-content: center;
  604 + }
  605 +
  606 + .summary-card {
  607 + background: #fff;
  608 + border-radius: 28rpx;
  609 + box-shadow: 0 4rpx 24rpx 0 rgba(67, 233, 123, 0.08);
  610 + margin: -36rpx 40rpx 36rpx 40rpx;
  611 + display: flex;
  612 + justify-content: space-between;
  613 + padding: 28rpx 20rpx;
  614 + position: relative;
  615 + z-index: 2;
  616 + }
  617 +
  618 + .summary-item {
  619 + flex: 1;
  620 + text-align: center;
  621 + color: #388e3c;
  622 + cursor: pointer;
  623 + }
  624 +
  625 + .summary-item .num {
  626 + font-size: 36rpx;
  627 + font-weight: bold;
  628 + color: #43a047;
  629 + margin-bottom: 4rpx;
  630 + display: block;
  631 + }
  632 +
  633 + .summary-item .label {
  634 + font-size: 24rpx;
  635 + color: #6a9c6a;
  636 + }
  637 +
  638 + .section-title {
  639 + margin: 0 40rpx 16rpx 40rpx;
  640 + font-size: 32rpx;
  641 + color: #388e3c;
  642 + font-weight: bold;
  643 + letter-spacing: 2rpx;
  644 + }
  645 +
  646 + .func-card {
  647 + background: #fff;
  648 + border-radius: 28rpx;
  649 + box-shadow: 0 4rpx 24rpx 0 rgba(67, 233, 123, 0.08);
  650 + padding: 36rpx 0 16rpx 0;
  651 + margin: 0 40rpx 36rpx 40rpx;
  652 + }
  653 +
  654 + .icon-grid {
  655 + display: grid;
  656 + grid-template-columns: repeat(3, 1fr);
  657 + gap: 36rpx 0;
  658 + width: 100%;
  659 + justify-items: center;
  660 + }
  661 +
  662 + .icon-btn {
  663 + display: flex;
  664 + flex-direction: column;
  665 + align-items: center;
  666 + justify-content: center;
  667 + background: linear-gradient(135deg, #e8f5e9 60%, #c8e6c9 100%);
  668 + border-radius: 32rpx;
  669 + box-shadow: 0 4rpx 16rpx #c8e6c9;
  670 + padding: 28rpx 0 16rpx 0;
  671 + width: 140rpx;
  672 + height: 160rpx;
  673 + transition: box-shadow 0.18s, background 0.18s;
  674 + }
  675 +
  676 + .icon-btn:active {
  677 + box-shadow: 0 8rpx 32rpx #a5d6a7;
  678 + background: linear-gradient(135deg, #b2dfdb 60%, #e8f5e9 100%);
  679 + }
  680 +
  681 + .icon {
  682 + width: 64rpx;
  683 + height: 64rpx;
  684 + margin-bottom: 12rpx;
  685 + display: flex;
  686 + align-items: center;
  687 + justify-content: center;
  688 + }
  689 +
  690 + .icon-label {
  691 + font-size: 28rpx;
  692 + color: #388e3c;
  693 + margin-top: 4rpx;
  694 + letter-spacing: 2rpx;
  695 + }
  696 +
  697 + /* 加载效果样式 */
  698 + .loading-overlay {
  699 + position: fixed;
  700 + top: 0;
  701 + left: 0;
  702 + width: 100%;
  703 + height: 100%;
  704 + background: rgba(255, 255, 255, 0.95);
  705 + display: flex;
  706 + justify-content: center;
  707 + align-items: center;
  708 + z-index: 9999;
  709 + backdrop-filter: blur(4px);
  710 + }
  711 +
  712 + .loading-content {
  713 + text-align: center;
  714 + }
  715 +
  716 + .loading-spinner {
  717 + width: 60rpx;
  718 + height: 60rpx;
  719 + border: 4rpx solid #e8f5e9;
  720 + border-top: 4rpx solid #43a047;
  721 + border-radius: 50%;
  722 + animation: spin 1s linear infinite;
  723 + margin: 0 auto;
  724 + }
  725 +
  726 + .loading-text {
  727 + margin-top: 20rpx;
  728 + color: #388e3c;
  729 + font-size: 32rpx;
  730 + font-weight: 500;
  731 + text-align: center;
  732 + }
  733 +
  734 + @keyframes spin {
  735 + 0% {
  736 + transform: rotate(0deg);
  737 + }
  738 +
  739 + 100% {
  740 + transform: rotate(360deg);
  741 + }
  742 + }
  743 +
  744 + /* 业绩数据板块样式 */
  745 + .performance-card {
  746 + background: #fff;
  747 + border-radius: 28rpx;
  748 + box-shadow: 0 4rpx 24rpx 0 rgba(67, 233, 123, 0.08);
  749 + padding: 32rpx 24rpx;
  750 + margin: 0 40rpx 36rpx 40rpx;
  751 + }
  752 +
  753 + .performance-row {
  754 + display: flex;
  755 + justify-content: space-between;
  756 + margin-bottom: 24rpx;
  757 + }
  758 +
  759 + .performance-row:last-child {
  760 + margin-bottom: 0;
  761 + }
  762 +
  763 + .performance-item {
  764 + flex: 1;
  765 + text-align: center;
  766 + padding: 16rpx 8rpx;
  767 + background: linear-gradient(135deg, #e8f5e9 60%, #c8e6c9 100%);
  768 + border-radius: 20rpx;
  769 + margin: 0 8rpx;
  770 + box-shadow: 0 2rpx 8rpx rgba(67, 233, 123, 0.1);
  771 + }
  772 +
  773 + .performance-item.highlight {
  774 + background: linear-gradient(135deg, #fff3e0 60%, #ffe0b2 100%);
  775 + box-shadow: 0 2rpx 8rpx rgba(255, 152, 0, 0.2);
  776 + }
  777 +
  778 + .performance-value {
  779 + font-size: 32rpx;
  780 + font-weight: bold;
  781 + color: #43a047;
  782 + margin-bottom: 8rpx;
  783 + }
  784 +
  785 + .performance-item.highlight .performance-value {
  786 + color: #f57c00;
  787 + }
  788 +
  789 + .performance-label {
  790 + font-size: 22rpx;
  791 + color: #6a9c6a;
  792 + }
  793 +
  794 + .performance-item.highlight .performance-label {
  795 + color: #e65100;
  796 + }
  797 +</style>
793 798 \ No newline at end of file
... ...
绿纤uni-app/pages/member-consume/member-consume copy.vue 0 → 100644
  1 +<template>
  2 + <view class="member-consume-container">
  3 + <view class="form-card">
  4 + <view class="form-content">
  5 + <form @submit="handleFormSubmit">
  6 + <!-- 会员选择 -->
  7 + <view class="form-group">
  8 + <text class="form-label">会员</text>
  9 + <view class="custom-select" @tap="removeid?'':openSelectModal('hy')">
  10 + <text class="select-text">{{ formData.hy || '请选择会员' }}</text>
  11 + <text class="select-arrow">▼</text>
  12 + </view>
  13 + </view>
  14 +
  15 + <!-- 耗卡日期 - 只有新增耗卡时且为授权用户才显示 -->
  16 + <view class="form-group" v-if="canEditDate && !removeid">
  17 + <text class="form-label">耗卡日期</text>
  18 + <view class="input-wrapper">
  19 + <picker mode="date" :value="formData.hksj" @change="onDateChange">
  20 + <view class="custom-select">
  21 + <text class="select-text">{{ formData.hksj || '请选择耗卡日期' }}</text>
  22 + <text class="select-arrow">▼</text>
  23 + </view>
  24 + </picker>
  25 + </view>
  26 + </view>
  27 +
  28 + <!-- 品项明细 -->
  29 + <view class="form-group">
  30 + <text class="form-label">品项明细</text>
  31 + <view class="px-container">
  32 + <view v-for="(px, index) in pxList" :key="index" class="px-row">
  33 + <!-- 品项选择区域 -->
  34 + <view v-if="px.px && px.pxmc" class="px-info">
  35 + <view class="px-info-title">{{ px.pxmc }}</view>
  36 + <view class="px-info-details">
  37 + <view class="px-info-item">
  38 + <text class="px-info-label">单价:</text>
  39 + <text class="px-info-value">¥{{ px.pxjg || 0 }}</text>
  40 + </view>
  41 + <view class="px-info-item" v-if="px.TotalPurchased ">
  42 + <text class="px-info-label">总购买:</text>
  43 + <text class="px-info-value">{{ px.TotalPurchased || 0 }}</text>
  44 + </view>
  45 + <view class="px-info-item" v-if="px.ConsumedCount ">
  46 + <text class="px-info-label">已消费:</text>
  47 + <text class="px-info-value">{{ px.ConsumedCount || 0 }}</text>
  48 + </view>
  49 + <view class="px-info-item" v-if="px.RemainingCount">
  50 + <text class="px-info-label">剩余:</text>
  51 + <text class="px-info-value">{{ px.RemainingCount || 0 }}</text>
  52 + </view>
  53 + <view class="px-info-item">
  54 + <text class="px-info-label">来源:</text>
  55 + <text class="px-info-value">{{ px.sourceType || "" }}</text>
  56 + </view>
  57 + <view class="px-info-item" >
  58 + <text class="px-info-label">科美手工费:</text>
  59 + <text class="px-info-value">{{ px.techBeautyLaborCost || "0" }}</text>
  60 + </view>
  61 + <view class="px-info-item">
  62 + <text class="px-info-label">健康师手工费:</text>
  63 + <text class="px-info-value">{{ px.healthCoachLaborCost || "0" }}</text>
  64 + </view>
  65 + <view class="px-info-item">
  66 + <text class="px-info-label">总业绩:</text>
  67 + <text class="px-info-value">{{ px.pxjg * px.projectNumber || "0" }}</text>
  68 + </view>
  69 + </view>
  70 + </view>
  71 + <view v-else class="px-select" @tap="selectPx(index)">
  72 + 选择品项
  73 + </view>
  74 +
  75 + <!-- 次数输入框 -->
  76 + <input :disabled="removeid?true:false" type="number" class="px-number" placeholder="次数" min="1" :max="px.RemainingCount"
  77 + step="1" v-model="px.projectNumber" @input="updatePxNumber(index, $event)">
  78 +
  79 + <!-- 删除按钮 -->
  80 + <button type="button" class="px-delete" @tap="deletePxRow(index)">删除</button>
  81 +
  82 + <!-- 第三行:健康师和科技部老师 -->
  83 + <view class="px-row-third">
  84 + <!-- 健康师选择 -->
  85 + <view class="px-staff-section">
  86 + <view class="px-jks-select" v-if="px.qt2 != '医美'" @tap="selectPxJks(index)">
  87 + 添加健康师
  88 + </view>
  89 + <view class="px-jks-list"
  90 + v-if="px.lqXhJksyjList && px.lqXhJksyjList.length > 0">
  91 + <view v-for="(jks, jksIndex) in px.lqXhJksyjList" :key="jksIndex"
  92 + class="px-staff-item">
  93 + <view class="px-staff-header">
  94 + <text class="px-staff-name">{{ jks.jksxm }}</text>
  95 + <button v-if="px.qt2 != '医美'" class="px-staff-remove"
  96 + @click="removePxJks(index, jksIndex)">删除</button>
  97 + </view>
  98 + <view class="px-staff-fields">
  99 + <view class="px-staff-row">
  100 + <view class="px-staff-field">
  101 + <text class="px-staff-field-label">业绩</text>
  102 + <input disabled type="text" v-model="jks.jksyj" placeholder="请输入业绩"
  103 + @change="updateJksField(index, jksIndex, 'jksyj', $event)">
  104 + </view>
  105 + <view v-if="px.qt2 != '医美'" class="px-staff-field">
  106 + <text class="px-staff-field-label">手工费</text>
  107 + <input disabled type="number" v-model="jks.laborCost"
  108 + placeholder="手工费" min="0" step="0.01"
  109 + @change="updateJksField(index, jksIndex, 'laborCost', $event)">
  110 + </view>
  111 + </view>
  112 + <view v-if="px.qt2 != '医美'" class="px-staff-row">
  113 + <view class="px-staff-field">
  114 + <text class="px-staff-field-label">次数</text>
  115 + <input disabled type="number" v-model="jks.kdpxNumber"
  116 + placeholder="次数" min="0" step="1"
  117 + @change="updateJksField(index, jksIndex, 'kdpxNumber', $event)">
  118 + </view>
  119 + <view class="px-staff-field">
  120 + <!-- 占位,保持布局平衡 -->
  121 + </view>
  122 + </view>
  123 + </view>
  124 + </view>
  125 + </view>
  126 + </view>
  127 +
  128 + <!-- 科技部老师选择 -->
  129 + <view class="px-staff-section">
  130 + <view class="px-kjb-select" @tap="selectPxKjb(index)"
  131 + :style="{display: px.qt2 === '科美' ? 'block' : 'none'}">
  132 + 添加科技部老师
  133 + </view>
  134 + <view class="px-kjb-list"
  135 + v-if="px.lqXhKjbsyjList && px.lqXhKjbsyjList.length > 0">
  136 + <view v-for="(kjb, kjbIndex) in px.lqXhKjbsyjList" :key="kjbIndex"
  137 + class="px-staff-item">
  138 + <view class="px-staff-header">
  139 + <text class="px-staff-name">{{ kjb.kjblsxm }}</text>
  140 + <button class="px-staff-remove"
  141 + @click="removePxKjb(index, kjbIndex)">删除</button>
  142 + </view>
  143 + <view class="px-staff-fields">
  144 + <view class="px-staff-row">
  145 + <view class="px-staff-field">
  146 + <text class="px-staff-field-label">业绩</text>
  147 + <input disabled type="text" v-model="kjb.kjblsyj" placeholder="请输入业绩"
  148 + @change="updateKjbField(index, kjbIndex, 'kjblsyj', $event)">
  149 + </view>
  150 + <view class="px-staff-field">
  151 + <text class="px-staff-field-label">手工费</text>
  152 + <input disabled type="number" v-model="kjb.laborCost"
  153 + placeholder="手工费" min="0" step="0.01"
  154 + @change="updateKjbField(index, kjbIndex, 'laborCost', $event)">
  155 + </view>
  156 + </view>
  157 + <view class="px-staff-row">
  158 + <view class="px-staff-field">
  159 + <text class="px-staff-field-label">次数</text>
  160 + <input disabled type="number" v-model="kjb.hdpxNumber"
  161 + placeholder="次数" min="0" step="1"
  162 + @change="updateKjbField(index, kjbIndex, 'hdpxNumber', $event)">
  163 + </view>
  164 + <view class="px-staff-field">
  165 + <!-- 占位,保持布局平衡 -->
  166 + </view>
  167 + </view>
  168 + </view>
  169 + </view>
  170 + </view>
  171 + </view>
  172 +
  173 + <!-- 陪同选择(仅当 isAllowAccompanied 为 1 时显示) -->
  174 + <view class="px-staff-section" v-if="px.isAllowAccompanied == 1">
  175 + <view class="px-jks-select" @tap="selectAccompaniedJks(index)">
  176 + 添加陪同健康师
  177 + </view>
  178 + <view class="px-jks-list"
  179 + v-if="px.accompaniedJksList && px.accompaniedJksList.length > 0">
  180 + <view v-for="(accompaniedJks, accompaniedIndex) in px.accompaniedJksList" :key="accompaniedIndex"
  181 + class="px-staff-item">
  182 + <view class="px-staff-header">
  183 + <text class="px-staff-name">{{ accompaniedJks.jksxm }}</text>
  184 + <button class="px-staff-remove"
  185 + @click="removeAccompaniedJks(index, accompaniedIndex)">删除</button>
  186 + </view>
  187 + <view class="px-staff-fields">
  188 + <view class="px-staff-row">
  189 + <!-- <view class="px-staff-field">
  190 + <text class="px-staff-field-label">是否陪同</text>
  191 + <input type="number" v-model="accompaniedJks.isAccompanied"
  192 + placeholder="是否陪同" min="0" max="1" step="1"
  193 + @change="updateAccompaniedJksField(index, accompaniedIndex, 'isAccompanied', $event)">
  194 + </view> -->
  195 + <view class="px-staff-field">
  196 + <text class="px-staff-field-label">陪同次数</text>
  197 + <input type="digit" v-model="accompaniedJks.accompaniedProjectNumber"
  198 + placeholder="陪同次数" min="0" step="1"
  199 + @change="updateAccompaniedJksField(index, accompaniedIndex, 'accompaniedProjectNumber', $event)">
  200 + </view>
  201 + </view>
  202 + </view>
  203 + </view>
  204 + </view>
  205 + </view>
  206 + </view>
  207 + </view>
  208 + </view>
  209 + <button type="button" class="btn-add-px" @tap="addPxRow">添加品项</button>
  210 + </view>
  211 +
  212 + <!-- 消费金额 -->
  213 + <view class="form-group">
  214 + <text class="form-label">消费金额</text>
  215 + <view class="input-wrapper">
  216 + <input type="number" v-model="formData.xfje" placeholder="自动计算" min="0" step="0.01"
  217 + disabled>
  218 + </view>
  219 + </view>
  220 +
  221 + <!-- 手工费用 -->
  222 + <view class="form-group">
  223 + <text class="form-label">手工费用</text>
  224 + <view class="input-wrapper">
  225 + <input type="number" v-model="formData.sgfy" placeholder="自动计算" min="0" step="0.01"
  226 + disabled>
  227 + </view>
  228 + </view>
  229 +
  230 + <!-- 是否加班 - 只在晚上7点半后显示 -->
  231 + <view class="form-group" v-if="showOvertimeOption && !removeid">
  232 + <text class="form-label">是否加班</text>
  233 + <view class="input-wrapper">
  234 + <checkbox-group @change="onOvertimeChange">
  235 + <view class="checkbox-wrapper" @tap.stop="toggleOvertime">
  236 + <checkbox value="overtime" :checked="formData.isOvertime" />
  237 + <text class="checkbox-label">加班</text>
  238 + </view>
  239 + </checkbox-group>
  240 + <!-- 加班系数下拉选择 -->
  241 + <view v-if="formData.isOvertime" class="overtime-select-wrapper">
  242 + <picker mode="selector" :range="overtimeOptions" :value="overtimeIndex" @change="onOvertimeCoefficientChange">
  243 + <view class="custom-select">
  244 + <text class="select-text">{{ formData.overtimeCoefficient > 0 ? formData.overtimeCoefficient : '请选择加班系数' }}</text>
  245 + <text class="select-arrow">▼</text>
  246 + </view>
  247 + </picker>
  248 + </view>
  249 + </view>
  250 + </view>
  251 + <view class="form-group" v-else-if="removeid">
  252 + <text class="form-label">是否加班</text>
  253 + <view class="input-wrapper">
  254 + <view class="custom-select">
  255 + <text class="select-text">{{ formData.overtimeCoefficient > 0 ? formData.overtimeCoefficient : '否' }}</text>
  256 + <!-- <text class="select-arrow">▼</text> -->
  257 + </view>
  258 + </view>
  259 + </view>
  260 + <!-- 会员签字 -->
  261 + <view class="form-group" v-if="!removeid">
  262 + <text class="form-label">会员签字</text>
  263 + <view class="input-wrapper">
  264 + <view v-if="!memberSignature" class="signature-placeholder">
  265 + <button @click="openSignatureModal" class="btn-signature-placeholder">
  266 + <text class="signature-placeholder-text">点击进行签字</text>
  267 + </button>
  268 + </view>
  269 + <view v-if="memberSignature" class="signature-preview">
  270 + <text class="preview-label">签字预览:</text>
  271 + <image @tap="previewSignature(memberSignature)" :src="memberSignature"
  272 + class="signature-image" mode="aspectFit" />
  273 + <view class="signature-actions">
  274 + <button @click="openSignatureModal" class="btn-re-signature">重新签字</button>
  275 + <button @click="clearMemberSignature" class="btn-clear-signature">清除签字</button>
  276 + </view>
  277 + </view>
  278 + </view>
  279 + </view>
  280 + <view class="form-group" v-else-if="memberSignature&&removeid">
  281 + <text class="form-label">会员签字</text>
  282 + <view class="input-wrapper">
  283 + <view class="signature-preview">
  284 + <image @tap="previewSignature(baseUrl+memberSignature)" :src="baseUrl+memberSignature"
  285 + class="signature-image" mode="aspectFit" />
  286 + </view>
  287 + </view>
  288 + </view>
  289 +
  290 +
  291 + <!-- 提交按钮 -->
  292 + <view class="btn-group">
  293 + <button type="submit" class="btn btn-primary"
  294 + @tap="issubmitOrder?submitConsume():null">{{ issubmitOrder?'提交':'提交中...' }}</button>
  295 + </view>
  296 + </form>
  297 + </view>
  298 + </view>
  299 +
  300 + <!-- 选择弹窗 -->
  301 + <SearchSelectModal :show="showModal" :title="modalTitle" :options="currentOptions" :loading="modalLoading"
  302 + :has-more="hasMoreData" :search-param="searchParam"
  303 + :show-cross-store="currentSelectField === 'hy'"
  304 + :is-cross-store="isCrossStore"
  305 + @confirm="handleModalConfirm" @close="closeModal"
  306 + @load-more="handleLoadMore" @refresh="handleRefresh" @search="handleSearch"
  307 + @cross-store-change="onCrossStoreChange" />
  308 +
  309 + <!-- 消息提示 -->
  310 + <u-toast ref="uToast"></u-toast>
  311 +
  312 + <!-- 全屏签字弹窗 -->
  313 + <view v-if="showSignatureModal" class="signature-modal-overlay" @tap="closeSignatureModal">
  314 + <view class="signature-modal" @tap.stop>
  315 + <view class="signature-modal-header">
  316 + <text class="signature-modal-title"></text>
  317 + <button @click="closeSignatureModal" class="btn-close-modal">×</button>
  318 + </view>
  319 + <view class="signature-modal-content">
  320 + <SignaturePad :width="800" :height="500" :line-width="4" stroke-color="#2e7d32"
  321 + @confirm="handleSignatureConfirm" @clear="handleSignatureClear" ref="signaturePadModal" />
  322 + </view>
  323 + </view>
  324 + </view>
  325 + </view>
  326 +</template>
  327 +
  328 +<script>
  329 + import SearchSelectModal from '@/components/SearchSelectModal.vue'
  330 + import SignaturePad from '@/components/SignaturePad.vue'
  331 + import memberApi from '@/apis/modules/member.js'
  332 + import lxApi from '@/apis/modules/lx.js'
  333 + import projectApi from '@/apis/modules/project.js'
  334 + import appointmentApi from '@/apis/modules/appointment.js'
  335 + import consumeApi from '@/apis/modules/consume.js'
  336 + import config from '@/common/config.js'
  337 +
  338 + export default {
  339 + components: {
  340 + SearchSelectModal,
  341 + SignaturePad
  342 + },
  343 + data() {
  344 + return {
  345 + issubmitOrder: true,
  346 + baseUrl: config.getApiBaseUrl(),
  347 + // 表单数据
  348 + formData: {
  349 + hy: '',
  350 + hyzh: '',
  351 + hymc: '',
  352 + gklx: '',
  353 + hksj: '', // 耗卡日期
  354 + xfje: '',
  355 + sgfy: '',
  356 + isOvertime: false, // 是否加班
  357 + overtimeCoefficient: 0 // 加班系数,默认0
  358 + },
  359 +
  360 + // 会员签字
  361 + memberSignature: '',
  362 + showSignatureModal: false,
  363 + scrollTop: 0,
  364 +
  365 + // 选中的值
  366 + selectedValues: {
  367 + hy: null
  368 + },
  369 +
  370 + // 品项列表
  371 + pxList: [],
  372 +
  373 + // 弹窗相关
  374 + showModal: false,
  375 + modalTitle: '',
  376 + currentSelectField: '',
  377 + currentOptions: [],
  378 + modalLoading: false,
  379 + hasMoreData: true,
  380 + currentPage: 1,
  381 + pageSize: 20,
  382 + searchKeyword: '',
  383 + searchParam: '',
  384 +
  385 + // 用户信息
  386 + userInfo: null,
  387 +
  388 + // 跨店相关
  389 + isCrossStore: false,
  390 +
  391 + // 选项数据
  392 + jksOptions: [],
  393 + kjbOptions: [],
  394 +
  395 + // 当前选择的行索引
  396 + currentRowIndex: -1,
  397 + currentJksIndex: -1,
  398 + currentKjbIndex: -1,
  399 + mdxx: null,
  400 + removeinfo: {},
  401 + removeid: null,
  402 +
  403 + // 加班系数选项
  404 + overtimeOptions: [0.5, 1, 1.5, 2],
  405 +
  406 + // 陪同模式标识
  407 + isAccompaniedMode: false
  408 + }
  409 + },
  410 + onLoad(options) {
  411 + this.initializePage(options);
  412 + },
  413 +
  414 + onUnload() {
  415 + // 页面卸载时恢复页面滚动
  416 + this.enablePageScroll();
  417 + },
  418 + computed: {
  419 + // 检查当前用户是否可以修改耗卡日期
  420 + canEditDate() {
  421 + // && this.userInfo.userId === '18628973287';
  422 + return this.userInfo
  423 + },
  424 + // 判断是否超过晚上8点,决定是否显示加班选项
  425 + showOvertimeOption() {
  426 + // return true;
  427 + const now = new Date();
  428 + const hours = now.getHours();
  429 + // 判断是否超过20:00(八点)
  430 + return hours >= 20;
  431 + },
  432 + // 获取当前加班系数在选项数组中的索引
  433 + overtimeIndex() {
  434 + const index = this.overtimeOptions.indexOf(this.formData.overtimeCoefficient);
  435 + return index >= 0 ? index : 0;
  436 + }
  437 + },
  438 + methods: {
  439 + // 处理日期变化
  440 + onDateChange(e) {
  441 + this.formData.hksj = e.detail.value;
  442 + },
  443 +
  444 + // 跨店开关变化
  445 + onCrossStoreChange(value) {
  446 + this.isCrossStore = value;
  447 + // 如果弹窗已打开且正在选择会员,重新加载数据
  448 + if (this.showModal && this.currentSelectField === 'hy') {
  449 + this.currentPage = 1;
  450 + this.hasMoreData = true;
  451 + this.loadOptionsData('hy', 1, this.searchKeyword);
  452 + }
  453 + },
  454 +
  455 + // 切换加班状态(点击容器时触发)
  456 + toggleOvertime() {
  457 + this.formData.isOvertime = !this.formData.isOvertime;
  458 + this.handleOvertimeChange(this.formData.isOvertime);
  459 + },
  460 +
  461 + // 处理是否加班变化
  462 + onOvertimeChange(e) {
  463 + console.log('onOvertimeChange', e);
  464 + // checkbox-group 的 change 事件,e.detail.value 是一个数组,包含所有选中的值
  465 + // const checked = e.detail.value && e.detail.value.length > 0 && e.detail.value.includes('overtime');
  466 + // this.formData.isOvertime = checked;
  467 + // this.handleOvertimeChange(checked);
  468 + },
  469 +
  470 + // 处理加班状态变化的统一逻辑
  471 + handleOvertimeChange(isOvertime) {
  472 + if (!isOvertime) {
  473 + // 如果取消勾选,重置加班系数为0
  474 + this.formData.overtimeCoefficient = 0;
  475 + } else {
  476 + // 如果勾选,默认选择第一个选项(0.5)
  477 + if (!this.formData.overtimeCoefficient || this.formData.overtimeCoefficient === 0) {
  478 + this.formData.overtimeCoefficient = 0.5;
  479 + }
  480 + }
  481 + },
  482 +
  483 + // 处理加班系数选择变化
  484 + onOvertimeCoefficientChange(e) {
  485 + const index = e.detail.value;
  486 + this.formData.overtimeCoefficient = this.overtimeOptions[index];
  487 + },
  488 +
  489 + // 签字相关方法
  490 + async newUploadBase64Image() {
  491 + let info = null;
  492 + await lxApi.UploadBase64Image({
  493 + "base64Data": this.memberSignature,
  494 + "imageType": "png",
  495 + "fileName": "memberSignature.png"
  496 + }).then(res => {
  497 + console.log('UploadBase64Image', res);
  498 + if (res.code == 200) {
  499 + info = res.data;
  500 + }
  501 + })
  502 + return info
  503 + },
  504 +
  505 + previewSignature(e) {
  506 + console.log('previewSignature', e);
  507 + uni.previewImage({
  508 + urls: [e]
  509 + });
  510 + },
  511 +
  512 + // 签字确认
  513 + handleSignatureConfirm(signatureData) {
  514 + this.memberSignature = signatureData.dataUrl;
  515 + this.showSignatureModal = false;
  516 + // 恢复页面滚动
  517 + this.enablePageScroll();
  518 + uni.showToast({
  519 + title: '签字确认成功',
  520 + icon: 'success'
  521 + });
  522 + },
  523 +
  524 + handleSignatureClear() {
  525 + this.memberSignature = '';
  526 + },
  527 +
  528 + clearMemberSignature() {
  529 + this.memberSignature = '';
  530 + if (this.$refs.signaturePad) {
  531 + this.$refs.signaturePad.clearSignature();
  532 + }
  533 + uni.showToast({
  534 + title: '签字已清除',
  535 + icon: 'success'
  536 + });
  537 + },
  538 +
  539 + // 全屏签字相关方法
  540 + openSignatureModal() {
  541 + this.showSignatureModal = true;
  542 + // 禁止页面滚动
  543 + this.disablePageScroll();
  544 + },
  545 +
  546 + closeSignatureModal() {
  547 + this.showSignatureModal = false;
  548 + // 恢复页面滚动
  549 + this.enablePageScroll();
  550 + },
  551 +
  552 + // 禁止页面滚动
  553 + disablePageScroll() {
  554 + // 获取当前滚动位置
  555 + this.scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
  556 + // 设置页面不可滚动
  557 + document.body.style.overflow = 'hidden';
  558 + document.body.style.position = 'fixed';
  559 + document.body.style.width = '100%';
  560 + document.body.style.top = `-${this.scrollTop}px`;
  561 + },
  562 +
  563 + // 恢复页面滚动
  564 + enablePageScroll() {
  565 + // 恢复页面滚动
  566 + document.body.style.overflow = '';
  567 + document.body.style.position = '';
  568 + document.body.style.width = '';
  569 + document.body.style.top = '';
  570 + // 恢复滚动位置
  571 + if (this.scrollTop !== undefined) {
  572 + window.scrollTo(0, this.scrollTop);
  573 + }
  574 + },
  575 +
  576 + clearSignatureModal() {
  577 + if (this.$refs.signaturePadModal) {
  578 + this.$refs.signaturePadModal.clearSignature();
  579 + }
  580 + },
  581 +
  582 + confirmSignatureModal() {
  583 + if (this.$refs.signaturePadModal) {
  584 + this.$refs.signaturePadModal.confirmSignature();
  585 + }
  586 + },
  587 + async getpxqtlist(list){
  588 + for(let i = 0; i < list.length; i++){
  589 + list[i].projectNumber = list[i].originalProjectNumber ;
  590 + let px = list[i].px;
  591 + const detailResult = await lxApi.getPxDetail(px);
  592 + list[i].qt2 = detailResult.data.qt2 || "";
  593 + // list[i].sgf = detailResult.data.sgf || 0;
  594 + list[i].healthCoachLaborCost = detailResult.data.healthCoachLaborCost || 0;
  595 + list[i].techBeautyLaborCost = detailResult.data.techBeautyLaborCost || 0;
  596 + list[i].isAllowAccompanied = detailResult.data.isAllowAccompanied || 0;
  597 + list[i].beautyType = detailResult.data.beautyType || '';
  598 + // 初始化陪同相关字段
  599 + if (!list[i].accompaniedJksList) {
  600 + list[i].accompaniedJksList = [];
  601 + }
  602 + let jkslist = []
  603 + let kjblist = []
  604 + let accompaniedJksList = []
  605 + list[i].lqXhJksyjList.forEach(jks => {
  606 + jks.kdpxNumber = jks.originalKdpxNumber;
  607 + jks.laborCost = jks.originalLaborCost;
  608 + if(jks.isAccompanied == 0){
  609 + jkslist.push(jks);
  610 + } else {
  611 + accompaniedJksList.push(jks);
  612 + }
  613 + });
  614 + list[i].lqXhKjbsyjList.forEach(kjb => {
  615 + kjb.kdpxNumber = kjb.originalKdpxNumber;
  616 + kjb.laborCost = kjb.originalLaborCost;
  617 + kjblist.push(kjb);
  618 + });
  619 + list[i].lqXhJksyjList = jkslist;
  620 + list[i].lqXhKjbsyjList = kjblist;
  621 + list[i].accompaniedJksList = accompaniedJksList;
  622 + }
  623 + this.pxList = list;
  624 + console.log(this.pxList);
  625 + this.calculateTotalAmounts();
  626 + this.$forceUpdate();
  627 + },
  628 + // 初始化页面
  629 + async initializePage(options) {
  630 + try {
  631 + // 获取用户信息
  632 + this.userInfo = uni.getStorageSync('userInfo');
  633 + if (!this.userInfo || Object.keys(this.userInfo).length === 0) {
  634 + uni.showToast({
  635 + title: '请先登录',
  636 + icon: 'none'
  637 + });
  638 + setTimeout(() => {
  639 + uni.reLaunch({
  640 + url: '/pages/login/login'
  641 + });
  642 + }, 1500);
  643 + return;
  644 + }
  645 + if (options.id) {
  646 + this.removeid = options.id;
  647 + this.API.getConsumeDetail(options.id).then(res => {
  648 + this.removeinfo = res.data;
  649 + this.formData.hy = res.data.hymc;
  650 + this.selectedValues.hy = res.data.hy;
  651 + this.formData.xfje = res.data.xfje;
  652 + this.formData.sgfy = res.data.sgfy;
  653 + this.formData.overtimeCoefficient = res.data.overtimeCoefficient;
  654 + this.getpxqtlist(res.data.lqXhPxmxList)
  655 + let hyqz = res.data.signatureFile ? JSON.parse(res.data.signatureFile) : [];
  656 + this.memberSignature = hyqz.length > 0 ? hyqz[0].url : '';
  657 +
  658 + });
  659 + } else {
  660 + // 设置默认日期为当前日期
  661 + this.formData.hksj = this.utils.gettime().substring(0, 10);
  662 + // 添加默认的品项行
  663 + this.addPxRow();
  664 + }
  665 +
  666 + // 初始化健康师和科技部老师数据
  667 + await this.loadInitialOptions();
  668 +
  669 +
  670 + } catch (error) {
  671 + console.error('页面初始化失败:', error);
  672 + uni.showToast({
  673 + title: '页面初始化失败',
  674 + icon: 'none'
  675 + });
  676 + }
  677 + },
  678 +
  679 + // 加载初始选项数据
  680 + async loadInitialOptions() {
  681 + try {
  682 + // 并行加载健康师和科技部老师数据
  683 + const [jksResult, kjbResult] = await Promise.all([
  684 + this.getJksOptions(1, ''),
  685 + this.getKjbOptions(1, '')
  686 + ]);
  687 +
  688 + console.log('健康师数据加载完成:', this.jksOptions.length);
  689 + console.log('科技部老师数据加载完成:', this.kjbOptions.length);
  690 + } catch (error) {
  691 + console.error('加载初始选项数据失败:', error);
  692 + }
  693 + this.API.getLqMdxx(this.userInfo.mdid).then(res => {
  694 + this.mdxx = res.data;
  695 + });
  696 + },
  697 +
  698 + // 打开选择弹窗
  699 + async openSelectModal(fieldId) {
  700 + this.currentSelectField = fieldId;
  701 + this.showModal = true;
  702 + this.modalTitle = '加载中...';
  703 + this.modalLoading = true;
  704 + this.currentPage = 1;
  705 + this.hasMoreData = true;
  706 + this.searchKeyword = '';
  707 + this.currentOptions = [];
  708 +
  709 + try {
  710 + // 设置搜索参数
  711 + switch (fieldId) {
  712 + case 'hy':
  713 + this.searchParam = 'khmc';
  714 + this.modalTitle = '选择会员';
  715 + break;
  716 + case 'px':
  717 + this.searchParam = 'pxmc';
  718 + this.modalTitle = '选择品项';
  719 + break;
  720 + case 'jks':
  721 + this.searchParam = 'jksxm';
  722 + this.modalTitle = '选择健康师';
  723 + break;
  724 + case 'kjb':
  725 + this.searchParam = 'kjblsxm';
  726 + this.modalTitle = '选择科技部人员';
  727 + break;
  728 + }
  729 +
  730 + await this.loadOptionsData(fieldId, 1);
  731 + } catch (error) {
  732 + console.error('获取选项数据失败:', error);
  733 + this.modalTitle = '加载失败';
  734 + this.currentOptions = [];
  735 + uni.showToast({
  736 + title: '数据加载失败,请检查网络连接',
  737 + icon: 'none'
  738 + });
  739 + } finally {
  740 + this.modalLoading = false;
  741 + }
  742 + },
  743 +
  744 + // 关闭弹窗
  745 + closeModal() {
  746 + this.showModal = false;
  747 + this.currentSelectField = '';
  748 + this.currentOptions = [];
  749 + this.modalLoading = false;
  750 + this.hasMoreData = true;
  751 + this.currentPage = 1;
  752 + this.searchKeyword = '';
  753 + this.isAccompaniedMode = false;
  754 + },
  755 +
  756 + // 加载选项数据
  757 + async loadOptionsData(fieldId, page = 1, searchKeyword = '') {
  758 + let options = [];
  759 +
  760 + switch (fieldId) {
  761 + case 'hy':
  762 + options = await this.getMemberOptions(page, searchKeyword);
  763 + break;
  764 + case 'px':
  765 + this.hasMoreData = false;
  766 + options = await this.getPxOptions(page, searchKeyword);
  767 + break;
  768 + case 'jks':
  769 + options = await this.getJksOptions(page, searchKeyword);
  770 + break;
  771 + case 'kjb':
  772 + options = await this.getKjbOptions(page, searchKeyword);
  773 + break;
  774 + }
  775 +
  776 + // 为每个选项添加全局唯一的 key
  777 + options = options.map((option, index) => ({
  778 + ...option,
  779 + uniqueKey: `${fieldId}_${page}_${index}_${Date.now()}`
  780 + }));
  781 +
  782 + if (page === 1) {
  783 + this.currentOptions = options;
  784 + } else {
  785 + this.currentOptions = [...this.currentOptions, ...options];
  786 + }
  787 +
  788 + // 检查是否还有更多数据
  789 + if (options.length < this.pageSize) {
  790 + this.hasMoreData = false;
  791 + }
  792 + },
  793 +
  794 + // 处理弹窗确认
  795 + handleModalConfirm(selectedOption) {
  796 + if (this.currentSelectField && selectedOption) {
  797 + if (this.currentSelectField === 'px') {
  798 + // 处理品项选择
  799 + this.handlePxSelection(selectedOption);
  800 + } else if (this.currentSelectField === 'jks') {
  801 + // 处理健康师选择
  802 + if (this.isAccompaniedMode) {
  803 + // 陪同健康师选择
  804 + this.handleAccompaniedJksSelection(selectedOption);
  805 + } else {
  806 + // 普通健康师选择
  807 + this.handleJksSelection(selectedOption);
  808 + }
  809 + } else if (this.currentSelectField === 'kjb') {
  810 + // 处理科技部老师选择
  811 + this.handleKjbSelection(selectedOption);
  812 + } else if (this.currentSelectField === 'hy') {
  813 + // 处理会员选择
  814 + this.formData.hy = selectedOption.label;
  815 + this.selectedValues.hy = selectedOption.value;
  816 + // 补充会员相关信息
  817 + this.formData.hyzh = selectedOption.value;
  818 + this.formData.hymc = selectedOption.label;
  819 + this.formData.gklx = selectedOption.khlx || '';
  820 + }
  821 + }
  822 + this.closeModal();
  823 + },
  824 +
  825 + // 处理品项选择
  826 + async handlePxSelection(selectedOption) {
  827 + if (this.currentRowIndex >= 0) {
  828 + try {
  829 + // console.error(selectedOption)
  830 + // // 先校验已经选择的品项有没有这个
  831 + // const existingIndex = this.pxList.findIndex((item, index) =>
  832 + // index !== this.currentRowIndex && item.BillingItemId === selectedOption.BillingItemId
  833 + // );
  834 + // if (existingIndex !== -1) {
  835 + // uni.showToast({
  836 + // title: '该品项已存在,请勿重复添加',
  837 + // icon: 'none',
  838 + // duration: 2000
  839 + // });
  840 + // this.closeModal();
  841 + // return;
  842 + // }
  843 +
  844 + // 请求品项详细信息
  845 + const detailResult = await lxApi.getPxDetail(selectedOption.px);
  846 + let qt2 = "";
  847 + let sgf = 0;
  848 + let techBeautyLaborCost = 0
  849 + let healthCoachLaborCost = 0
  850 + let isAllowAccompanied = 0;
  851 +
  852 + if (detailResult.code === 200 && detailResult.data) {
  853 + qt2 = detailResult.data.qt2 || "";
  854 + sgf = detailResult.data.sgf || 0;
  855 + healthCoachLaborCost = detailResult.data.healthCoachLaborCost || 0;
  856 + techBeautyLaborCost = detailResult.data.techBeautyLaborCost || 0;
  857 + isAllowAccompanied = detailResult.data.isAllowAccompanied || 0;
  858 + }
  859 +
  860 + this.pxList[this.currentRowIndex] = {
  861 + ...this.pxList[this.currentRowIndex],
  862 + px: selectedOption.px,
  863 + pxmc: selectedOption.pxmc,
  864 + pxjg: selectedOption.ItemPrice || 0,
  865 + memberId: this.selectedValues.hy || "",
  866 + sourceType: selectedOption.sourceType || "",
  867 + totalPrice: (selectedOption.ItemPrice || 0) * (this.pxList[this.currentRowIndex]
  868 + .projectNumber || 1),
  869 + qt2: qt2, // 从接口获取的qt2字段
  870 + sgf: sgf, // 从接口获取的手工费
  871 + isAllowAccompanied: isAllowAccompanied, // 是否允许陪同
  872 + ItemName: selectedOption.pxmc,
  873 + ItemPrice: selectedOption.ItemPrice || 0,
  874 + TotalPurchased: selectedOption.TotalPurchased || 0,
  875 + ConsumedCount: selectedOption.ConsumedCount || 0,
  876 + RemainingCount: selectedOption.RemainingCount || 0,
  877 + BillingItemId: selectedOption.BillingItemId,
  878 + // 陪同相关字段
  879 + accompaniedJksList: [],// 陪同健康师列表
  880 + techBeautyLaborCost:techBeautyLaborCost,
  881 + healthCoachLaborCost:healthCoachLaborCost,
  882 + beautyType:detailResult.data.beautyType || '',
  883 + };
  884 +
  885 + // 品项修改时,清空健康师和科技部老师列表
  886 + this.pxList[this.currentRowIndex].lqXhJksyjList = [];
  887 + this.pxList[this.currentRowIndex].lqXhKjbsyjList = [];
  888 + // 清空陪同健康师列表
  889 + this.pxList[this.currentRowIndex].accompaniedJksList = [];
  890 +
  891 + this.calculateTotalAmounts();
  892 +
  893 + // 如果是医美品项,自动选择T区健康师并分配全部业绩
  894 + if (qt2 === '医美') {
  895 + this.$nextTick(() => {
  896 + this.handleYimeiJksAutoSelection(this.currentRowIndex);
  897 + });
  898 + }
  899 +
  900 + } catch (error) {
  901 + console.error('获取品项详情失败:', error);
  902 + uni.showToast({
  903 + title: '获取品项详情失败,请重试',
  904 + icon: 'none'
  905 + });
  906 + }
  907 + }
  908 + },
  909 +
  910 + // 处理健康师选择
  911 + async handleJksSelection(selectedOption) {
  912 + if (this.currentRowIndex >= 0) {
  913 + if (!this.pxList[this.currentRowIndex].lqXhJksyjList) {
  914 + this.pxList[this.currentRowIndex].lqXhJksyjList = [];
  915 + }
  916 +
  917 + const px = this.pxList[this.currentRowIndex];
  918 + const jksItem = {
  919 + jks: selectedOption.jks,
  920 + jksxm: selectedOption.jksxm,
  921 + jkszh: selectedOption.jkszh,
  922 + jksyj: "",
  923 + jsjId: "",
  924 + kdpxid: px.BillingItemId || px.px,
  925 + laborCost: 0, // 初始为0,后续通过均分计算
  926 + kdpxNumber: 0, // 初始为0,后续通过均分计算
  927 + isAccompanied: 0, // 是否陪同,默认0
  928 + accompaniedProjectNumber: 0 // 陪同次数,默认0
  929 + };
  930 +
  931 + this.pxList[this.currentRowIndex].lqXhJksyjList.push(jksItem);
  932 +
  933 + // 重新分配健康师的次数和手工费
  934 + this.redistributeJksNumbersAndLaborCost(this.currentRowIndex);
  935 +
  936 + // 如果是医美品项,重新分配健康师业绩
  937 + if (px.qt2 === '医美') {
  938 + this.$nextTick(() => {
  939 + // this.handleYimeiJksDistribution(this.currentRowIndex);
  940 + });
  941 + } else {
  942 + // 非医美品项,如果有健康师和科技部老师,自动均分业绩
  943 + this.$nextTick(() => {
  944 + this.distributePerformance(this.currentRowIndex);
  945 + });
  946 + }
  947 +
  948 + // 异步获取金三角信息
  949 + this.getJsjInfoByUserId(selectedOption.value, (jsjId, jsjName) => {
  950 + jksItem.jsjId = jsjId;
  951 + });
  952 + }
  953 + },
  954 +
  955 + // 处理科技部老师选择
  956 + handleKjbSelection(selectedOption) {
  957 + if (this.currentRowIndex >= 0) {
  958 + if (!this.pxList[this.currentRowIndex].lqXhKjbsyjList) {
  959 + this.pxList[this.currentRowIndex].lqXhKjbsyjList = [];
  960 + }
  961 +
  962 + const px = this.pxList[this.currentRowIndex];
  963 + const kjbItem = {
  964 + kjbls: selectedOption.kjbls,
  965 + kjblsxm: selectedOption.kjblsxm,
  966 + kjblszh: selectedOption.kjblszh,
  967 + kjblsyj: "",
  968 + hkpxid: px.BillingItemId || px.px,
  969 + laborCost: 0, // 初始为0,后续通过均分计算
  970 + hdpxNumber: 0 // 初始为0,后续通过均分计算
  971 + };
  972 +
  973 + this.pxList[this.currentRowIndex].lqXhKjbsyjList.push(kjbItem);
  974 +
  975 + // 重新分配科技部老师的次数和手工费
  976 + this.redistributeKjbNumbersAndLaborCost(this.currentRowIndex);
  977 +
  978 + // 如果有健康师和科技部老师,自动均分业绩
  979 + this.$nextTick(() => {
  980 + this.distributePerformance(this.currentRowIndex);
  981 + });
  982 + }
  983 + },
  984 +
  985 + // 处理加载更多
  986 + async handleLoadMore(page) {
  987 + if (this.currentSelectField && this.hasMoreData && !this.modalLoading) {
  988 + this.modalLoading = true;
  989 + try {
  990 + await this.loadOptionsData(this.currentSelectField, page, this.searchKeyword);
  991 + } catch (error) {
  992 + console.error('加载更多数据失败:', error);
  993 + uni.showToast({
  994 + title: '加载失败',
  995 + icon: 'none'
  996 + });
  997 + } finally {
  998 + this.modalLoading = false;
  999 + }
  1000 + }
  1001 + },
  1002 +
  1003 + // 处理刷新
  1004 + async handleRefresh() {
  1005 + if (this.currentSelectField) {
  1006 + this.currentPage = 1;
  1007 + this.hasMoreData = true;
  1008 + this.searchKeyword = '';
  1009 + await this.loadOptionsData(this.currentSelectField, 1);
  1010 + }
  1011 + },
  1012 +
  1013 + // 处理搜索
  1014 + async handleSearch(searchKeyword) {
  1015 + if (this.currentSelectField) {
  1016 + this.searchKeyword = searchKeyword;
  1017 + this.currentPage = 1;
  1018 + this.hasMoreData = true;
  1019 + this.modalLoading = true;
  1020 +
  1021 + try {
  1022 + await this.loadOptionsData(this.currentSelectField, 1, searchKeyword);
  1023 + } catch (error) {
  1024 + console.error('搜索失败:', error);
  1025 + uni.showToast({
  1026 + title: '搜索失败',
  1027 + icon: 'none'
  1028 + });
  1029 + } finally {
  1030 + this.modalLoading = false;
  1031 + }
  1032 + }
  1033 + },
  1034 +
  1035 + // 获取会员选项
  1036 + async getMemberOptions(page = 1, searchKeyword = '') {
  1037 + try {
  1038 + const params = {
  1039 + currentPage: page,
  1040 + pageSize: this.pageSize,
  1041 + };
  1042 +
  1043 + if (searchKeyword) {
  1044 + params.keyword = searchKeyword;
  1045 + }
  1046 +
  1047 + // 添加跨店参数
  1048 + if (!this.isCrossStore) {
  1049 + if(this.userInfo && this.userInfo.mdid) {
  1050 + params.gsmd = this.userInfo.mdid;
  1051 + } else{
  1052 + params.gsmd = '暂无';
  1053 + }
  1054 + }
  1055 +
  1056 + const result = await memberApi.getMemberList(params);
  1057 + if (result.code === 200 && result.data) {
  1058 + return result.data.list.map((item, index) => ({
  1059 + value: item.id,
  1060 + label: item.khmc,
  1061 + sjh: item.sjh,
  1062 + khlx: item.khlx,
  1063 + khlxName: item.khlxName,
  1064 + subtitle: '客户类型:' + (item.khlxName || '无') + ';手机号:' + (item.sjh || '无') +
  1065 + ';健康师:' + (item.mrsName || '无') + ';门店:' + (item.gsmdName || '无') + ';',
  1066 + }));
  1067 + }
  1068 + return [];
  1069 + } catch (error) {
  1070 + console.error('获取会员列表出错:', error);
  1071 + return [];
  1072 + }
  1073 + },
  1074 +
  1075 + // 添加品项行
  1076 + addPxRow() {
  1077 + this.pxList.push({
  1078 + px: "",
  1079 + pxmc: "",
  1080 + pxjg: 0,
  1081 + memberId: "",
  1082 + projectNumber: 1,
  1083 + sourceType: "购买",
  1084 + totalPrice: 0,
  1085 + lqXhJksyjList: [],
  1086 + lqXhKjbsyjList: [],
  1087 + qt2: "",
  1088 + isAllowAccompanied: 0,
  1089 + accompaniedJksList: []
  1090 + });
  1091 + },
  1092 +
  1093 + // 删除品项行
  1094 + deletePxRow(rowIndex) {
  1095 + if (this.pxList.length > 1) {
  1096 + this.pxList.splice(rowIndex, 1);
  1097 + this.calculateTotalAmounts();
  1098 + } else {
  1099 + uni.showToast({
  1100 + title: '至少需要保留一个品项',
  1101 + icon: 'none'
  1102 + });
  1103 + }
  1104 + },
  1105 +
  1106 + // 选择品项
  1107 + async selectPx(rowIndex) {
  1108 + this.currentRowIndex = rowIndex;
  1109 + this.openSelectModal('px');
  1110 + },
  1111 +
  1112 + // 获取品项选项(从会员剩余品项API获取)
  1113 + async getPxOptions(page = 1, searchKeyword = '') {
  1114 + if (!this.selectedValues.hy) {
  1115 + console.warn("请先选择会员");
  1116 + return [];
  1117 + }
  1118 +
  1119 + try {
  1120 + const params = {
  1121 + memberId: this.selectedValues.hy
  1122 + };
  1123 +
  1124 + if (searchKeyword) {
  1125 + params.xmmc = searchKeyword;
  1126 + }
  1127 +
  1128 + const result = await lxApi.getMemberRemainingItems(params);
  1129 + if (result.code === 200 && result.data && result.data.RemainingItems) {
  1130 + return result.data.RemainingItems.map(item => ({
  1131 + value: item.BillingItemId,
  1132 + label: item.ItemName,
  1133 + px: item.ItemId,
  1134 + pxmc: item.ItemName,
  1135 + pxjg: item.ItemPrice || 0,
  1136 + qt2: item.qt2 || "",
  1137 + RemainingCount: item.RemainingCount || 0,
  1138 + ItemPrice: item.ItemPrice || 0,
  1139 + sgf: 0,
  1140 + sourceType: item.SourceType || "购买",
  1141 + TotalPurchased: item.TotalPurchased || 0,
  1142 + ConsumedCount: item.ConsumedCount || 0,
  1143 + BillingItemId: item.BillingItemId,
  1144 + subtitle: '剩余: ' + (item.RemainingCount || 0) + ';类型:' + item.SourceType +
  1145 + ';单价:' + item.ItemPrice +';备注:' + (item.Remark || '无')+ ';'
  1146 + }));
  1147 + }
  1148 + return [];
  1149 + } catch (error) {
  1150 + console.error('获取会员剩余品项出错:', error);
  1151 + return [];
  1152 + }
  1153 + },
  1154 +
  1155 + // 更新品项次数
  1156 + updatePxNumber(rowIndex, event) {
  1157 + const value = event.detail.value;
  1158 + if (this.pxList[rowIndex]) {
  1159 + let inputNumber = parseInt(value) || 1;
  1160 +
  1161 + // 验证不能超过剩余次数
  1162 + if (inputNumber > this.pxList[rowIndex].RemainingCount) {
  1163 + inputNumber = this.pxList[rowIndex].RemainingCount;
  1164 + // 更新输入框显示的值
  1165 + this.$nextTick(() => {
  1166 + const inputElement = event.target;
  1167 + if (inputElement) {
  1168 + inputElement.value = inputNumber;
  1169 + }
  1170 + });
  1171 + }
  1172 +
  1173 + this.pxList[rowIndex].projectNumber = inputNumber;
  1174 + this.pxList[rowIndex].totalPrice = this.pxList[rowIndex].pxjg * this.pxList[rowIndex].projectNumber;
  1175 +
  1176 +
  1177 + // 重新分配健康师和科技部老师的次数和手工费
  1178 + this.redistributeJksNumbersAndLaborCost(rowIndex);
  1179 + this.redistributeKjbNumbersAndLaborCost(rowIndex);
  1180 +
  1181 + // 如果是医美品项,更新T区健康师业绩
  1182 + if (this.pxList[rowIndex].qt2 === '医美') {
  1183 + this.updateYimeiJksDistribution(rowIndex);
  1184 + } else {
  1185 + // 非医美品项,如果有健康师和科技部老师,自动均分业绩
  1186 + this.distributePerformance(rowIndex);
  1187 + }
  1188 + }
  1189 + },
  1190 +
  1191 + // 计算总消费金额和手工费用
  1192 + calculateTotalAmounts() {
  1193 + let totalXfje = 0;
  1194 + let totalSgfy = 0;
  1195 +
  1196 + this.pxList.forEach(px => {
  1197 + if (px.px && px.pxmc && px.pxjg && px.projectNumber) {
  1198 + const pxTotal = px.pxjg * px.projectNumber;
  1199 + totalXfje += pxTotal;
  1200 + }
  1201 + if (px.px && px.pxmc && px.projectNumber) {
  1202 + // totalSgfy += px.sgf * px.projectNumber;
  1203 + if (px.qt2 === '科美' && px.beautyType != 'cell') {
  1204 + totalSgfy += px.techBeautyLaborCost * px.projectNumber;
  1205 + } else if(px.qt2 === '科美' && px.beautyType == 'cell'){
  1206 + if(px.lqXhKjbsyjList.length > 0){
  1207 + totalSgfy += px.techBeautyLaborCost * px.projectNumber;
  1208 + } else {
  1209 + totalSgfy += px.healthCoachLaborCost * px.projectNumber;
  1210 + }
  1211 + } else {
  1212 + totalSgfy += px.healthCoachLaborCost * px.projectNumber;
  1213 + }
  1214 + }
  1215 + });
  1216 +
  1217 + this.formData.xfje = totalXfje.toFixed(2);
  1218 + this.formData.sgfy = totalSgfy.toFixed(2);
  1219 + },
  1220 +
  1221 + // 选择品项健康师
  1222 + async selectPxJks(rowIndex) {
  1223 + this.currentRowIndex = rowIndex;
  1224 + this.currentJksIndex = this.pxList[rowIndex].lqXhJksyjList.length;
  1225 + this.isAccompaniedMode = false;
  1226 + this.openSelectModal('jks');
  1227 + },
  1228 +
  1229 + // 选择品项科技部老师
  1230 + async selectPxKjb(rowIndex) {
  1231 + this.currentRowIndex = rowIndex;
  1232 + this.currentKjbIndex = this.pxList[rowIndex].lqXhKjbsyjList.length;
  1233 + this.openSelectModal('kjb');
  1234 + },
  1235 +
  1236 + // 选择陪同健康师
  1237 + async selectAccompaniedJks(rowIndex) {
  1238 + this.currentRowIndex = rowIndex;
  1239 + this.isAccompaniedMode = true;
  1240 + this.openSelectModal('jks');
  1241 + },
  1242 +
  1243 + // 处理陪同健康师选择
  1244 + async handleAccompaniedJksSelection(selectedOption) {
  1245 + if (this.currentRowIndex >= 0) {
  1246 + if (!this.pxList[this.currentRowIndex].accompaniedJksList) {
  1247 + this.pxList[this.currentRowIndex].accompaniedJksList = [];
  1248 + }
  1249 +
  1250 + const px = this.pxList[this.currentRowIndex];
  1251 + const accompaniedJksItem = {
  1252 + jks: selectedOption.jks,
  1253 + jksxm: selectedOption.jksxm,
  1254 + jkszh: selectedOption.jkszh,
  1255 + jksyj: 0, // 业绩默认为0,不参与逻辑
  1256 + jsjId: "",
  1257 + kdpxid: px.BillingItemId || px.px,
  1258 + laborCost: 0, // 手工费默认为0,不参与逻辑
  1259 + kdpxNumber: 0, // 次数默认为0,不参与逻辑
  1260 + isAccompanied: 1, // 是否陪同,默认为1
  1261 + accompaniedProjectNumber: 1 // 陪同次数,需要手动填写
  1262 + };
  1263 +
  1264 + this.pxList[this.currentRowIndex].accompaniedJksList.push(accompaniedJksItem);
  1265 + this.isAccompaniedMode = false;
  1266 + this.$forceUpdate();
  1267 +
  1268 + // 异步获取金三角信息
  1269 + this.getJsjInfoByUserId(selectedOption.value, (jsjId, jsjName) => {
  1270 + accompaniedJksItem.jsjId = jsjId;
  1271 + });
  1272 + }
  1273 + },
  1274 +
  1275 + // 删除陪同健康师
  1276 + removeAccompaniedJks(rowIndex, accompaniedIndex) {
  1277 + if (this.pxList[rowIndex].accompaniedJksList) {
  1278 + this.pxList[rowIndex].accompaniedJksList.splice(accompaniedIndex, 1);
  1279 + }
  1280 + this.$forceUpdate();
  1281 + },
  1282 +
  1283 + // 更新陪同健康师字段
  1284 + updateAccompaniedJksField(rowIndex, accompaniedIndex, field, event) {
  1285 + const value = event.detail.value;
  1286 + if (this.pxList[rowIndex].accompaniedJksList && this.pxList[rowIndex].accompaniedJksList[accompaniedIndex]) {
  1287 + if (field === 'accompaniedProjectNumber' || field === 'isAccompanied') {
  1288 + this.pxList[rowIndex].accompaniedJksList[accompaniedIndex][field] = parseInt(value) || 0;
  1289 + } else {
  1290 + this.pxList[rowIndex].accompaniedJksList[accompaniedIndex][field] = value;
  1291 + }
  1292 + }
  1293 + this.$forceUpdate();
  1294 + },
  1295 +
  1296 + // 获取健康师选项
  1297 + async getJksOptions(page = 1, searchKeyword = '') {
  1298 + try {
  1299 + const params = {
  1300 + currentPage: page,
  1301 + pageSize: this.pageSize,
  1302 + gw: '健康师',
  1303 + mdid: this.userInfo.mdid
  1304 + };
  1305 +
  1306 + if (searchKeyword) {
  1307 + params.jksxm = searchKeyword;
  1308 + }
  1309 +
  1310 + const result = await appointmentApi.getHealthWorkerList(params);
  1311 + if (result.code === 200 && result.data) {
  1312 + const options = result.data.list.map((item, index) => ({
  1313 + value: item.id,
  1314 + label: item.realName || `健康师${index + 1}`,
  1315 + fullName: item.realName || `健康师${index + 1}`,
  1316 + id: item.id,
  1317 + jks: item.id, // 添加jks字段
  1318 + jkszh: item.id,
  1319 + jksxm: item.realName || `健康师${index + 1}`,
  1320 + userName: item.userName || item.account || item.id,
  1321 + account: item.account || item.userName || item.id,
  1322 + subtitle: item.department || item.role || ''
  1323 + }));
  1324 +
  1325 + // 如果是第一页且没有搜索关键词,存储到jksOptions中
  1326 + if (page === 1 && !searchKeyword) {
  1327 + this.jksOptions = options;
  1328 + }
  1329 +
  1330 + return options;
  1331 + }
  1332 + return [];
  1333 + } catch (error) {
  1334 + console.error('获取健康师列表出错:', error);
  1335 + return [];
  1336 + }
  1337 + },
  1338 +
  1339 + // 获取科技部人员选项
  1340 + async getKjbOptions(page = 1, searchKeyword = '') {
  1341 + try {
  1342 + const params = {
  1343 + currentPage: page,
  1344 + pageSize: this.pageSize,
  1345 + gw: '科技老师' // 可以根据实际API调整筛选条件
  1346 + };
  1347 +
  1348 + if (searchKeyword) {
  1349 + params.kjblsxm = searchKeyword;
  1350 + }
  1351 +
  1352 + const result = await appointmentApi.getHealthWorkerList(params);
  1353 + if (result.code === 200 && result.data) {
  1354 + const options = result.data.list.map((item, index) => ({
  1355 + value: item.id,
  1356 + label: item.realName || `科技部人员${index + 1}`,
  1357 + fullName: item.realName || `科技部人员${index + 1}`,
  1358 + id: item.id,
  1359 + kjbls: item.id, // 添加kjbls字段
  1360 + kjblszh: item.id,
  1361 + kjblsxm: item.realName || `科技部人员${index + 1}`,
  1362 + userName: item.userName || item.account || item.id,
  1363 + account: item.account || item.userName || item.id,
  1364 + subtitle: item.department || item.role || ''
  1365 + }));
  1366 +
  1367 + // 如果是第一页且没有搜索关键词,存储到kjbOptions中
  1368 + if (page === 1 && !searchKeyword) {
  1369 + this.kjbOptions = options;
  1370 + }
  1371 +
  1372 + return options;
  1373 + }
  1374 + return [];
  1375 + } catch (error) {
  1376 + console.error('获取科技部人员列表出错:', error);
  1377 + return [];
  1378 + }
  1379 + },
  1380 +
  1381 + // 删除品项健康师
  1382 + removePxJks(rowIndex, jksIndex) {
  1383 + console.log('删除健康师:', rowIndex, jksIndex);
  1384 + if (this.pxList[rowIndex].lqXhJksyjList) {
  1385 + this.pxList[rowIndex].lqXhJksyjList.splice(jksIndex, 1);
  1386 +
  1387 + // 重新分配健康师的次数和手工费
  1388 + this.redistributeJksNumbersAndLaborCost(rowIndex);
  1389 +
  1390 + // 如果是医美品项,重新分配健康师业绩
  1391 + if (this.pxList[rowIndex].qt2 === '医美') {
  1392 + this.$nextTick(() => {
  1393 + this.handleYimeiJksDistribution(rowIndex);
  1394 + });
  1395 + } else {
  1396 + // 非医美品项,如果有健康师和科技部老师,自动均分业绩
  1397 + this.$nextTick(() => {
  1398 + this.distributePerformance(rowIndex);
  1399 + });
  1400 + }
  1401 + }
  1402 + this.$forceUpdate()
  1403 + },
  1404 +
  1405 + // 删除品项科技部老师
  1406 + removePxKjb(rowIndex, kjbIndex) {
  1407 + console.log('删除科技部老师:', rowIndex, kjbIndex);
  1408 + if (this.pxList[rowIndex].lqXhKjbsyjList) {
  1409 + this.pxList[rowIndex].lqXhKjbsyjList.splice(kjbIndex, 1);
  1410 +
  1411 + // 重新分配科技部老师的次数和手工费
  1412 + this.redistributeKjbNumbersAndLaborCost(rowIndex);
  1413 +
  1414 + // 如果有健康师和科技部老师,自动均分业绩
  1415 + this.$nextTick(() => {
  1416 + this.distributePerformance(rowIndex);
  1417 + });
  1418 + }
  1419 + this.$forceUpdate()
  1420 + },
  1421 +
  1422 + // 更新健康师字段
  1423 + updateJksField(rowIndex, jksIndex, field, event) {
  1424 + const value = event.detail.value;
  1425 + if (this.pxList[rowIndex].lqXhJksyjList && this.pxList[rowIndex].lqXhJksyjList[jksIndex]) {
  1426 + if (field === 'laborCost' || field === 'kdpxNumber') {
  1427 + this.pxList[rowIndex].lqXhJksyjList[jksIndex][field] = parseFloat(value) || 0;
  1428 + } else {
  1429 + this.pxList[rowIndex].lqXhJksyjList[jksIndex][field] = value;
  1430 + }
  1431 + }
  1432 + this.$forceUpdate()
  1433 + },
  1434 +
  1435 + // 更新科技部老师字段
  1436 + updateKjbField(rowIndex, kjbIndex, field, event) {
  1437 + const value = event.detail.value;
  1438 + if (this.pxList[rowIndex].lqXhKjbsyjList && this.pxList[rowIndex].lqXhKjbsyjList[kjbIndex]) {
  1439 + if (field === 'laborCost' || field === 'hdpxNumber') {
  1440 + this.pxList[rowIndex].lqXhKjbsyjList[kjbIndex][field] = parseFloat(value) || 0;
  1441 + } else {
  1442 + this.pxList[rowIndex].lqXhKjbsyjList[kjbIndex][field] = value;
  1443 + }
  1444 + }
  1445 + this.$forceUpdate()
  1446 + },
  1447 +
  1448 + // 处理表单提交
  1449 + handleFormSubmit(event) {
  1450 + event.preventDefault();
  1451 + this.submitConsume();
  1452 + },
  1453 +
  1454 + // 提交耗卡
  1455 + async submitConsume() {
  1456 + // 验证表单
  1457 + if (!this.selectedValues.hy) {
  1458 + uni.showToast({
  1459 + title: '请选择会员',
  1460 + icon: 'none'
  1461 + });
  1462 + return;
  1463 + }
  1464 +
  1465 + if(!this.validateForm()){
  1466 + return;
  1467 + }
  1468 + if (this.removeid) {
  1469 + // 检查是否有科美品项
  1470 + // const hasKemei = this.pxList.some(px => px.qt2 === '科美');
  1471 + // 过滤品项列表,只保留提交需要的字段
  1472 + const filteredPxList = this.pxList.map(px => ({
  1473 + billingItemId: px.BillingItemId || px.billingItemId,
  1474 + px: px.px,
  1475 + memberId: px.memberId,
  1476 + pxmc: px.pxmc,
  1477 + pxjg: px.pxjg,
  1478 + projectNumber: px.projectNumber,
  1479 + sourceType: px.sourceType,
  1480 + totalPrice: px.pxjg * px.projectNumber,
  1481 + lqXhJksyjList: [...px.lqXhJksyjList,...px.accompaniedJksList] || [],
  1482 + lqXhKjbsyjList: px.lqXhKjbsyjList || [],
  1483 + // accompaniedJksList: px.accompaniedJksList || []
  1484 + }));
  1485 + const formData = {
  1486 + ...this.removeinfo,
  1487 + xfje: this.formData.xfje,
  1488 + sgfy: this.formData.sgfy,
  1489 + lqXhPxmxList:filteredPxList.filter(px => px.px && px.pxmc),
  1490 + // overtimeCoefficient: this.formData.overtimeCoefficient || 0
  1491 + };
  1492 + console.log({...formData});
  1493 + // return;
  1494 + this.issubmitOrder = false;
  1495 + const result = await this.API.updateConsumeForNoDelete(formData)
  1496 +
  1497 + uni.hideLoading();
  1498 + if (result.code === 200) {
  1499 + uni.showToast({
  1500 + title: '修改成功!',
  1501 + icon: 'success'
  1502 + });
  1503 + this.clearForm();
  1504 + this.issubmitOrder = true;
  1505 + setTimeout(() => {
  1506 + // 返回上一页
  1507 + uni.navigateBack();
  1508 + }, 1000);
  1509 + } else {
  1510 + uni.showToast({
  1511 + title: result.msg || '提交失败,请重试',
  1512 + icon: 'none'
  1513 + });
  1514 + this.issubmitOrder = true;
  1515 + }
  1516 +
  1517 + } else {
  1518 + try {
  1519 + // 检查是否有科美品项
  1520 + const hasKemei = this.pxList.some(px => px.qt2 === '科美');
  1521 +
  1522 + // 过滤品项列表,只保留提交需要的字段
  1523 + const filteredPxList = this.pxList.map(px => ({
  1524 + billingItemId: px.BillingItemId,
  1525 + px: px.px,
  1526 + memberId: px.memberId,
  1527 + pxmc: px.pxmc,
  1528 + pxjg: px.pxjg,
  1529 + projectNumber: px.projectNumber,
  1530 + sourceType: px.sourceType,
  1531 + totalPrice: px.pxjg * px.projectNumber,
  1532 + lqXhJksyjList: [...px.lqXhJksyjList,...px.accompaniedJksList] || [],
  1533 + lqXhKjbsyjList: px.lqXhKjbsyjList || [],
  1534 + // accompaniedJksList: px.accompaniedJksList || []
  1535 + }));
  1536 +
  1537 + // 处理会员签字
  1538 + let hyqz = []
  1539 + if (this.memberSignature) {
  1540 + let memberinfo = await this.newUploadBase64Image()
  1541 + console.error(memberinfo)
  1542 + if (memberinfo) {
  1543 + hyqz.push({
  1544 + name: memberinfo.name,
  1545 + fileId: memberinfo.name,
  1546 + url: memberinfo.url
  1547 + });
  1548 + }
  1549 + }
  1550 +
  1551 + // 收集表单数据
  1552 + // 处理耗卡日期:如果用户设置了日期则使用设置的日期,否则使用当前时间
  1553 + let hksjValue = this.utils.gettime();
  1554 + if (this.formData.hksj) {
  1555 + // 如果设置了日期,则添加时分秒
  1556 + hksjValue = this.formData.hksj + ' ' + new Date().toTimeString().substring(0, 8);
  1557 + }
  1558 +
  1559 + const formData = {
  1560 + md: this.userInfo.mdid || "",
  1561 + "mdbh": this.userInfo.mdid,
  1562 + "mdmc": this.mdxx.dm,
  1563 + hy: this.selectedValues.hy,
  1564 + "hyzh": this.formData.hyzh,
  1565 + "hymc": this.formData.hymc,
  1566 + "gklx": this.formData.gklx,
  1567 + xfje: this.formData.xfje,
  1568 + sgfy: this.formData.sgfy,
  1569 + hksj: hksjValue,
  1570 + sfykjb: hasKemei ? "是" : "否", // 是否有科技部
  1571 + lqXhPxmxList: filteredPxList.filter(px => px.px && px.pxmc),
  1572 + signatureFile: JSON.stringify(hyqz),
  1573 + overtimeCoefficient: this.formData.overtimeCoefficient || 0
  1574 + };
  1575 +
  1576 + console.log("耗卡数据:", formData);
  1577 + // return
  1578 + uni.showLoading({
  1579 + title: '正在提交...'
  1580 + });
  1581 + this.issubmitOrder = false;
  1582 + // 调用实际的API
  1583 + const result = await consumeApi.submitConsume(formData);
  1584 +
  1585 + uni.hideLoading();
  1586 +
  1587 + if (result.code === 200) {
  1588 + uni.showToast({
  1589 + title: '耗卡成功!',
  1590 + icon: 'success'
  1591 + });
  1592 + this.clearForm();
  1593 + this.issubmitOrder = true;
  1594 + } else {
  1595 + uni.showToast({
  1596 + title: result.msg || '提交失败,请重试',
  1597 + icon: 'none'
  1598 + });
  1599 + this.issubmitOrder = true;
  1600 + }
  1601 +
  1602 + } catch (error) {
  1603 + uni.hideLoading();
  1604 + console.error('提交失败:', error);
  1605 + uni.showToast({
  1606 + title: '网络错误,请稍后重试',
  1607 + icon: 'none'
  1608 + });
  1609 + }
  1610 + }
  1611 +
  1612 +
  1613 +
  1614 + },
  1615 + validateForm() {
  1616 + if (this.pxList.length === 0) {
  1617 + uni.showToast({
  1618 + title: '请至少添加一个品项',
  1619 + icon: 'none'
  1620 + });
  1621 + return;
  1622 + }
  1623 +
  1624 + // 验证每个品项的信息
  1625 + for (let i = 0; i < this.pxList.length; i++) {
  1626 + const px = this.pxList[i];
  1627 +
  1628 + // 验证品项基本信息
  1629 + if (!px.px || !px.pxmc) {
  1630 + uni.showToast({
  1631 + title: `第${i + 1}个品项信息不完整,请重新选择`,
  1632 + icon: 'none'
  1633 + });
  1634 + return;
  1635 + }
  1636 +
  1637 + // 验证次数不能超过剩余次数
  1638 + if (px.RemainingCount && px.projectNumber > px.RemainingCount) {
  1639 + uni.showToast({
  1640 + title: `第${i + 1}个品项的次数(${px.projectNumber})不能超过剩余次数(${px.RemainingCount})`,
  1641 + icon: 'none'
  1642 + });
  1643 + return;
  1644 + }
  1645 +
  1646 + // 验证健康师(特殊处理:px为cell时,健康师和科技部老师至少选择一个)
  1647 + const isSpecialPx = px.beautyType == 'cell';
  1648 + // 过滤掉陪同健康师(isAccompanied为1的健康师不参与验证)
  1649 + const normalJksList = px.lqXhJksyjList ? px.lqXhJksyjList.filter(jks => !jks.isAccompanied || jks.isAccompanied === 0) : [];
  1650 + const hasJks = normalJksList.length > 0;
  1651 + const hasKjb = px.lqXhKjbsyjList && px.lqXhKjbsyjList.length > 0;
  1652 +
  1653 + if (isSpecialPx) {
  1654 + // px为cell时,健康师和科技部老师至少选择一个
  1655 + if (!hasJks && !hasKjb) {
  1656 + uni.showToast({
  1657 + // (px=${px.px})
  1658 + title: `第${i + 1}个品项必须至少选择一个健康师或科技部老师`,
  1659 + icon: 'none'
  1660 + });
  1661 + return;
  1662 + }
  1663 + } else {
  1664 + // 其他品项必须选择健康师
  1665 + if (!hasJks) {
  1666 + uni.showToast({
  1667 + title: `第${i + 1}个品项必须至少选择一个健康师`,
  1668 + icon: 'none'
  1669 + });
  1670 + return;
  1671 + }
  1672 + }
  1673 +
  1674 + // 计算健康师业绩总和(只有当选择了健康师时才进行验证)
  1675 + let jksTotalYj = 0;
  1676 + let jksTotalLaborCost = 0;
  1677 + let jksTotalNumber = 0;
  1678 +
  1679 + // 检查是否为医美品项
  1680 + const isYimei = px.qt2 === '医美';
  1681 + let tquJks = null; // T区健康师
  1682 + let otherJksList = []; // 其他健康师
  1683 +
  1684 + // 只有当选择了健康师时才进行健康师相关验证(排除陪同健康师)
  1685 + if (hasJks) {
  1686 + // 分离T区健康师和其他健康师(排除陪同健康师)
  1687 + for (let j = 0; j < normalJksList.length; j++) {
  1688 + const jks = normalJksList[j];
  1689 +
  1690 + // 验证健康师是否选择
  1691 + if (!jks.jks || !jks.jksxm) {
  1692 + uni.showToast({
  1693 + title: `第${i + 1}个品项的第${j + 1}个健康师必须选择`,
  1694 + icon: 'none'
  1695 + });
  1696 + return;
  1697 + }
  1698 +
  1699 + // 验证健康师业绩必须填写(医美品项的非T区健康师除外)
  1700 + const isTquJks = isYimei && jks.jksxm && jks.jksxm.includes('T区');
  1701 + const isNonTquJks = isYimei && !isTquJks;
  1702 +
  1703 + if (!isNonTquJks && (!jks.jksyj || jks.jksyj.trim() === "")) {
  1704 + uni.showToast({
  1705 + title: `第${i + 1}个品项的第${j + 1}个健康师业绩必须填写`,
  1706 + icon: 'none'
  1707 + });
  1708 + return;
  1709 + }
  1710 +
  1711 + // 验证业绩为数字(医美品项的非T区健康师可以为空或0)
  1712 + const yj = parseFloat(jks.jksyj || 0);
  1713 + if (isNaN(yj) || yj < 0) {
  1714 + uni.showToast({
  1715 + title: `第${i + 1}个品项的第${j + 1}个健康师业绩必须为有效数字`,
  1716 + icon: 'none'
  1717 + });
  1718 + return;
  1719 + }
  1720 +
  1721 + // 医美品项特殊处理:检查是否包含"T区"
  1722 + if (isYimei && jks.jksxm && jks.jksxm.includes('T区')) {
  1723 + tquJks = jks;
  1724 + } else {
  1725 + otherJksList.push(jks);
  1726 + }
  1727 +
  1728 + jksTotalYj += yj;
  1729 + jksTotalLaborCost += parseFloat(jks.laborCost) || 0;
  1730 + jksTotalNumber += parseInt(jks.kdpxNumber) || 0;
  1731 + }
  1732 +
  1733 + // 医美品项特殊验证
  1734 + if (isYimei) {
  1735 + const pxTotalAmount = px.pxjg * px.projectNumber;
  1736 + // const pxTotalLaborCost = (px.sgf || 0) * px.projectNumber;
  1737 +
  1738 + if (tquJks) {
  1739 + // 有T区健康师的情况
  1740 + // 验证T区健康师业绩等于品项总金额
  1741 + const tquYj = parseFloat(tquJks.jksyj);
  1742 + if (Math.abs(tquYj - pxTotalAmount) > 0.01) {
  1743 + uni.showToast({
  1744 + title: `第${i + 1}个品项是医美品项,T区健康师业绩(${tquYj.toFixed(2)})必须等于品项金额(${pxTotalAmount.toFixed(2)})`,
  1745 + icon: 'none'
  1746 + });
  1747 + return;
  1748 + }
  1749 +
  1750 + // 验证T区健康师次数和手工费等于品项总次数和总手工费
  1751 + // if (parseInt(tquJks.kdpxNumber) !== px.projectNumber) {
  1752 + // uni.showToast({
  1753 + // title: `第${i + 1}个品项是医美品项,T区健康师次数必须等于品项次数(${px.projectNumber})`,
  1754 + // icon: 'none'
  1755 + // });
  1756 + // return;
  1757 + // }
  1758 + // if (Math.abs(parseFloat(tquJks.laborCost) - pxTotalLaborCost) > 0.01) {
  1759 + // uni.showToast({
  1760 + // title: `第${i + 1}个品项是医美品项,T区健康师手工费必须等于品项手工费(${pxTotalLaborCost.toFixed(2)})`,
  1761 + // icon: 'none'
  1762 + // });
  1763 + // return;
  1764 + // }
  1765 +
  1766 + // 验证其他健康师业绩为0
  1767 + for (let k = 0; k < otherJksList.length; k++) {
  1768 + const otherJks = otherJksList[k];
  1769 + const otherYj = parseFloat(otherJks.jksyj);
  1770 + if (Math.abs(otherYj) > 0.01) {
  1771 + uni.showToast({
  1772 + title: `第${i + 1}个品项是医美品项,非T区健康师业绩必须为0`,
  1773 + icon: 'none'
  1774 + });
  1775 + return;
  1776 + }
  1777 + }
  1778 +
  1779 + // 验证其他健康师次数和手工费都为0
  1780 + for (let k = 0; k < otherJksList.length; k++) {
  1781 + const otherJks = otherJksList[k];
  1782 + if (parseInt(otherJks.kdpxNumber) !== 0) {
  1783 + uni.showToast({
  1784 + title: `第${i + 1}个品项是医美品项,非T区健康师次数必须为0`,
  1785 + icon: 'none'
  1786 + });
  1787 + return;
  1788 + }
  1789 + if (Math.abs(parseFloat(otherJks.laborCost)) > 0.01) {
  1790 + uni.showToast({
  1791 + title: `第${i + 1}个品项是医美品项,非T区健康师手工费必须为0`,
  1792 + icon: 'none'
  1793 + });
  1794 + return;
  1795 + }
  1796 + }
  1797 + } else {
  1798 + // 没有T区健康师的情况,按普通品项验证
  1799 + // if (Math.abs(jksTotalYj - pxTotalAmount) > 0.01) {
  1800 + // uni.showToast({
  1801 + // title: `第${i + 1}个品项的健康师业绩总和(${jksTotalYj.toFixed(2)})必须等于品项金额(${pxTotalAmount.toFixed(2)})`,
  1802 + // icon: 'none'
  1803 + // });
  1804 + // return;
  1805 + // }
  1806 + }
  1807 + } else {
  1808 + // 非医美品项,按原逻辑验证
  1809 + const pxTotalAmount = px.pxjg * px.projectNumber;
  1810 + // 如果同时有健康师和科技部老师,健康师组和科技老师组都获得全部业绩;否则健康师获得全部业绩
  1811 + const expectedJksAmount = pxTotalAmount; // 无论是否有科技部老师,健康师组都获得全部业绩
  1812 + // if (Math.abs(jksTotalYj - expectedJksAmount) > 0.01) {
  1813 + // uni.showToast({
  1814 + // title: `第${i + 1}个品项的健康师业绩总和(${jksTotalYj.toFixed(2)})必须等于品项金额(${pxTotalAmount.toFixed(2)})`,
  1815 + // icon: 'none'
  1816 + // });
  1817 + // return;
  1818 + // }
  1819 + }
  1820 + } // 结束健康师验证逻辑
  1821 +
  1822 + // 如果是科美品项,验证科技部老师(特殊处理:px为cell允许没有科技部老师)
  1823 + if (px.qt2 === '科美') {
  1824 + if (!isSpecialPx && (!px.lqXhKjbsyjList || px.lqXhKjbsyjList.length === 0)) {
  1825 + uni.showToast({
  1826 + title: `第${i + 1}个品项是科美品项,必须至少选择一个科技部老师`,
  1827 + icon: 'none'
  1828 + });
  1829 + return;
  1830 + }
  1831 +
  1832 + // 只有当选择了科技部老师时才进行科技部老师相关验证
  1833 + if (hasKjb) {
  1834 + let kjbTotalYj = 0;
  1835 + let kjbTotalLaborCost = 0;
  1836 + let kjbTotalNumber = 0;
  1837 + const pxTotalAmount = px.pxjg * px.projectNumber;
  1838 +
  1839 + for (let k = 0; k < px.lqXhKjbsyjList.length; k++) {
  1840 + const kjb = px.lqXhKjbsyjList[k];
  1841 +
  1842 + // 验证科技部老师是否选择
  1843 + if (!kjb.kjbls || !kjb.kjblsxm) {
  1844 + uni.showToast({
  1845 + title: `第${i + 1}个品项的第${k + 1}个科技部老师必须选择`,
  1846 + icon: 'none'
  1847 + });
  1848 + return;
  1849 + }
  1850 +
  1851 + // 验证科技部老师业绩必须填写
  1852 + if (!kjb.kjblsyj || kjb.kjblsyj.trim() === "") {
  1853 + uni.showToast({
  1854 + title: `第${i + 1}个品项的第${k + 1}个科技部老师业绩必须填写`,
  1855 + icon: 'none'
  1856 + });
  1857 + return;
  1858 + }
  1859 +
  1860 + // 验证业绩为数字
  1861 + const yj = parseFloat(kjb.kjblsyj);
  1862 + if (isNaN(yj) || yj < 0) {
  1863 + uni.showToast({
  1864 + title: `第${i + 1}个品项的第${k + 1}个科技部老师业绩必须为有效数字`,
  1865 + icon: 'none'
  1866 + });
  1867 + return;
  1868 + }
  1869 +
  1870 + kjbTotalYj += yj;
  1871 + kjbTotalLaborCost += parseFloat(kjb.laborCost) || 0;
  1872 + kjbTotalNumber += parseInt(kjb.hdpxNumber) || 0;
  1873 + }
  1874 +
  1875 + // 如果同时有健康师和科技部老师,科技老师组获得全部业绩;否则科技部老师获得全部业绩
  1876 + const expectedKjbAmount = pxTotalAmount; // 无论是否有健康师,科技老师组都获得全部业绩
  1877 + // if (Math.abs(kjbTotalYj - expectedKjbAmount) > 0.01) {
  1878 + // uni.showToast({
  1879 + // title: `第${i + 1}个品项的科技部老师业绩总和(${kjbTotalYj.toFixed(2)})必须等于品项金额(${pxTotalAmount.toFixed(2)})`,
  1880 + // icon: 'none'
  1881 + // });
  1882 + // return;
  1883 + // }
  1884 + } // 结束科技部老师验证逻辑
  1885 + } else if (hasKjb && hasJks) {
  1886 + // 非科美品项,但如果同时有健康师和科技部老师,也需要验证均分
  1887 + let kjbTotalYj = 0;
  1888 + const pxTotalAmount = px.pxjg * px.projectNumber;
  1889 +
  1890 + for (let k = 0; k < px.lqXhKjbsyjList.length; k++) {
  1891 + const kjb = px.lqXhKjbsyjList[k];
  1892 +
  1893 + // 验证科技部老师是否选择
  1894 + if (!kjb.kjbls || !kjb.kjblsxm) {
  1895 + uni.showToast({
  1896 + title: `第${i + 1}个品项的第${k + 1}个科技部老师必须选择`,
  1897 + icon: 'none'
  1898 + });
  1899 + return;
  1900 + }
  1901 +
  1902 + // 验证科技部老师业绩必须填写
  1903 + if (!kjb.kjblsyj || kjb.kjblsyj.trim() === "") {
  1904 + uni.showToast({
  1905 + title: `第${i + 1}个品项的第${k + 1}个科技部老师业绩必须填写`,
  1906 + icon: 'none'
  1907 + });
  1908 + return;
  1909 + }
  1910 +
  1911 + // 验证业绩为数字
  1912 + const yj = parseFloat(kjb.kjblsyj);
  1913 + if (isNaN(yj) || yj < 0) {
  1914 + uni.showToast({
  1915 + title: `第${i + 1}个品项的第${k + 1}个科技部老师业绩必须为有效数字`,
  1916 + icon: 'none'
  1917 + });
  1918 + return;
  1919 + }
  1920 +
  1921 + kjbTotalYj += yj;
  1922 + }
  1923 +
  1924 + // 验证科技部老师业绩总和等于品项金额(全部各自组内均分)
  1925 + const expectedKjbAmount = pxTotalAmount;
  1926 + // if (Math.abs(kjbTotalYj - expectedKjbAmount) > 0.01) {
  1927 + // uni.showToast({
  1928 + // title: `第${i + 1}个品项的科技部老师业绩总和(${kjbTotalYj.toFixed(2)})必须等于品项金额(${pxTotalAmount.toFixed(2)})`,
  1929 + // icon: 'none'
  1930 + // });
  1931 + // return;
  1932 + // }
  1933 + }
  1934 + }
  1935 + return true;
  1936 + },
  1937 + // 重新分配健康师的次数和手工费
  1938 + redistributeJksNumbersAndLaborCost(pxIndex) {
  1939 + const px = this.pxList[pxIndex];
  1940 + console.log('px:', px);
  1941 + if (!px.lqXhJksyjList || px.lqXhJksyjList.length === 0) {
  1942 + return;
  1943 + }
  1944 +
  1945 + // 过滤掉陪同健康师(isAccompanied为1的健康师不参与计算)
  1946 + const normalJksList = px.lqXhJksyjList.filter(jks => !jks.isAccompanied || jks.isAccompanied === 0);
  1947 +
  1948 + if (normalJksList.length === 0) {
  1949 + return;
  1950 + }
  1951 +
  1952 + // 如果是科美品项,健康师的次数和手工费都是0
  1953 + if (px.qt2 === '科美' && px.beautyType != 'cell') {
  1954 + normalJksList.forEach(jks => {
  1955 + jks.kdpxNumber = 0;
  1956 + jks.laborCost = 0;
  1957 + });
  1958 + return;
  1959 + }
  1960 + if (px.beautyType == 'cell' && px.lqXhKjbsyjList.length > 0) {
  1961 + normalJksList.forEach(jks => {
  1962 + jks.kdpxNumber = 0;
  1963 + jks.laborCost = 0;
  1964 + });
  1965 + return;
  1966 + }
  1967 +
  1968 + // 计算品项总次数和总手工费
  1969 + const totalNumber = px.projectNumber || 0;
  1970 + const totalLaborCost = (px.healthCoachLaborCost || 0) * totalNumber;
  1971 +
  1972 + // 健康师数量(排除陪同健康师)
  1973 + const jksCount = normalJksList.length;
  1974 +
  1975 + if (jksCount > 0) {
  1976 + // 次数和手工费都小数均分,保留两位小数
  1977 + const avgNumber = totalNumber / jksCount;
  1978 + const avgLaborCost = totalLaborCost / jksCount;
  1979 +
  1980 + normalJksList.forEach((jks, index) => {
  1981 + // 次数:小数均分,保留两位小数
  1982 + jks.kdpxNumber = parseFloat(avgNumber.toFixed(2));
  1983 + // 手工费:小数均分,保留两位小数
  1984 + jks.laborCost = avgLaborCost.toFixed(2);
  1985 + });
  1986 + }
  1987 + this.$forceUpdate()
  1988 + },
  1989 +
  1990 + // 重新分配科技部老师的次数和手工费
  1991 + redistributeKjbNumbersAndLaborCost(pxIndex) {
  1992 + this.calculateTotalAmounts();
  1993 + const px = this.pxList[pxIndex];
  1994 + // 计算品项总次数和总手工费
  1995 + const totalNumber = px.projectNumber || 0;
  1996 + const totalLaborCost = (px.techBeautyLaborCost || 0) * totalNumber;
  1997 + const totalLaborCostjks = (px.healthCoachLaborCost || 0) * totalNumber;
  1998 +
  1999 + // 科技部老师数量
  2000 + const kjbCount = px.lqXhKjbsyjList.length;
  2001 + const jksCount = px.lqXhJksyjList.length;
  2002 + if (!px.lqXhKjbsyjList || px.lqXhKjbsyjList.length === 0) {
  2003 + const avgNumberjks = totalNumber / jksCount;
  2004 + const avgLaborCostjks = totalLaborCostjks / jksCount;
  2005 + px.lqXhJksyjList.forEach((jks, index) => {
  2006 + jks.kdpxNumber = parseFloat(avgNumberjks.toFixed(2));
  2007 + jks.laborCost = avgLaborCostjks.toFixed(2);
  2008 + });
  2009 + return;
  2010 + }
  2011 +
  2012 + if (kjbCount > 0) {
  2013 + // 次数和手工费都小数均分,保留两位小数
  2014 + const avgNumber = totalNumber / kjbCount;
  2015 + const avgLaborCost = totalLaborCost / kjbCount;
  2016 +
  2017 + px.lqXhKjbsyjList.forEach((kjb, index) => {
  2018 + // 次数:小数均分,保留两位小数
  2019 + kjb.hdpxNumber = parseFloat(avgNumber.toFixed(2));
  2020 + // 手工费:小数均分,保留两位小数
  2021 + kjb.laborCost = avgLaborCost.toFixed(2);
  2022 + });
  2023 + px.lqXhJksyjList.forEach(jks => {
  2024 + jks.kdpxNumber = 0;
  2025 + jks.laborCost = 0;
  2026 + });
  2027 + }
  2028 + this.$forceUpdate()
  2029 + },
  2030 +
  2031 + // 医美品项自动选择T区健康师
  2032 + handleYimeiJksAutoSelection(pxIndex) {
  2033 + const px = this.pxList[pxIndex];
  2034 +
  2035 + if (px.qt2 !== '医美') {
  2036 + return;
  2037 + }
  2038 +
  2039 + // 检查健康师选项是否已加载
  2040 + if (!this.jksOptions || this.jksOptions.length === 0) {
  2041 + console.warn('健康师选项尚未加载,无法自动选择T区健康师');
  2042 + return;
  2043 + }
  2044 +
  2045 + // 查找T区健康师
  2046 + const tquJks = this.jksOptions.find(jks => jks.fullName && jks.fullName.includes('T区'));
  2047 +
  2048 + if (tquJks) {
  2049 + // 自动添加T区健康师
  2050 + const pxTotalAmount = px.pxjg * px.projectNumber;
  2051 + // const pxTotalLaborCost = (px.sgf || 0) * px.projectNumber;
  2052 +
  2053 + const jksItem = {
  2054 + "jks": tquJks.id,
  2055 + "jksxm": tquJks.fullName,
  2056 + "jkszh": tquJks.userName || tquJks.account || tquJks.id,
  2057 + "jksyj": pxTotalAmount.toFixed(2), // 全部业绩
  2058 + "jsjId": "",
  2059 + // "laborCost": pxTotalLaborCost.toFixed(2), // 全部手工费
  2060 + // "kdpxNumber": px.projectNumber, // 全部次数
  2061 + "laborCost": 0, // 全部手工费
  2062 + "kdpxNumber":0, // 全部次数
  2063 + "kdpxid": px.BillingItemId || px.px,
  2064 + "isAccompanied": 0, // 是否陪同,默认0
  2065 + "accompaniedProjectNumber": 0 // 陪同次数,默认0
  2066 + };
  2067 +
  2068 + // 使用Vue.set确保响应式更新
  2069 + this.$set(this.pxList[pxIndex], 'lqXhJksyjList', [jksItem]);
  2070 +
  2071 + // 强制更新视图
  2072 + this.$forceUpdate();
  2073 +
  2074 + // 获取金三角信息
  2075 + this.getJsjInfoByUserId(tquJks.id, (jsjId, jsjName) => {
  2076 + jksItem.jsjId = jsjId;
  2077 + });
  2078 + } else {
  2079 + console.warn('未找到T区健康师,请确保健康师列表中包含名称带有"T区"的健康师');
  2080 + }
  2081 + },
  2082 +
  2083 + // 医美品项健康师业绩自动分配
  2084 + handleYimeiJksDistribution(pxIndex) {
  2085 + const px = this.pxList[pxIndex];
  2086 + if (px.qt2 !== '医美' || !px.lqXhJksyjList || px.lqXhJksyjList.length === 0) {
  2087 + return;
  2088 + }
  2089 +
  2090 + const pxTotalAmount = px.pxjg * px.projectNumber;
  2091 + const pxTotalLaborCost = (px.sgf || 0) * px.projectNumber;
  2092 +
  2093 + // 过滤掉陪同健康师(isAccompanied为1的健康师不参与计算)
  2094 + const normalJksList = px.lqXhJksyjList.filter(jks => !jks.isAccompanied || jks.isAccompanied === 0);
  2095 +
  2096 + if (normalJksList.length === 0) {
  2097 + return;
  2098 + }
  2099 +
  2100 + // 分离T区健康师和其他健康师(排除陪同健康师)
  2101 + let tquJks = null;
  2102 + let otherJksList = [];
  2103 +
  2104 + for (let i = 0; i < normalJksList.length; i++) {
  2105 + const jks = normalJksList[i];
  2106 + if (jks.jksxm && jks.jksxm.includes('T区')) {
  2107 + tquJks = jks;
  2108 + } else {
  2109 + otherJksList.push(jks);
  2110 + }
  2111 + }
  2112 +
  2113 + if (tquJks) {
  2114 + // 有T区健康师的情况
  2115 + // T区健康师获得全部业绩、次数和手工费
  2116 + tquJks.jksyj = pxTotalAmount.toFixed(2);
  2117 + tquJks.kdpxNumber = px.projectNumber;
  2118 + tquJks.laborCost = pxTotalLaborCost.toFixed(2);
  2119 +
  2120 + // 其他健康师业绩、次数和手工费都为0
  2121 + if (otherJksList.length > 0) {
  2122 + for (let i = 0; i < otherJksList.length; i++) {
  2123 + const otherJks = otherJksList[i];
  2124 + otherJks.jksyj = 0;
  2125 + otherJks.kdpxNumber = 0;
  2126 + otherJks.laborCost = 0;
  2127 + }
  2128 + }
  2129 + } else {
  2130 + // 没有T区健康师的情况,按普通品项重新分配
  2131 + this.redistributeJksNumbersAndLaborCost(pxIndex);
  2132 + }
  2133 + },
  2134 +
  2135 + // 根据用户ID获取金三角信息
  2136 + getJsjInfoByUserId(userId, callback) {
  2137 + // let date = new Date();
  2138 + // let formattedDate = this.formatDate(date, 'yyyy-MM-dd HH:mm:ss');
  2139 + let formattedDate = this.formData.hksj
  2140 + console.log('formattedDate:', formattedDate);
  2141 + memberApi.getJsjInfoByUserMonth(userId, formattedDate).then((res) => {
  2142 + if (res.code === 200 && res.data) {
  2143 + const jsjId = res.data.jsjId;
  2144 + const jsjName = res.data.jsjName;
  2145 + if (callback) {
  2146 + callback(jsjId, jsjName);
  2147 + }
  2148 + } else {
  2149 + if (callback) {
  2150 + callback('');
  2151 + }
  2152 + }
  2153 + }).catch((err) => {
  2154 + console.error('获取金三角信息出错:', err);
  2155 + if (callback) {
  2156 + callback('');
  2157 + }
  2158 + });
  2159 + },
  2160 +
  2161 + // 更新医美品项T区健康师业绩分配
  2162 + updateYimeiJksDistribution(pxIndex) {
  2163 + const px = this.pxList[pxIndex];
  2164 + if (px.qt2 !== '医美' || !px.lqXhJksyjList || px.lqXhJksyjList.length === 0) {
  2165 + return;
  2166 + }
  2167 +
  2168 + const pxTotalAmount = px.pxjg * px.projectNumber;
  2169 + // const pxTotalLaborCost = (px.sgf || 0) * px.projectNumber;
  2170 +
  2171 + // 过滤掉陪同健康师(isAccompanied为1的健康师不参与计算)
  2172 + const normalJksList = px.lqXhJksyjList.filter(jks => !jks.isAccompanied || jks.isAccompanied === 0);
  2173 +
  2174 + // 查找T区健康师(排除陪同健康师)
  2175 + const tquJks = normalJksList.find(jks => jks.jksxm && jks.jksxm.includes('T区'));
  2176 +
  2177 + if (tquJks) {
  2178 + // 更新T区健康师的业绩、次数和手工费
  2179 + tquJks.jksyj = pxTotalAmount.toFixed(2);
  2180 + // tquJks.kdpxNumber = px.projectNumber;
  2181 + // tquJks.laborCost = pxTotalLaborCost.toFixed(2);
  2182 + tquJks.kdpxNumber = 0;
  2183 + tquJks.laborCost = 0;
  2184 + } else {
  2185 + // 如果没有T区健康师,重新分配所有健康师的次数和手工费
  2186 + this.redistributeJksNumbersAndLaborCost(pxIndex);
  2187 + }
  2188 + },
  2189 +
  2190 + // 业绩均分:当同时有健康师和科技部老师时,自动均分业绩
  2191 + distributePerformance(pxIndex) {
  2192 + const px = this.pxList[pxIndex];
  2193 + if (!px || !px.px || !px.pxmc) {
  2194 + return;
  2195 + }
  2196 +
  2197 + // 医美品项不处理均分
  2198 + if (px.qt2 === '医美') {
  2199 + return;
  2200 + }
  2201 +
  2202 + // 过滤掉陪同健康师(isAccompanied为1的健康师不参与计算)
  2203 + const normalJksList = px.lqXhJksyjList ? px.lqXhJksyjList.filter(jks => !jks.isAccompanied || jks.isAccompanied === 0) : [];
  2204 + const hasJks = normalJksList.length > 0;
  2205 + const hasKjb = px.lqXhKjbsyjList && px.lqXhKjbsyjList.length > 0;
  2206 + const pxTotalAmount = px.pxjg * px.projectNumber;
  2207 +
  2208 + // 如果同时有健康师和科技部老师,全部各自组内均分
  2209 + // 健康师组:总业绩全部在健康师之间均分
  2210 + // 科技老师组:总业绩全部在科技老师之间均分
  2211 + if (hasJks && hasKjb) {
  2212 + // 健康师组:总业绩全部,在健康师之间均分(排除陪同健康师)
  2213 + const jksCount = normalJksList.length;
  2214 + const avgJksAmount = pxTotalAmount / jksCount;
  2215 + normalJksList.forEach((jks, index) => {
  2216 + // 转为字符串格式,避免验证时的类型问题
  2217 + jks.jksyj = avgJksAmount.toFixed(2);
  2218 + });
  2219 +
  2220 + // 科技老师组:总业绩全部,在科技老师之间均分
  2221 + const kjbCount = px.lqXhKjbsyjList.length;
  2222 + const avgKjbAmount = pxTotalAmount / kjbCount;
  2223 + px.lqXhKjbsyjList.forEach((kjb, index) => {
  2224 + // 转为字符串格式,避免验证时的类型问题
  2225 + kjb.kjblsyj = avgKjbAmount.toFixed(2);
  2226 + });
  2227 + } else if (hasJks && !hasKjb) {
  2228 + // 只有健康师,健康师组获得全部业绩,在健康师之间均分(排除陪同健康师)
  2229 + const jksCount = normalJksList.length;
  2230 + const avgJksAmount = pxTotalAmount / jksCount;
  2231 + normalJksList.forEach((jks, index) => {
  2232 + jks.jksyj = avgJksAmount.toFixed(2);
  2233 + });
  2234 + } else if (!hasJks && hasKjb) {
  2235 + // 只有科技部老师,科技老师组获得全部业绩,在科技老师之间均分
  2236 + const kjbCount = px.lqXhKjbsyjList.length;
  2237 + const avgKjbAmount = pxTotalAmount / kjbCount;
  2238 + px.lqXhKjbsyjList.forEach((kjb, index) => {
  2239 + kjb.kjblsyj = avgKjbAmount.toFixed(2);
  2240 + });
  2241 + }
  2242 +
  2243 + this.$forceUpdate();
  2244 + },
  2245 +
  2246 + // 格式化日期方法
  2247 + formatDate(date, format) {
  2248 + if (!date) return '';
  2249 + const d = new Date(date);
  2250 + const year = d.getFullYear();
  2251 + const month = String(d.getMonth() + 1).padStart(2, '0');
  2252 + const day = String(d.getDate()).padStart(2, '0');
  2253 + const hours = String(d.getHours()).padStart(2, '0');
  2254 + const minutes = String(d.getMinutes()).padStart(2, '0');
  2255 + const seconds = String(d.getSeconds()).padStart(2, '0');
  2256 +
  2257 + return format
  2258 + .replace('yyyy', year)
  2259 + .replace('MM', month)
  2260 + .replace('dd', day)
  2261 + .replace('HH', hours)
  2262 + .replace('mm', minutes)
  2263 + .replace('ss', seconds);
  2264 + },
  2265 +
  2266 + // 清空表单
  2267 + clearForm() {
  2268 + this.formData = {
  2269 + hy: '',
  2270 + hyzh: '',
  2271 + hymc: '',
  2272 + gklx: '',
  2273 + hksj: this.utils.gettime().substring(0, 10),
  2274 + xfje: '',
  2275 + sgfy: '',
  2276 + isOvertime: false,
  2277 + overtimeCoefficient: 0
  2278 + };
  2279 +
  2280 + this.selectedValues = {
  2281 + hy: null
  2282 + };
  2283 +
  2284 + this.pxList = [];
  2285 + this.memberSignature = '';
  2286 + if (this.$refs.signaturePad) {
  2287 + this.$refs.signaturePad.clearSignature();
  2288 + }
  2289 + this.addPxRow();
  2290 + }
  2291 + }
  2292 + }
  2293 +</script>
  2294 +
  2295 +<style lang="scss" scoped>
  2296 + .member-consume-container {
  2297 + min-height: 100vh;
  2298 + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%);
  2299 + padding: 20rpx;
  2300 + }
  2301 +
  2302 + .header {
  2303 + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
  2304 + border-radius: 36rpx;
  2305 + padding: 48rpx;
  2306 + text-align: center;
  2307 + margin-bottom: 48rpx;
  2308 + box-shadow: 0 8rpx 48rpx 0 rgba(76, 175, 80, 0.10);
  2309 + }
  2310 +
  2311 + .header-title {
  2312 + color: #fff;
  2313 + font-size: 36rpx;
  2314 + font-weight: bold;
  2315 + letter-spacing: 4rpx;
  2316 + }
  2317 +
  2318 + .form-card {
  2319 + background: #fff;
  2320 + border-radius: 36rpx;
  2321 + box-shadow: 0 8rpx 48rpx 0 rgba(76, 175, 80, 0.10);
  2322 + border: 3rpx solid #c8e6c9;
  2323 + overflow: hidden;
  2324 + }
  2325 +
  2326 + .form-content {
  2327 + padding: 48rpx;
  2328 + }
  2329 +
  2330 + .form-group {
  2331 + margin-bottom: 40rpx;
  2332 + }
  2333 +
  2334 + .form-group:last-child {
  2335 + margin-bottom: 0;
  2336 + }
  2337 +
  2338 + .form-label {
  2339 + display: block;
  2340 + margin-bottom: 16rpx;
  2341 + font-weight: bold;
  2342 + color: #388e3c;
  2343 + letter-spacing: 2rpx;
  2344 + font-size: 28rpx;
  2345 + }
  2346 +
  2347 + .custom-select {
  2348 + position: relative;
  2349 + background: #f9fff9;
  2350 + border: 3rpx solid #c8e6c9;
  2351 + border-radius: 20rpx;
  2352 + padding: 28rpx 24rpx;
  2353 + display: flex;
  2354 + align-items: center;
  2355 + justify-content: space-between;
  2356 + cursor: pointer;
  2357 + z-index: 10;
  2358 + min-height: 80rpx;
  2359 + height: 80rpx;
  2360 + box-sizing: border-box;
  2361 + }
  2362 +
  2363 + .select-text {
  2364 + font-size: 28rpx;
  2365 + color: #2e7d32;
  2366 + flex: 1;
  2367 + }
  2368 +
  2369 + .select-arrow {
  2370 + position: absolute;
  2371 + right: 24rpx;
  2372 + top: 50%;
  2373 + transform: translateY(-50%);
  2374 + color: #6a9c6a;
  2375 + font-size: 24rpx;
  2376 + pointer-events: none;
  2377 + }
  2378 +
  2379 + .input-wrapper {
  2380 + position: relative;
  2381 + }
  2382 +
  2383 + input {
  2384 + width: 100%;
  2385 + padding: 0 24rpx;
  2386 + border: 3rpx solid #c8e6c9;
  2387 + border-radius: 20rpx;
  2388 + font-size: 28rpx;
  2389 + background: #f9fff9;
  2390 + color: #2e7d32;
  2391 + box-sizing: border-box;
  2392 + min-height: 80rpx;
  2393 + height: 80rpx;
  2394 + // box-sizing: border-box;
  2395 + }
  2396 +
  2397 + input:focus {
  2398 + outline: none;
  2399 + border-color: #43a047;
  2400 + box-shadow: 0 0 0 6rpx rgba(76, 175, 80, 0.1);
  2401 + background: #fff;
  2402 + }
  2403 +
  2404 + input:disabled {
  2405 + background: #f5f5f5;
  2406 + color: #666;
  2407 + cursor: not-allowed;
  2408 + }
  2409 +
  2410 + /* 加班相关样式 */
  2411 + .checkbox-wrapper {
  2412 + display: flex;
  2413 + align-items: center;
  2414 + margin-bottom: 16rpx;
  2415 + }
  2416 +
  2417 + .checkbox-label {
  2418 + margin-left: 12rpx;
  2419 + font-size: 28rpx;
  2420 + color: #2e7d32;
  2421 + }
  2422 +
  2423 + .overtime-select-wrapper {
  2424 + margin-top: 16rpx;
  2425 + }
  2426 +
  2427 + /* 品项相关样式 */
  2428 + .px-container {
  2429 + margin-bottom: 32rpx;
  2430 + }
  2431 +
  2432 + .px-row {
  2433 + display: flex;
  2434 + gap: 24rpx;
  2435 + margin-bottom: 24rpx;
  2436 + align-items: center;
  2437 + flex-wrap: wrap;
  2438 + padding: 32rpx;
  2439 + border: 2rpx solid #e0e0e0;
  2440 + border-radius: 20rpx;
  2441 + background: #fafafa;
  2442 + }
  2443 +
  2444 + .px-select {
  2445 + flex: 2;
  2446 + padding: 24rpx;
  2447 + border: 3rpx solid #c8e6c9;
  2448 + border-radius: 20rpx;
  2449 + font-size: 28rpx;
  2450 + background: #f9fff9;
  2451 + color: #2e7d32;
  2452 + cursor: pointer;
  2453 + position: relative;
  2454 + min-width: 400rpx;
  2455 + text-align: center;
  2456 + }
  2457 +
  2458 + .px-select:focus {
  2459 + outline: none;
  2460 + border-color: #43a047;
  2461 + box-shadow: 0 0 0 6rpx rgba(76, 175, 80, 0.1);
  2462 + background: #fff;
  2463 + }
  2464 +
  2465 + .px-info {
  2466 + flex: 2;
  2467 + padding: 24rpx;
  2468 + border: 3rpx solid #e0e0e0;
  2469 + border-radius: 20rpx;
  2470 + font-size: 24rpx;
  2471 + background: #f8f9fa;
  2472 + color: #495057;
  2473 + min-width: 400rpx;
  2474 + }
  2475 +
  2476 + .px-info-title {
  2477 + font-weight: bold;
  2478 + color: #2e7d32;
  2479 + margin-bottom: 12rpx;
  2480 + font-size: 28rpx;
  2481 + }
  2482 +
  2483 + .px-info-details {
  2484 + display: grid;
  2485 + grid-template-columns: 1fr 1fr;
  2486 + gap: 8rpx;
  2487 + font-size: 24rpx;
  2488 + }
  2489 +
  2490 + .px-info-item {
  2491 + display: flex;
  2492 + align-items: center;
  2493 + }
  2494 +
  2495 + .px-info-label {
  2496 + color: #6c757d;
  2497 + font-weight: 500;
  2498 + margin-right: 20rpx;
  2499 + }
  2500 +
  2501 + .px-info-value {
  2502 + color: #495057;
  2503 + font-weight: 600;
  2504 + }
  2505 +
  2506 + .px-number {
  2507 + flex: 1;
  2508 + // padding: 24rpx;
  2509 + border: 3rpx solid #c8e6c9;
  2510 + border-radius: 20rpx;
  2511 + font-size: 28rpx;
  2512 + background: #f9fff9;
  2513 + color: #2e7d32;
  2514 + min-width: 160rpx;
  2515 + }
  2516 +
  2517 + .px-delete {
  2518 + padding: 24rpx 32rpx;
  2519 + // border: 3rpx solid #ddd;
  2520 + border-radius: 20rpx;
  2521 + background: #f5f5f5;
  2522 + color: #666;
  2523 + cursor: pointer;
  2524 + font-size: 24rpx;
  2525 + transition: all 0.2s ease;
  2526 + }
  2527 +
  2528 + .px-delete:hover {
  2529 + background: #e8e8e8;
  2530 + border-color: #ccc;
  2531 + }
  2532 +
  2533 + .btn-add-px {
  2534 + width: 100%;
  2535 + padding: 0 40rpx;
  2536 + border: 3rpx solid #43a047;
  2537 + border-radius: 20rpx;
  2538 + background: #e8f5e9;
  2539 + color: #2e7d32;
  2540 + font-size: 28rpx;
  2541 + font-weight: bold;
  2542 + }
  2543 +
  2544 + /* 品项第三行样式(健康师和科技部老师选择) */
  2545 + .px-row-third {
  2546 + display: block;
  2547 + margin-top: 16rpx;
  2548 + width: 100%;
  2549 + }
  2550 +
  2551 + .px-staff-section {
  2552 + width: 100%;
  2553 + margin-bottom: 24rpx;
  2554 + }
  2555 +
  2556 + .px-staff-section:last-child {
  2557 + margin-bottom: 0;
  2558 + }
  2559 +
  2560 + .px-jks-select,
  2561 + .px-kjb-select {
  2562 + padding: 16rpx 24rpx;
  2563 + border: 3rpx solid #c8e6c9;
  2564 + border-radius: 16rpx;
  2565 + background: #f9fff9;
  2566 + color: #2e7d32;
  2567 + cursor: pointer;
  2568 + font-size: 24rpx;
  2569 + text-align: center;
  2570 + transition: all 0.2s ease;
  2571 + }
  2572 +
  2573 + .px-jks-select:hover,
  2574 + .px-kjb-select:hover {
  2575 + background: #e8f5e9;
  2576 + border-color: #43a047;
  2577 + }
  2578 +
  2579 + .px-jks-list,
  2580 + .px-kjb-list {
  2581 + margin-top: 16rpx;
  2582 + border: 2rpx solid #e0e0e0;
  2583 + border-radius: 12rpx;
  2584 + background: #fff;
  2585 + }
  2586 +
  2587 + .px-staff-item {
  2588 + padding: 16rpx;
  2589 + border-bottom: 2rpx solid #f0f0f0;
  2590 + font-size: 24rpx;
  2591 + }
  2592 +
  2593 + .px-staff-item:last-child {
  2594 + border-bottom: none;
  2595 + }
  2596 +
  2597 + .px-staff-header {
  2598 + display: flex;
  2599 + justify-content: space-between;
  2600 + align-items: center;
  2601 + margin-bottom: 12rpx;
  2602 + }
  2603 +
  2604 + .px-staff-name {
  2605 + flex: 1;
  2606 + color: #2e7d32;
  2607 + font-weight: bold;
  2608 + }
  2609 +
  2610 + .px-staff-remove {
  2611 + background: #f44336;
  2612 + color: #fff;
  2613 + border: none;
  2614 + border-radius: 8rpx;
  2615 + padding: 4rpx 12rpx;
  2616 + font-size: 20rpx;
  2617 + cursor: pointer;
  2618 + }
  2619 +
  2620 + .px-staff-remove:hover {
  2621 + background: #d32f2f;
  2622 + }
  2623 +
  2624 + .px-staff-fields {
  2625 + display: flex;
  2626 + flex-direction: column;
  2627 + gap: 16rpx;
  2628 + }
  2629 +
  2630 + .px-staff-row {
  2631 + display: flex;
  2632 + gap: 16rpx;
  2633 + align-items: center;
  2634 + }
  2635 +
  2636 + .px-staff-field {
  2637 + flex: 1;
  2638 + min-width: 200rpx;
  2639 + }
  2640 +
  2641 + .px-staff-field input {
  2642 + width: 100%;
  2643 + // padding: 12rpx 16rpx;
  2644 + border: 2rpx solid #ddd;
  2645 + border-radius: 8rpx;
  2646 + font-size: 24rpx;
  2647 + background: #f9f9f9;
  2648 + }
  2649 +
  2650 + .px-staff-field input:focus {
  2651 + outline: none;
  2652 + border-color: #43a047;
  2653 + background: #fff;
  2654 + }
  2655 +
  2656 + .px-staff-field-label {
  2657 + display: block;
  2658 + font-size: 20rpx;
  2659 + color: #666;
  2660 + margin-bottom: 8rpx;
  2661 + font-weight: 500;
  2662 + }
  2663 +
  2664 + .btn-group {
  2665 + display: flex;
  2666 + gap: 24rpx;
  2667 + margin-top: 48rpx;
  2668 + }
  2669 +
  2670 + .btn {
  2671 + flex: 1;
  2672 + padding: 10rpx 40rpx;
  2673 + border: none;
  2674 + border-radius: 20rpx;
  2675 + font-size: 28rpx;
  2676 + font-weight: bold;
  2677 + cursor: pointer;
  2678 + transition: all 0.2s ease;
  2679 + letter-spacing: 2rpx;
  2680 + }
  2681 +
  2682 + .btn-primary {
  2683 + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  2684 + color: #fff;
  2685 + box-shadow: 0 4rpx 16rpx rgba(67, 233, 123, 0.3);
  2686 + }
  2687 +
  2688 + .btn-primary:hover {
  2689 + box-shadow: 0 8rpx 32rpx rgba(67, 233, 123, 0.4);
  2690 + transform: translateY(-2rpx);
  2691 + }
  2692 +
  2693 + /* 签字相关样式 */
  2694 + .signature-preview {
  2695 + margin-top: 24rpx;
  2696 + padding: 24rpx;
  2697 + background: #f9fff9;
  2698 + border: 2rpx solid #c8e6c9;
  2699 + border-radius: 16rpx;
  2700 + }
  2701 +
  2702 + .preview-label {
  2703 + display: block;
  2704 + font-size: 26rpx;
  2705 + color: #2e7d32;
  2706 + font-weight: bold;
  2707 + margin-bottom: 16rpx;
  2708 + }
  2709 +
  2710 + .signature-image {
  2711 + width: 100%;
  2712 + max-width: 300rpx;
  2713 + height: 120rpx;
  2714 + border: 2rpx solid #e0e0e0;
  2715 + border-radius: 12rpx;
  2716 + background: #fff;
  2717 + margin-bottom: 16rpx;
  2718 + }
  2719 +
  2720 + .btn-clear-signature {
  2721 + padding: 12rpx 24rpx;
  2722 + background: #f5f5f5;
  2723 + color: #666;
  2724 + border: 2rpx solid #ddd;
  2725 + border-radius: 12rpx;
  2726 + font-size: 24rpx;
  2727 + font-weight: 500;
  2728 + cursor: pointer;
  2729 + transition: all 0.2s ease;
  2730 + }
  2731 +
  2732 + .btn-clear-signature:hover {
  2733 + background: #e0e0e0;
  2734 + }
  2735 +
  2736 + /* 全屏签字弹窗样式 */
  2737 + .signature-modal-overlay {
  2738 + position: fixed;
  2739 + top: 0;
  2740 + left: 0;
  2741 + width: 100%;
  2742 + height: 100%;
  2743 + background: rgba(0, 0, 0, 0.7);
  2744 + z-index: 9999;
  2745 + display: flex;
  2746 + align-items: center;
  2747 + justify-content: center;
  2748 + box-sizing: border-box;
  2749 + }
  2750 +
  2751 + .signature-modal {
  2752 + width: 100%;
  2753 + background: #fff;
  2754 + overflow: hidden;
  2755 + height: 100%;
  2756 + display: flex;
  2757 + flex-direction: column;
  2758 + justify-content: space-between;
  2759 + }
  2760 +
  2761 + .signature-modal-header {
  2762 + display: flex;
  2763 + justify-content: space-between;
  2764 + align-items: center;
  2765 + padding: 30rpx 40rpx;
  2766 + background: #f8f9fa;
  2767 + border-bottom: 2rpx solid #e9ecef;
  2768 + }
  2769 +
  2770 + .signature-modal-title {
  2771 + font-size: 32rpx;
  2772 + font-weight: bold;
  2773 + color: #2e7d32;
  2774 + }
  2775 +
  2776 + .btn-close-modal {
  2777 + width: 60rpx;
  2778 + height: 60rpx;
  2779 + background: #f5f5f5;
  2780 + border: none;
  2781 + border-radius: 50%;
  2782 + font-size: 36rpx;
  2783 + color: #666;
  2784 + display: flex;
  2785 + align-items: center;
  2786 + justify-content: center;
  2787 + cursor: pointer;
  2788 + transition: all 0.2s ease;
  2789 + margin: 0;
  2790 + }
  2791 +
  2792 + .btn-close-modal:hover {
  2793 + background: #e0e0e0;
  2794 + color: #333;
  2795 + }
  2796 +
  2797 + .signature-modal-content {
  2798 + background: #fff;
  2799 + flex: 1;
  2800 + width: 100%;
  2801 + }
  2802 +
  2803 + /* 全屏模式下SignaturePad组件样式调整 */
  2804 + .signature-modal-content .signature-container {
  2805 + height: 100%;
  2806 + border: none;
  2807 + border-radius: 0;
  2808 + box-shadow: none;
  2809 + }
  2810 +
  2811 + .signature-modal-content .signature-header {
  2812 + padding: 20rpx 30rpx;
  2813 + background: #f8f9fa;
  2814 + border-bottom: 2rpx solid #e9ecef;
  2815 + }
  2816 +
  2817 + .signature-modal-content .signature-title {
  2818 + font-size: 32rpx;
  2819 + font-weight: bold;
  2820 + color: #2e7d32;
  2821 + }
  2822 +
  2823 + .signature-modal-content .btn-clear,
  2824 + .signature-modal-content .btn-confirm {
  2825 + padding: 16rpx 32rpx;
  2826 + font-size: 28rpx;
  2827 + font-weight: 500;
  2828 + border-radius: 12rpx;
  2829 + }
  2830 +
  2831 + .signature-modal-content .btn-clear {
  2832 + background: #f5f5f5;
  2833 + color: #666;
  2834 + border: 2rpx solid #ddd;
  2835 + }
  2836 +
  2837 + .signature-modal-content .btn-clear:hover {
  2838 + background: #e0e0e0;
  2839 + color: #333;
  2840 + }
  2841 +
  2842 + .signature-modal-content .btn-confirm {
  2843 + background: #2e7d32;
  2844 + color: #fff;
  2845 + border: none;
  2846 + }
  2847 +
  2848 + .signature-modal-content .btn-confirm:hover {
  2849 + background: #1b5e20;
  2850 + transform: translateY(-2rpx);
  2851 + }
  2852 +
  2853 + .signature-modal-content .signature-pad {
  2854 + flex: 1;
  2855 + display: flex;
  2856 + justify-content: center;
  2857 + align-items: center;
  2858 + padding: 20rpx;
  2859 + }
  2860 +
  2861 + .signature-modal-content .signature-tips {
  2862 + padding: 20rpx;
  2863 + text-align: center;
  2864 + background: #f8f9fa;
  2865 + border-top: 2rpx solid #e9ecef;
  2866 + }
  2867 +
  2868 + .signature-modal-content .tips-text {
  2869 + font-size: 24rpx;
  2870 + color: #666;
  2871 + }
  2872 +
  2873 + /* 签字占位符样式 */
  2874 + .signature-placeholder {
  2875 + display: flex;
  2876 + justify-content: center;
  2877 + align-items: center;
  2878 + min-height: 200rpx;
  2879 + border: 2rpx dashed #c8e6c9;
  2880 + border-radius: 16rpx;
  2881 + background: #f9fff9;
  2882 + }
  2883 +
  2884 + .btn-signature-placeholder {
  2885 + background: #2e7d32;
  2886 + color: #fff;
  2887 + border: none;
  2888 + border-radius: 16rpx;
  2889 + font-size: 28rpx;
  2890 + font-weight: 500;
  2891 + cursor: pointer;
  2892 + transition: all 0.2s ease;
  2893 + box-shadow: 0 4rpx 16rpx rgba(46, 125, 50, 0.3);
  2894 + }
  2895 +
  2896 + .btn-signature-placeholder:hover {
  2897 + background: #1b5e20;
  2898 + transform: translateY(-2rpx);
  2899 + box-shadow: 0 8rpx 32rpx rgba(46, 125, 50, 0.4);
  2900 + }
  2901 +
  2902 + .signature-placeholder-text {
  2903 + color: #fff;
  2904 + }
  2905 +
  2906 + /* 签字操作按钮样式 */
  2907 + .signature-actions {
  2908 + display: flex;
  2909 + gap: 16rpx;
  2910 + margin-top: 16rpx;
  2911 + }
  2912 +
  2913 + .btn-re-signature {
  2914 + flex: 1;
  2915 + padding: 12rpx 24rpx;
  2916 + background: #2e7d32;
  2917 + color: #fff;
  2918 + border: none;
  2919 + border-radius: 12rpx;
  2920 + font-size: 24rpx;
  2921 + font-weight: 500;
  2922 + cursor: pointer;
  2923 + transition: all 0.2s ease;
  2924 + }
  2925 +
  2926 + .btn-re-signature:hover {
  2927 + background: #1b5e20;
  2928 + }
  2929 +
  2930 + @media (max-width: 750rpx) {
  2931 + // .form-content {
  2932 + // padding: 40rpx;
  2933 + // }
  2934 +
  2935 + // .px-row {
  2936 + // flex-direction: column;
  2937 + // }
  2938 +
  2939 + // .px-select,
  2940 + // .px-info,
  2941 + // .px-number {
  2942 + // min-width: 100%;
  2943 + // }
  2944 +
  2945 + // .signature-preview {
  2946 + // padding: 20rpx;
  2947 + // }
  2948 +
  2949 + // .preview-label {
  2950 + // font-size: 24rpx;
  2951 + // }
  2952 +
  2953 + // .signature-image {
  2954 + // max-width: 250rpx;
  2955 + // height: 100rpx;
  2956 + // }
  2957 +
  2958 + // .btn-clear-signature {
  2959 + // padding: 10rpx 20rpx;
  2960 + // font-size: 22rpx;
  2961 + // }
  2962 + }
  2963 +</style>
0 2964 \ No newline at end of file
... ...
绿纤uni-app/pages/member-consume/member-consume.vue
... ... @@ -73,7 +73,7 @@
73 73 </view>
74 74  
75 75 <!-- 次数输入框 -->
76   - <input :disabled="removeid?true:false" type="number" class="px-number" placeholder="次数" min="1" :max="px.RemainingCount"
  76 + <input :disabled="px.RemainingCount?false:true" type="number" class="px-number" placeholder="次数" min="1" :max="px.RemainingCount"
77 77 step="1" v-model="px.projectNumber" @input="updatePxNumber(index, $event)">
78 78  
79 79 <!-- 删除按钮 -->
... ... @@ -826,21 +826,6 @@
826 826 async handlePxSelection(selectedOption) {
827 827 if (this.currentRowIndex >= 0) {
828 828 try {
829   - console.error(selectedOption)
830   - // 先校验已经选择的品项有没有这个
831   - const existingIndex = this.pxList.findIndex((item, index) =>
832   - index !== this.currentRowIndex && item.BillingItemId === selectedOption.BillingItemId
833   - );
834   - if (existingIndex !== -1) {
835   - uni.showToast({
836   - title: '该品项已存在,请勿重复添加',
837   - icon: 'none',
838   - duration: 2000
839   - });
840   - this.closeModal();
841   - return;
842   - }
843   -
844 829 // 请求品项详细信息
845 830 const detailResult = await lxApi.getPxDetail(selectedOption.px);
846 831 let qt2 = "";
... ... @@ -1155,12 +1140,14 @@
1155 1140 // 更新品项次数
1156 1141 updatePxNumber(rowIndex, event) {
1157 1142 const value = event.detail.value;
  1143 + console.log('value',value)
1158 1144 if (this.pxList[rowIndex]) {
1159   - let inputNumber = parseInt(value) || 1;
  1145 + let inputNumber = parseInt(value);
1160 1146  
1161 1147 // 验证不能超过剩余次数
1162   - if (inputNumber > this.pxList[rowIndex].RemainingCount) {
1163   - inputNumber = this.pxList[rowIndex].RemainingCount;
  1148 + if (inputNumber <= 0 ) {
  1149 + inputNumber = 1;
  1150 + console.log('inputNumber',inputNumber)
1164 1151 // 更新输入框显示的值
1165 1152 this.$nextTick(() => {
1166 1153 const inputElement = event.target;
... ... @@ -1168,6 +1155,7 @@
1168 1155 inputElement.value = inputNumber;
1169 1156 }
1170 1157 });
  1158 + this.$forceUpdate();
1171 1159 }
1172 1160  
1173 1161 this.pxList[rowIndex].projectNumber = inputNumber;
... ... @@ -1633,15 +1621,94 @@
1633 1621 });
1634 1622 return;
1635 1623 }
  1624 + }
  1625 +
  1626 + // 验证相同品项的次数总和不能超过剩余次数
  1627 + // 按品项分组(优先使用 BillingItemId,如果没有则使用 px)
  1628 + console.log('========== 开始验证相同品项次数总和 ==========');
  1629 + console.log('品项列表:', this.pxList.map((px, idx) => ({
  1630 + 行号: idx + 1,
  1631 + 品项名称: px.pxmc,
  1632 + BillingItemId: px.BillingItemId,
  1633 + px: px.px,
  1634 + 次数: px.projectNumber,
  1635 + 剩余次数: px.RemainingCount
  1636 + })));
  1637 +
  1638 + const pxGroups = new Map();
  1639 + for (let i = 0; i < this.pxList.length; i++) {
  1640 + const px = this.pxList[i];
  1641 + // 生成唯一标识:优先使用 BillingItemId,否则使用 px
  1642 + const key = px.BillingItemId;
  1643 +
  1644 + console.log(`处理第${i + 1}行: 品项=${px.pxmc}, BillingItemId=${key}, 次数=${px.projectNumber}, 剩余次数=${px.RemainingCount}`);
  1645 +
  1646 + if (!pxGroups.has(key)) {
  1647 + pxGroups.set(key, {
  1648 + items: [],
  1649 + pxName: px.pxmc || '',
  1650 + remainingCount: null
  1651 + });
  1652 + console.log(` 创建新分组: key=${key}, 品项名称=${px.pxmc}`);
  1653 + }
  1654 +
  1655 + const group = pxGroups.get(key);
  1656 + group.items.push({
  1657 + index: i,
  1658 + px: px,
  1659 + projectNumber: px.projectNumber || 0
  1660 + });
  1661 + console.log(` 添加到分组: 当前分组有${group.items.length}个品项`);
  1662 +
  1663 + // 记录第一个有 RemainingCount 的值作为该品项的剩余次数
  1664 + if (group.remainingCount === null && px.RemainingCount !== undefined && px.RemainingCount !== null) {
  1665 + group.remainingCount = px.RemainingCount;
  1666 + console.log(` 设置剩余次数: ${px.RemainingCount}`);
  1667 + }
  1668 + }
  1669 +
  1670 + console.log('分组结果:', Array.from(pxGroups.entries()).map(([key, group]) => ({
  1671 + key: key,
  1672 + 品项名称: group.pxName,
  1673 + 剩余次数: group.remainingCount,
  1674 + 包含行数: group.items.length,
  1675 + 行号列表: group.items.map(item => item.index + 1),
  1676 + 次数列表: group.items.map(item => item.projectNumber)
  1677 + })));
  1678 +
  1679 + // 验证每个分组的次数总和
  1680 + for (const [key, group] of pxGroups.entries()) {
  1681 + console.log(`\n验证分组: key=${key}, 品项=${group.pxName}`);
  1682 +
  1683 + // 只验证有 RemainingCount 的品项(修改时已存在的品项可能没有此字段)
  1684 + if (group.remainingCount === null) {
  1685 + console.log(` 跳过验证: 该品项没有剩余次数字段(可能是修改时已存在的品项)`);
  1686 + continue;
  1687 + }
1636 1688  
1637   - // 验证次数不能超过剩余次数
1638   - if (px.RemainingCount && px.projectNumber > px.RemainingCount) {
  1689 + // 计算该品项在所有行的次数总和
  1690 + const totalNumber = group.items.reduce((sum, item) => sum + Number(item.projectNumber), 0);
  1691 + const rowNumbers = group.items.map(item => item.index + 1).join('、');
  1692 +
  1693 + console.log(` 次数总和: ${totalNumber} (行号: ${rowNumbers})`);
  1694 + console.log(` 剩余次数: ${group.remainingCount}`);
  1695 + console.log(` 验证结果: ${totalNumber > group.remainingCount ? '❌ 失败' : '✅ 通过'}`);
  1696 +
  1697 + if (totalNumber > group.remainingCount) {
  1698 + console.error(`验证失败: 品项"${group.pxName}"在第${rowNumbers}行的次数总和(${totalNumber})超过剩余次数(${group.remainingCount})`);
1639 1699 uni.showToast({
1640   - title: `第${i + 1}个品项的次数(${px.projectNumber})不能超过剩余次数(${px.RemainingCount})`,
  1700 + title: `品项"${group.pxName}"在第${rowNumbers}行的次数总和(${totalNumber})不能超过剩余次数(${group.remainingCount})`,
1641 1701 icon: 'none'
1642 1702 });
1643 1703 return;
1644 1704 }
  1705 + }
  1706 +
  1707 + console.log('========== 相同品项次数总和验证通过 ==========\n');
  1708 +
  1709 + // 继续验证其他信息
  1710 + for (let i = 0; i < this.pxList.length; i++) {
  1711 + const px = this.pxList[i];
1645 1712  
1646 1713 // 验证健康师(特殊处理:px为cell时,健康师和科技部老师至少选择一个)
1647 1714 const isSpecialPx = px.beautyType == 'cell';
... ...