Commit a510c633fc1bab44bd18eaeacd2fe712316d3c0e

Authored by 李宇
2 parents f7aed489 77631543

Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP

antis-ncc-admin/src/views/extend/storeDashboard/index.vue
... ... @@ -92,18 +92,9 @@
92 92 </el-col>
93 93 </el-row>
94 94  
95   - <!-- 第二行:每日运营数据趋势 + 门店综合能力雷达图 -->
  95 + <!-- 第二行:门店综合能力雷达图 -->
96 96 <el-row :gutter="16" class="chart-row">
97   - <el-col :span="16">
98   - <el-card class="chart-card" shadow="hover">
99   - <div slot="header" class="card-header">
100   - <i class="el-icon-date"></i>
101   - <span>每日运营数据趋势</span>
102   - </div>
103   - <div ref="dailyChart" class="chart-container"></div>
104   - </el-card>
105   - </el-col>
106   - <el-col :span="8">
  97 + <el-col :span="24">
107 98 <el-card class="chart-card" shadow="hover">
108 99 <div slot="header" class="card-header">
109 100 <i class="el-icon-aim"></i>
... ... @@ -182,7 +173,8 @@
182 173 <el-table :data="topBillingItems" size="small" border stripe>
183 174 <el-table-column type="index" label="排名" width="60" align="center">
184 175 <template slot-scope="scope">
185   - <el-tag v-if="scope.$index < 3" :type="['danger', 'warning', 'success'][scope.$index]" size="mini">
  176 + <el-tag v-if="scope.$index < 3"
  177 + :type="['danger', 'warning', 'success'][scope.$index]" size="mini">
186 178 {{ scope.$index + 1 }}
187 179 </el-tag>
188 180 <span v-else>{{ scope.$index + 1 }}</span>
... ... @@ -191,13 +183,15 @@
191 183 <el-table-column prop="itemName" label="品项名称" min-width="180" />
192 184 <el-table-column prop="billingAmount" label="开单金额" width="140" align="right">
193 185 <template slot-scope="scope">
194   - <span style="font-weight: 600; color: #67C23A;">¥{{ formatMoney(scope.row.billingAmount) }}</span>
  186 + <span style="font-weight: 600; color: #67C23A;">¥{{
  187 + formatMoney(scope.row.billingAmount) }}</span>
195 188 </template>
196 189 </el-table-column>
197 190 <el-table-column prop="billingCount" label="开单次数" width="100" align="center" />
198 191 <el-table-column prop="category" label="分类" width="80" align="center">
199 192 <template slot-scope="scope">
200   - <el-tag size="mini" :type="getCategoryType(scope.row.category)">{{ scope.row.category }}</el-tag>
  193 + <el-tag size="mini" :type="getCategoryType(scope.row.category)">{{
  194 + scope.row.category }}</el-tag>
201 195 </template>
202 196 </el-table-column>
203 197 </el-table>
... ... @@ -217,13 +211,16 @@
217 211 <el-table-column type="index" label="排名" width="60" align="center" />
218 212 <el-table-column prop="name" label="健康师姓名" min-width="120" />
219 213 <el-table-column prop="billingPerformance" label="开单业绩" width="120" align="right">
220   - <template slot-scope="scope">¥{{ formatMoney(scope.row.billingPerformance) }}</template>
  214 + <template slot-scope="scope">¥{{ formatMoney(scope.row.billingPerformance)
  215 + }}</template>
221 216 </el-table-column>
222 217 <el-table-column prop="consumePerformance" label="消耗业绩" width="120" align="right">
223   - <template slot-scope="scope">¥{{ formatMoney(scope.row.consumePerformance) }}</template>
  218 + <template slot-scope="scope">¥{{ formatMoney(scope.row.consumePerformance)
  219 + }}</template>
224 220 </el-table-column>
225 221 <el-table-column prop="totalPerformance" label="总业绩" width="120" align="right">
226   - <template slot-scope="scope">¥{{ formatMoney(scope.row.totalPerformance) }}</template>
  222 + <template slot-scope="scope">¥{{ formatMoney(scope.row.totalPerformance)
  223 + }}</template>
227 224 </el-table-column>
228 225 </el-table>
229 226 </el-card>
... ... @@ -239,7 +236,8 @@
239 236 <el-table-column prop="itemName" label="品项名称" min-width="150" />
240 237 <el-table-column prop="consumeAmount" label="消耗金额" width="120" align="right">
241 238 <template slot-scope="scope">
242   - <span style="font-weight: 600; color: #409EFF;">¥{{ formatMoney(scope.row.consumeAmount) }}</span>
  239 + <span style="font-weight: 600; color: #409EFF;">¥{{
  240 + formatMoney(scope.row.consumeAmount) }}</span>
243 241 </template>
244 242 </el-table-column>
245 243 <el-table-column prop="category" label="分类" width="80" />
... ... @@ -340,7 +338,8 @@
340 338 <span class="rank-number">{{ comparison.performanceRanking }}</span>
341 339 <span class="rank-total">/ {{ comparison.totalStoreCount }}</span>
342 340 </div>
343   - <div class="ranking-badge" :class="getRankingClass(comparison.performanceRanking, comparison.totalStoreCount)">
  341 + <div class="ranking-badge"
  342 + :class="getRankingClass(comparison.performanceRanking, comparison.totalStoreCount)">
344 343 {{ getRankingText(comparison.performanceRanking, comparison.totalStoreCount) }}
345 344 </div>
346 345 </div>
... ... @@ -410,7 +409,8 @@
410 409 <span class="progress-label">{{ metric.label }}</span>
411 410 <span class="progress-value">{{ metric.value }}%</span>
412 411 </div>
413   - <el-progress :percentage="metric.value" :color="metric.color" :stroke-width="8"></el-progress>
  412 + <el-progress :percentage="metric.value" :color="metric.color"
  413 + :stroke-width="8"></el-progress>
414 414 </div>
415 415 </div>
416 416 </el-card>
... ... @@ -517,7 +517,6 @@ export default {
517 517 ],
518 518 trendChart: null,
519 519 categoryChart: null,
520   - dailyChart: null,
521 520 compareChart: null,
522 521 stackedChart: null,
523 522 funnelChart: null,
... ... @@ -561,7 +560,6 @@ export default {
561 560 beforeDestroy() {
562 561 if (this.trendChart) this.trendChart.dispose()
563 562 if (this.categoryChart) this.categoryChart.dispose()
564   - if (this.dailyChart) this.dailyChart.dispose()
565 563 if (this.compareChart) this.compareChart.dispose()
566 564 if (this.stackedChart) this.stackedChart.dispose()
567 565 if (this.funnelChart) this.funnelChart.dispose()
... ... @@ -577,7 +575,6 @@ export default {
577 575 this.$nextTick(() => {
578 576 this.renderTrendChart()
579 577 this.renderCategoryChart()
580   - this.renderDailyChart()
581 578 this.renderCompareChart()
582 579 this.renderStackedChart()
583 580 this.renderFunnelChart()
... ... @@ -630,28 +627,6 @@ export default {
630 627 }
631 628 this.categoryChart.setOption(option)
632 629 },
633   - renderDailyChart() {
634   - if (!this.$refs.dailyChart) return
635   - this.dailyChart = echarts.init(this.$refs.dailyChart)
636   - const option = {
637   - tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
638   - legend: { data: ['开单业绩', '消耗业绩', '人头数', '人次', '项目数'], top: 10 },
639   - grid: { left: '60px', right: '80px', top: '50px', bottom: '60px', containLabel: false },
640   - xAxis: { type: 'category', data: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30'], axisLabel: { rotate: 45, interval: 2, fontSize: 11 } },
641   - yAxis: [
642   - { type: 'value', name: '业绩', position: 'left', axisLabel: { formatter: value => value >= 10000 ? '¥' + (value / 10000).toFixed(1) + '万' : '¥' + value }, splitLine: { lineStyle: { type: 'dashed', color: '#E4E7ED' } } },
643   - { type: 'value', name: '数量', position: 'right', splitLine: { show: false } }
644   - ],
645   - series: [
646   - { name: '开单业绩', type: 'bar', yAxisIndex: 0, data: [45680, 52890, 48960, 56890, 49860, 59860, 62890, 55860, 51860, 59860, 52890, 48960, 56890, 49860, 59860, 62890, 55860, 51860, 59860, 52890, 48960, 56890, 49860, 59860, 62890, 55860, 51860, 59860, 52890, 48960], itemStyle: { color: '#409EFF' }, barWidth: '30%' },
647   - { name: '消耗业绩', type: 'bar', yAxisIndex: 0, data: [32890, 38960, 35280, 41250, 36580, 43280, 45680, 40250, 37580, 43280, 38960, 35280, 41250, 36580, 43280, 45680, 40250, 37580, 43280, 38960, 35280, 41250, 36580, 43280, 45680, 40250, 37580, 43280, 38960, 35280], itemStyle: { color: '#67C23A' }, barWidth: '30%' },
648   - { name: '人头数', type: 'line', yAxisIndex: 1, data: [45, 52, 48, 56, 49, 58, 62, 55, 51, 59, 52, 48, 56, 49, 58, 62, 55, 51, 59, 52, 48, 56, 49, 58, 62, 55, 51, 59, 52, 48], itemStyle: { color: '#F56C6C' }, lineStyle: { width: 2 }, symbol: 'circle', symbolSize: 6 },
649   - { name: '人次', type: 'line', yAxisIndex: 1, data: [128, 145, 132, 156, 138, 162, 178, 152, 142, 165, 145, 132, 156, 138, 162, 178, 152, 142, 165, 145, 132, 156, 138, 162, 178, 152, 142, 165, 145, 132], itemStyle: { color: '#E6A23C' }, lineStyle: { width: 2 }, symbol: 'circle', symbolSize: 6 },
650   - { name: '项目数', type: 'line', yAxisIndex: 1, data: [256, 289, 268, 312, 278, 325, 356, 304, 284, 330, 289, 268, 312, 278, 325, 356, 304, 284, 330, 289, 268, 312, 278, 325, 356, 304, 284, 330, 289, 268], itemStyle: { color: '#909399' }, lineStyle: { width: 2 }, symbol: 'circle', symbolSize: 6 }
651   - ]
652   - }
653   - this.dailyChart.setOption(option)
654   - },
655 630 renderCompareChart() {
656 631 if (!this.$refs.compareChart) return
657 632 this.compareChart = echarts.init(this.$refs.compareChart)
... ... @@ -864,7 +839,6 @@ export default {
864 839 handleResize() {
865 840 if (this.trendChart) this.trendChart.resize()
866 841 if (this.categoryChart) this.categoryChart.resize()
867   - if (this.dailyChart) this.dailyChart.resize()
868 842 if (this.compareChart) this.compareChart.resize()
869 843 if (this.stackedChart) this.stackedChart.resize()
870 844 if (this.funnelChart) this.funnelChart.resize()
... ... @@ -1150,6 +1124,7 @@ export default {
1150 1124 grid-template-columns: 1fr 400px;
1151 1125 gap: 20px;
1152 1126 margin-bottom: 20px;
  1127 + align-items: start;
1153 1128  
1154 1129 .content-left {
1155 1130 display: flex;
... ...
antis-ncc-admin/src/views/statisticsList/form21.vue
... ... @@ -270,6 +270,17 @@
270 270 </el-tag>
271 271 </template>
272 272 </el-table-column>
  273 + <el-table-column prop="healthCoachName" label="健康师" min-width="120" sortable="custom">
  274 + <template slot-scope="scope">
  275 + <i class="el-icon-user-solid" style="margin-right: 4px; color: #F56C6C;"></i>
  276 + <span>{{ scope.row.healthCoachName || '无' }}</span>
  277 + </template>
  278 + </el-table-column>
  279 + <el-table-column prop="healthCoachPerformance" label="健康师业绩" min-width="130" sortable="custom">
  280 + <template slot-scope="scope">
  281 + <span class="amount-value">¥{{ formatMoney(scope.row.healthCoachPerformance) }}</span>
  282 + </template>
  283 + </el-table-column>
273 284 </NCC-table>
274 285 <pagination
275 286 v-show="total > 0"
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqHytkHytk/RefundDetailListOutput.cs
... ... @@ -81,6 +81,21 @@ namespace NCC.Extend.Entitys.Dto.LqHytkHytk
81 81 /// 品项分类
82 82 /// </summary>
83 83 public string itemCategory { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 健康师账号
  87 + /// </summary>
  88 + public string healthCoachId { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 健康师姓名
  92 + /// </summary>
  93 + public string healthCoachName { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 健康师业绩
  97 + /// </summary>
  98 + public decimal healthCoachPerformance { get; set; }
84 99 }
85 100 }
86 101  
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs
... ... @@ -27,6 +27,7 @@ using NCC.Extend.Entitys.lq_hytk_jksyj;
27 27 using NCC.Extend.Entitys.lq_hytk_kjbsyj;
28 28 using NCC.Extend.Entitys.lq_hytk_mx;
29 29 using NCC.Extend.Entitys.lq_kd_pxmx;
  30 +using NCC.Extend.Entitys.lq_kd_jksyj;
30 31 using NCC.Extend.Entitys.lq_xmzl;
31 32 using NCC.Extend.Entitys.lq_khxx;
32 33 using NCC.Extend.Entitys.lq_mdxx;
... ... @@ -915,7 +916,43 @@ namespace NCC.Extend.LqHytkHytk
915 916 memberDict = members.ToDictionary(x => x.Id, x => x.Sjh ?? "");
916 917 }
917 918  
918   - // 5. 组装数据
  919 + // 5. 批量查询健康师业绩信息
  920 + var billingItemIds = mxList.Select(x => x.BillingItemId).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  921 + var healthCoachDict = new Dictionary<string, (string Id, string Name, decimal Performance)>();
  922 + if (billingItemIds.Any())
  923 + {
  924 + // 通过开单品项明细ID查询开单品项明细,获取开单编号和品项明细ID
  925 + var pxmxList = await _db.Queryable<LqKdPxmxEntity>()
  926 + .Where(x => billingItemIds.Contains(x.Id))
  927 + .Select(x => new { x.Id, x.Glkdbh })
  928 + .ToListAsync();
  929 +
  930 + if (pxmxList.Any())
  931 + {
  932 + var glkdbhList = pxmxList.Select(x => x.Glkdbh).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  933 + var pxmxIdList = pxmxList.Select(x => x.Id).Distinct().ToList();
  934 +
  935 + // 通过开单编号和品项明细ID查询健康师业绩
  936 + var jksyjList = await _db.Queryable<LqKdJksyjEntity>()
  937 + .Where(x => glkdbhList.Contains(x.Glkdbh) && pxmxIdList.Contains(x.Kdpxid) && x.IsEffective == 1)
  938 + .Select(x => new { x.Glkdbh, x.Kdpxid, x.Jkszh, x.Jksxm, x.Jksyj })
  939 + .ToListAsync();
  940 +
  941 + // 建立开单品项明细ID到健康师信息的映射
  942 + foreach (var jksyj in jksyjList)
  943 + {
  944 + // 找到对应的开单品项明细ID
  945 + var pxmxId = pxmxList.FirstOrDefault(x => x.Glkdbh == jksyj.Glkdbh && x.Id == jksyj.Kdpxid)?.Id;
  946 + if (!string.IsNullOrEmpty(pxmxId))
  947 + {
  948 + var jksyjValue = decimal.TryParse(jksyj.Jksyj, out var perf) ? perf : 0;
  949 + healthCoachDict[pxmxId] = (jksyj.Jkszh ?? "", jksyj.Jksxm ?? "", jksyjValue);
  950 + }
  951 + }
  952 + }
  953 + }
  954 +
  955 + // 6. 组装数据
919 956 var resultList = new List<RefundDetailListOutput>();
920 957 foreach (var mx in mxList)
921 958 {
... ... @@ -936,6 +973,11 @@ namespace NCC.Extend.LqHytkHytk
936 973 var memberId = mx.MemberId ?? (refundInfo?.Hy);
937 974 var refundTime = mx.Tksj ?? refundInfo?.Tksj;
938 975  
  976 + // 获取健康师信息
  977 + var healthCoachInfo = !string.IsNullOrEmpty(mx.BillingItemId) && healthCoachDict.ContainsKey(mx.BillingItemId)
  978 + ? healthCoachDict[mx.BillingItemId]
  979 + : (Id: "", Name: "", Performance: 0m);
  980 +
939 981 resultList.Add(new RefundDetailListOutput
940 982 {
941 983 storeId = refundInfo?.Md,
... ... @@ -952,11 +994,14 @@ namespace NCC.Extend.LqHytkHytk
952 994 performanceType = mx.PerformanceType,
953 995 beautyType = mx.BeautyType,
954 996 sourceType = mx.SourceType,
955   - itemCategory = mx.ItemCategory
  997 + itemCategory = mx.ItemCategory,
  998 + healthCoachId = healthCoachInfo.Id,
  999 + healthCoachName = healthCoachInfo.Name,
  1000 + healthCoachPerformance = healthCoachInfo.Performance
956 1001 });
957 1002 }
958 1003  
959   - // 6. 排序
  1004 + // 7. 排序
960 1005 var sidx = string.IsNullOrEmpty(input.sidx) ? "refundTime" : input.sidx;
961 1006 var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
962 1007  
... ... @@ -969,7 +1014,7 @@ namespace NCC.Extend.LqHytkHytk
969 1014 resultList = resultList.OrderBy(x => GetPropertyValue(x, sidx)).ToList();
970 1015 }
971 1016  
972   - // 7. 分页
  1017 + // 8. 分页
973 1018 var total = resultList.Count;
974 1019 var skip = (input.currentPage - 1) * input.pageSize;
975 1020 var pagedList = resultList.Skip(skip).Take(input.pageSize).ToList();
... ... @@ -1069,7 +1114,43 @@ namespace NCC.Extend.LqHytkHytk
1069 1114 memberDict = members.ToDictionary(x => x.Id, x => x.Sjh ?? "");
1070 1115 }
1071 1116  
1072   - // 5. 组装数据
  1117 + // 5. 批量查询健康师业绩信息
  1118 + var billingItemIds = mxList.Select(x => x.BillingItemId).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  1119 + var healthCoachDict = new Dictionary<string, (string Id, string Name, decimal Performance)>();
  1120 + if (billingItemIds.Any())
  1121 + {
  1122 + // 通过开单品项明细ID查询开单品项明细,获取开单编号和品项明细ID
  1123 + var pxmxList = await _db.Queryable<LqKdPxmxEntity>()
  1124 + .Where(x => billingItemIds.Contains(x.Id))
  1125 + .Select(x => new { x.Id, x.Glkdbh })
  1126 + .ToListAsync();
  1127 +
  1128 + if (pxmxList.Any())
  1129 + {
  1130 + var glkdbhList = pxmxList.Select(x => x.Glkdbh).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  1131 + var pxmxIdList = pxmxList.Select(x => x.Id).Distinct().ToList();
  1132 +
  1133 + // 通过开单编号和品项明细ID查询健康师业绩
  1134 + var jksyjList = await _db.Queryable<LqKdJksyjEntity>()
  1135 + .Where(x => glkdbhList.Contains(x.Glkdbh) && pxmxIdList.Contains(x.Kdpxid) && x.IsEffective == 1)
  1136 + .Select(x => new { x.Glkdbh, x.Kdpxid, x.Jkszh, x.Jksxm, x.Jksyj })
  1137 + .ToListAsync();
  1138 +
  1139 + // 建立开单品项明细ID到健康师信息的映射
  1140 + foreach (var jksyj in jksyjList)
  1141 + {
  1142 + // 找到对应的开单品项明细ID
  1143 + var pxmxId = pxmxList.FirstOrDefault(x => x.Glkdbh == jksyj.Glkdbh && x.Id == jksyj.Kdpxid)?.Id;
  1144 + if (!string.IsNullOrEmpty(pxmxId))
  1145 + {
  1146 + var jksyjValue = decimal.TryParse(jksyj.Jksyj, out var perf) ? perf : 0;
  1147 + healthCoachDict[pxmxId] = (jksyj.Jkszh ?? "", jksyj.Jksxm ?? "", jksyjValue);
  1148 + }
  1149 + }
  1150 + }
  1151 + }
  1152 +
  1153 + // 6. 组装数据
1073 1154 var resultList = new List<RefundDetailListOutput>();
1074 1155 foreach (var mx in mxList)
1075 1156 {
... ... @@ -1090,6 +1171,11 @@ namespace NCC.Extend.LqHytkHytk
1090 1171 var memberId = mx.MemberId ?? (refundInfo?.Hy);
1091 1172 var refundTime = mx.Tksj ?? refundInfo?.Tksj;
1092 1173  
  1174 + // 获取健康师信息
  1175 + var healthCoachInfo = !string.IsNullOrEmpty(mx.BillingItemId) && healthCoachDict.ContainsKey(mx.BillingItemId)
  1176 + ? healthCoachDict[mx.BillingItemId]
  1177 + : (Id: "", Name: "", Performance: 0m);
  1178 +
1093 1179 resultList.Add(new RefundDetailListOutput
1094 1180 {
1095 1181 storeId = refundInfo?.Md,
... ... @@ -1106,11 +1192,14 @@ namespace NCC.Extend.LqHytkHytk
1106 1192 performanceType = mx.PerformanceType,
1107 1193 beautyType = mx.BeautyType,
1108 1194 sourceType = mx.SourceType,
1109   - itemCategory = mx.ItemCategory
  1195 + itemCategory = mx.ItemCategory,
  1196 + healthCoachId = healthCoachInfo.Id,
  1197 + healthCoachName = healthCoachInfo.Name,
  1198 + healthCoachPerformance = healthCoachInfo.Performance
1110 1199 });
1111 1200 }
1112 1201  
1113   - // 6. 排序
  1202 + // 7. 排序
1114 1203 var sidx = string.IsNullOrEmpty(input.sidx) ? "refundTime" : input.sidx;
1115 1204 var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
1116 1205  
... ...