Commit 430949ed5ffc1c4a2184350f5122200e31c566d7

Authored by “wangming”
1 parent 9bad463b

feat: 优化门店数据结构分析功能

- 创建单独的门店基础信息接口(get-store-base-info),支持单独获取门店基础信息和归属信息
- 修复门店数据结构分析接口的类型转换问题
- 优化前端调用逻辑,支持分步骤获取数据
- 添加门店归属信息显示(事业部、科技部、教育部、大项目部)
- 添加性能优化相关的SQL索引脚本
antis-ncc-admin/src/components/store-data-analysis-dialog.vue
@@ -33,6 +33,22 @@ @@ -33,6 +33,22 @@
33 <i class="el-icon-calendar"></i> 33 <i class="el-icon-calendar"></i>
34 <span>开业:{{ formatDate(baseInfo.OpenTime) }}</span> 34 <span>开业:{{ formatDate(baseInfo.OpenTime) }}</span>
35 </div> 35 </div>
  36 + <div class="meta-item" v-if="baseInfo.BusinessUnit">
  37 + <i class="el-icon-office-building"></i>
  38 + <span>事业部:{{ baseInfo.BusinessUnit }}</span>
  39 + </div>
  40 + <div class="meta-item" v-if="baseInfo.TechDepartment">
  41 + <i class="el-icon-cpu"></i>
  42 + <span>科技部:{{ baseInfo.TechDepartment }}</span>
  43 + </div>
  44 + <div class="meta-item" v-if="baseInfo.EducationDepartment">
  45 + <i class="el-icon-reading"></i>
  46 + <span>教育部:{{ baseInfo.EducationDepartment }}</span>
  47 + </div>
  48 + <div class="meta-item" v-if="baseInfo.MajorProjectDepartment">
  49 + <i class="el-icon-folder-opened"></i>
  50 + <span>大项目部:{{ baseInfo.MajorProjectDepartment }}</span>
  51 + </div>
36 </div> 52 </div>
37 </div> 53 </div>
38 <div class="header-right"> 54 <div class="header-right">
@@ -501,34 +517,42 @@ export default { @@ -501,34 +517,42 @@ export default {
501 517
502 this.loading = true 518 this.loading = true
503 try { 519 try {
504 - // 第一步:先获取门店基础信息 520 + // 准备请求参数(所有接口共用)
  521 + const requestParams = {
  522 + storeId: this.storeId,
  523 + statisticsMonth: this.statisticsMonth || this.getCurrentMonth()
  524 + }
  525 +
  526 + // 第一步:获取门店基础信息(单独接口)
505 console.log('开始获取门店基础信息,门店ID:', this.storeId) 527 console.log('开始获取门店基础信息,门店ID:', this.storeId)
  528 +
506 const baseInfoRes = await request({ 529 const baseInfoRes = await request({
507 - url: `/api/Extend/LqMdxx/${this.storeId}`,  
508 - method: 'GET' 530 + url: '/api/Extend/LqReport/get-store-base-info',
  531 + method: 'POST',
  532 + data: requestParams
509 }) 533 })
510 534
511 console.log('门店基础信息接口响应:', baseInfoRes) 535 console.log('门店基础信息接口响应:', baseInfoRes)
512 536
513 // 处理门店基础信息 537 // 处理门店基础信息
514 if (baseInfoRes && baseInfoRes.code === 200 && baseInfoRes.data) { 538 if (baseInfoRes && baseInfoRes.code === 200 && baseInfoRes.data) {
515 - const data = baseInfoRes.data  
516 - console.log('门店数据:', data) 539 + const baseInfoData = baseInfoRes.data
  540 + console.log('门店基础信息数据:', baseInfoData)
517 this.baseInfo = { 541 this.baseInfo = {
518 - StoreId: data.id || this.storeId,  
519 - StoreName: data.dm || '—',  
520 - StoreCode: data.mdbm || '',  
521 - City: data.cs || '',  
522 - Address: data.dz || '',  
523 - Status: data.zxzt || '',  
524 - OpenTime: data.kysj || null,  
525 - EmployeeCount: data.zzrs || 0,  
526 - StoreType: (data.storeType != null ? data.storeType.toString() : '') || '',  
527 - StoreCategory: (data.storeCategory != null ? data.storeCategory.toString() : '') || '',  
528 - BusinessUnit: data.syb || '',  
529 - EducationDepartment: data.jyb || '',  
530 - TechDepartment: data.kjb || '',  
531 - MajorProjectDepartment: data.dxmb || '' 542 + StoreId: baseInfoData.StoreId || this.storeId,
  543 + StoreName: baseInfoData.StoreName || '—',
  544 + StoreCode: baseInfoData.StoreCode || '',
  545 + City: baseInfoData.City || '',
  546 + Address: baseInfoData.Address || '',
  547 + Status: baseInfoData.Status || '',
  548 + OpenTime: baseInfoData.OpenTime || null,
  549 + EmployeeCount: baseInfoData.EmployeeCount || 0,
  550 + StoreType: baseInfoData.StoreType || '',
  551 + StoreCategory: baseInfoData.StoreCategory || '',
  552 + BusinessUnit: baseInfoData.BusinessUnit || '',
  553 + EducationDepartment: baseInfoData.EducationDepartment || '',
  554 + TechDepartment: baseInfoData.TechDepartment || '',
  555 + MajorProjectDepartment: baseInfoData.MajorProjectDepartment || ''
532 } 556 }
533 console.log('处理后的门店基础信息:', this.baseInfo) 557 console.log('处理后的门店基础信息:', this.baseInfo)
534 } else { 558 } else {
@@ -538,14 +562,37 @@ export default { @@ -538,14 +562,37 @@ export default {
538 } else { 562 } else {
539 this.$message.error('获取门店基础信息失败: 接口返回异常') 563 this.$message.error('获取门店基础信息失败: 接口返回异常')
540 } 564 }
  565 + // 如果获取失败,尝试从门店接口获取基本信息(但归属信息会是ID)
  566 + try {
  567 + const fallbackRes = await request({
  568 + url: `/api/Extend/LqMdxx/${this.storeId}`,
  569 + method: 'GET'
  570 + })
  571 + if (fallbackRes && fallbackRes.code === 200 && fallbackRes.data) {
  572 + const data = fallbackRes.data
  573 + this.baseInfo = {
  574 + StoreId: data.id || this.storeId,
  575 + StoreName: data.dm || '—',
  576 + StoreCode: data.mdbm || '',
  577 + City: data.cs || '',
  578 + Address: data.dz || '',
  579 + Status: data.zxzt || '',
  580 + OpenTime: data.kysj || null,
  581 + EmployeeCount: data.zzrs || 0,
  582 + StoreType: (data.storeType != null ? data.storeType.toString() : '') || '',
  583 + StoreCategory: (data.storeCategory != null ? data.storeCategory.toString() : '') || '',
  584 + BusinessUnit: '', // 备用方案:无法获取名称时留空
  585 + EducationDepartment: '',
  586 + TechDepartment: '',
  587 + MajorProjectDepartment: ''
  588 + }
  589 + }
  590 + } catch (e) {
  591 + console.error('备用方案也失败:', e)
  592 + }
541 } 593 }
542 594
543 // 第二步:获取业绩概览 595 // 第二步:获取业绩概览
544 - const requestParams = {  
545 - storeId: this.storeId,  
546 - statisticsMonth: this.statisticsMonth || this.getCurrentMonth()  
547 - }  
548 -  
549 console.log('开始获取业绩概览,参数:', requestParams) 596 console.log('开始获取业绩概览,参数:', requestParams)
550 const performanceRes = await request({ 597 const performanceRes = await request({
551 url: '/api/Extend/LqReport/get-store-performance-overview', 598 url: '/api/Extend/LqReport/get-store-performance-overview',
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StoreDataAnalysisOutput.cs
@@ -36,7 +36,7 @@ namespace NCC.Extend.Entitys.Dto.LqReport @@ -36,7 +36,7 @@ namespace NCC.Extend.Entitys.Dto.LqReport
36 /// <summary> 36 /// <summary>
37 /// 品项分析 37 /// 品项分析
38 /// </summary> 38 /// </summary>
39 - public ItemAnalysis Item { get; set; } 39 + public ItemAnalysis? Item { get; set; }
40 40
41 /// <summary> 41 /// <summary>
42 /// 每日运营数据(当月) 42 /// 每日运营数据(当月)
netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs
@@ -1130,28 +1130,47 @@ namespace NCC.Extend @@ -1130,28 +1130,47 @@ namespace NCC.Extend
1130 ? DateTime.Now 1130 ? DateTime.Now
1131 : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59); 1131 : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
1132 1132
1133 - // 实时查询金三角业绩  
1134 - var sql = $@" 1133 + // 使用参数化查询,优化性能
  1134 + var parameters = new List<SugarParameter>
  1135 + {
  1136 + new SugarParameter("@StatisticsMonth", input.StatisticsMonth),
  1137 + new SugarParameter("@StartDate", startDate),
  1138 + new SugarParameter("@EndDate", endDateForQuery)
  1139 + };
  1140 +
  1141 + // 优化SQL:使用子查询先聚合数据,减少JOIN的数据量
  1142 + // 1. 先过滤有效业绩数据并按金三角ID聚合(减少数据量)
  1143 + // 2. 再关联金三角设定和门店信息
  1144 + // 这样可以避免在大数据量下进行复杂的多表JOIN
  1145 + var sql = @"
1135 SELECT 1146 SELECT
1136 jsj.F_Id as F_GoldTriangleId, 1147 jsj.F_Id as F_GoldTriangleId,
1137 jsj.jsj as F_GoldTriangleName, 1148 jsj.jsj as F_GoldTriangleName,
1138 jsj.md as F_StoreId, 1149 jsj.md as F_StoreId,
1139 - mdxx.dm as F_StoreName,  
1140 - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) as F_TotalPerformance,  
1141 - COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount 1150 + COALESCE(mdxx.dm, '') as F_StoreName,
  1151 + COALESCE(performance.F_TotalPerformance, 0) as F_TotalPerformance,
  1152 + COALESCE(performance.F_OrderCount, 0) as F_OrderCount
1142 FROM lq_ycsd_jsj jsj 1153 FROM lq_ycsd_jsj jsj
1143 - INNER JOIN lq_jinsanjiao_user jsjUser ON jsj.F_Id = jsjUser.jsj_id  
1144 - AND jsjUser.status = 'ACTIVE'  
1145 - AND jsjUser.F_DeleteMark = 0  
1146 - INNER JOIN lq_kd_jksyj jksyj ON jsjUser.user_id = jksyj.jkszh  
1147 - INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id 1154 + LEFT JOIN (
  1155 + -- 子查询:先过滤并聚合有效业绩数据,减少JOIN数据量
  1156 + SELECT
  1157 + jsjUser.jsj_id,
  1158 + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) as F_TotalPerformance,
  1159 + COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount
  1160 + FROM lq_kd_jksyj jksyj
  1161 + INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id
  1162 + INNER JOIN lq_jinsanjiao_user jsjUser ON jksyj.jkszh = jsjUser.user_id
  1163 + WHERE jksyj.F_IsEffective = 1
  1164 + AND kd.F_IsEffective = 1
  1165 + AND kd.kdrq >= @StartDate
  1166 + AND kd.kdrq <= @EndDate
  1167 + AND jsjUser.status = 'ACTIVE'
  1168 + AND jsjUser.F_DeleteMark = 0
  1169 + GROUP BY jsjUser.jsj_id
  1170 + ) performance ON jsj.F_Id = performance.jsj_id
1148 LEFT JOIN lq_mdxx mdxx ON jsj.md = mdxx.F_Id 1171 LEFT JOIN lq_mdxx mdxx ON jsj.md = mdxx.F_Id
1149 - WHERE jsj.yf = '{input.StatisticsMonth}'  
1150 - AND jksyj.F_IsEffective = 1  
1151 - AND kd.F_IsEffective = 1  
1152 - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}'  
1153 - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'  
1154 - GROUP BY jsj.F_Id, jsj.jsj, jsj.md, mdxx.dm 1172 + WHERE jsj.yf = @StatisticsMonth
  1173 + AND performance.jsj_id IS NOT NULL
1155 ORDER BY F_TotalPerformance DESC"; 1174 ORDER BY F_TotalPerformance DESC";
1156 1175
1157 if (input.TopCount > 0) 1176 if (input.TopCount > 0)
@@ -1159,7 +1178,7 @@ namespace NCC.Extend @@ -1159,7 +1178,7 @@ namespace NCC.Extend
1159 sql += $" LIMIT {input.TopCount}"; 1178 sql += $" LIMIT {input.TopCount}";
1160 } 1179 }
1161 1180
1162 - var data = await _db.Ado.SqlQueryAsync<dynamic>(sql); 1181 + var data = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters);
1163 1182
1164 if (data == null || !data.Any()) 1183 if (data == null || !data.Any())
1165 { 1184 {
@@ -1320,31 +1339,39 @@ namespace NCC.Extend @@ -1320,31 +1339,39 @@ namespace NCC.Extend
1320 ? DateTime.Now 1339 ? DateTime.Now
1321 : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59); 1340 : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
1322 1341
  1342 + // 使用参数化查询,优化性能
  1343 + var parameters = new List<SugarParameter>
  1344 + {
  1345 + new SugarParameter("@StartDate", startDate),
  1346 + new SugarParameter("@EndDate", endDateForQuery),
  1347 + new SugarParameter("@StatisticsMonth", statisticsMonth)
  1348 + };
  1349 +
1323 // 1. 门店业绩汇总(实时查询) 1350 // 1. 门店业绩汇总(实时查询)
1324 // 先查询开单业绩 1351 // 先查询开单业绩
1325 - var billingSql = $@" 1352 + var billingSql = @"
1326 SELECT 1353 SELECT
1327 COUNT(DISTINCT kd.djmd) as StoreCount, 1354 COUNT(DISTINCT kd.djmd) as StoreCount,
1328 COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as TotalBillingAmount 1355 COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as TotalBillingAmount
1329 FROM lq_kd_kdjlb kd 1356 FROM lq_kd_kdjlb kd
1330 WHERE kd.F_IsEffective = 1 1357 WHERE kd.F_IsEffective = 1
1331 - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}'  
1332 - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; 1358 + AND kd.kdrq >= @StartDate
  1359 + AND kd.kdrq <= @EndDate";
1333 1360
1334 - var billingResult = await _db.Ado.SqlQueryAsync<dynamic>(billingSql); 1361 + var billingResult = await _db.Ado.SqlQueryAsync<dynamic>(billingSql, parameters);
1335 var storeCount = Convert.ToInt32(billingResult?.FirstOrDefault()?.StoreCount ?? 0); 1362 var storeCount = Convert.ToInt32(billingResult?.FirstOrDefault()?.StoreCount ?? 0);
1336 var totalBillingAmount = Convert.ToDecimal(billingResult?.FirstOrDefault()?.TotalBillingAmount ?? 0m); 1363 var totalBillingAmount = Convert.ToDecimal(billingResult?.FirstOrDefault()?.TotalBillingAmount ?? 0m);
1337 1364
1338 // 查询退卡金额 1365 // 查询退卡金额
1339 - var refundSql = $@" 1366 + var refundSql = @"
1340 SELECT 1367 SELECT
1341 COALESCE(SUM(CAST(COALESCE(F_ActualRefundAmount, tkje, 0) AS DECIMAL(18,2))), 0) as TotalRefundAmount 1368 COALESCE(SUM(CAST(COALESCE(F_ActualRefundAmount, tkje, 0) AS DECIMAL(18,2))), 0) as TotalRefundAmount
1342 FROM lq_hytk_hytk 1369 FROM lq_hytk_hytk
1343 WHERE F_IsEffective = 1 1370 WHERE F_IsEffective = 1
1344 - AND tksj >= '{startDate:yyyy-MM-dd 00:00:00}'  
1345 - AND tksj <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; 1371 + AND tksj >= @StartDate
  1372 + AND tksj <= @EndDate";
1346 1373
1347 - var refundResult = await _db.Ado.SqlQueryAsync<dynamic>(refundSql); 1374 + var refundResult = await _db.Ado.SqlQueryAsync<dynamic>(refundSql, parameters);
1348 var totalRefundAmount = Convert.ToDecimal(refundResult?.FirstOrDefault()?.TotalRefundAmount ?? 0m); 1375 var totalRefundAmount = Convert.ToDecimal(refundResult?.FirstOrDefault()?.TotalRefundAmount ?? 0m);
1349 1376
1350 var totalPerformance = totalBillingAmount - totalRefundAmount; 1377 var totalPerformance = totalBillingAmount - totalRefundAmount;
@@ -1363,7 +1390,7 @@ namespace NCC.Extend @@ -1363,7 +1390,7 @@ namespace NCC.Extend
1363 }.Select(x => (dynamic)x).ToList(); 1390 }.Select(x => (dynamic)x).ToList();
1364 1391
1365 // 2. 健康师业绩汇总(实时查询) 1392 // 2. 健康师业绩汇总(实时查询)
1366 - var healthCoachPerformanceSql = $@" 1393 + var healthCoachPerformanceSql = @"
1367 SELECT 1394 SELECT
1368 COUNT(DISTINCT jks.jkszh) as HealthCoachCount, 1395 COUNT(DISTINCT jks.jkszh) as HealthCoachCount,
1369 COALESCE(SUM(CAST(jks.jksyj AS DECIMAL(18,2))), 0) as TotalPerformance, 1396 COALESCE(SUM(CAST(jks.jksyj AS DECIMAL(18,2))), 0) as TotalPerformance,
@@ -1378,38 +1405,47 @@ namespace NCC.Extend @@ -1378,38 +1405,47 @@ namespace NCC.Extend
1378 INNER JOIN lq_kd_kdjlb kd ON jks.glkdbh = kd.F_Id 1405 INNER JOIN lq_kd_kdjlb kd ON jks.glkdbh = kd.F_Id
1379 WHERE jks.F_IsEffective = 1 1406 WHERE jks.F_IsEffective = 1
1380 AND kd.F_IsEffective = 1 1407 AND kd.F_IsEffective = 1
1381 - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}'  
1382 - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; 1408 + AND kd.kdrq >= @StartDate
  1409 + AND kd.kdrq <= @EndDate";
1383 1410
1384 - var healthCoachPerformance = await _db.Ado.SqlQueryAsync<dynamic>(healthCoachPerformanceSql); 1411 + var healthCoachPerformance = await _db.Ado.SqlQueryAsync<dynamic>(healthCoachPerformanceSql, parameters);
1385 1412
1386 - // 3. 金三角业绩汇总(实时查询)  
1387 - var goldTrianglePerformanceSql = $@" 1413 + // 3. 金三角业绩汇总(实时查询)- 使用子查询优化性能
  1414 + var goldTrianglePerformanceSql = @"
1388 SELECT 1415 SELECT
1389 COUNT(DISTINCT jsj.F_Id) as GoldTriangleCount, 1416 COUNT(DISTINCT jsj.F_Id) as GoldTriangleCount,
1390 - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) as TotalPerformance,  
1391 - COUNT(DISTINCT jksyj.glkdbh) as TotalOrderCount, 1417 + COALESCE(SUM(performance.F_TotalPerformance), 0) as TotalPerformance,
  1418 + COALESCE(SUM(performance.F_OrderCount), 0) as TotalOrderCount,
1392 CASE 1419 CASE
1393 WHEN COUNT(DISTINCT jsj.F_Id) > 0 THEN 1420 WHEN COUNT(DISTINCT jsj.F_Id) > 0 THEN
1394 - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) / COUNT(DISTINCT jsj.F_Id) 1421 + COALESCE(SUM(performance.F_TotalPerformance), 0) / COUNT(DISTINCT jsj.F_Id)
1395 ELSE 0 1422 ELSE 0
1396 END as AvgPerformance 1423 END as AvgPerformance
1397 FROM lq_ycsd_jsj jsj 1424 FROM lq_ycsd_jsj jsj
1398 - INNER JOIN lq_jinsanjiao_user jsjUser ON jsj.F_Id = jsjUser.jsj_id  
1399 - AND jsjUser.status = 'ACTIVE'  
1400 - AND jsjUser.F_DeleteMark = 0  
1401 - INNER JOIN lq_kd_jksyj jksyj ON jsjUser.user_id = jksyj.jkszh  
1402 - INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id  
1403 - WHERE jsj.yf = '{statisticsMonth}'  
1404 - AND jksyj.F_IsEffective = 1  
1405 - AND kd.F_IsEffective = 1  
1406 - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}'  
1407 - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'";  
1408 -  
1409 - var goldTrianglePerformance = await _db.Ado.SqlQueryAsync<dynamic>(goldTrianglePerformanceSql); 1425 + LEFT JOIN (
  1426 + -- 子查询:先过滤并聚合有效业绩数据,减少JOIN数据量
  1427 + SELECT
  1428 + jsjUser.jsj_id,
  1429 + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) as F_TotalPerformance,
  1430 + COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount
  1431 + FROM lq_kd_jksyj jksyj
  1432 + INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id
  1433 + INNER JOIN lq_jinsanjiao_user jsjUser ON jksyj.jkszh = jsjUser.user_id
  1434 + WHERE jksyj.F_IsEffective = 1
  1435 + AND kd.F_IsEffective = 1
  1436 + AND kd.kdrq >= @StartDate
  1437 + AND kd.kdrq <= @EndDate
  1438 + AND jsjUser.status = 'ACTIVE'
  1439 + AND jsjUser.F_DeleteMark = 0
  1440 + GROUP BY jsjUser.jsj_id
  1441 + ) performance ON jsj.F_Id = performance.jsj_id
  1442 + WHERE jsj.yf = @StatisticsMonth
  1443 + AND performance.jsj_id IS NOT NULL";
  1444 +
  1445 + var goldTrianglePerformance = await _db.Ado.SqlQueryAsync<dynamic>(goldTrianglePerformanceSql, parameters);
1410 1446
1411 // 4. 消耗业绩汇总(实时查询) 1447 // 4. 消耗业绩汇总(实时查询)
1412 - var consumePerformanceSql = $@" 1448 + var consumePerformanceSql = @"
1413 SELECT 1449 SELECT
1414 COUNT(*) as ConsumeRecordCount, 1450 COUNT(*) as ConsumeRecordCount,
1415 COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as TotalConsumePerformance, 1451 COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as TotalConsumePerformance,
@@ -1419,18 +1455,23 @@ namespace NCC.Extend @@ -1419,18 +1455,23 @@ namespace NCC.Extend
1419 FROM lq_xh_hyhk xh 1455 FROM lq_xh_hyhk xh
1420 LEFT JOIN lq_xh_pxmx pxmx ON xh.F_Id = pxmx.F_ConsumeInfoId AND pxmx.F_IsEffective = 1 1456 LEFT JOIN lq_xh_pxmx pxmx ON xh.F_Id = pxmx.F_ConsumeInfoId AND pxmx.F_IsEffective = 1
1421 WHERE xh.F_IsEffective = 1 1457 WHERE xh.F_IsEffective = 1
1422 - AND xh.hksj >= '{startDate:yyyy-MM-dd 00:00:00}'  
1423 - AND xh.hksj <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; 1458 + AND xh.hksj >= @StartDate
  1459 + AND xh.hksj <= @EndDate";
1424 1460
1425 - var consumePerformance = await _db.Ado.SqlQueryAsync<dynamic>(consumePerformanceSql); 1461 + var consumePerformance = await _db.Ado.SqlQueryAsync<dynamic>(consumePerformanceSql, parameters);
1426 1462
1427 // 5. 会员统计汇总 1463 // 5. 会员统计汇总
1428 var lastMonth = DateTime.ParseExact(statisticsMonth, "yyyyMM", null).AddMonths(-1).ToString("yyyyMM"); 1464 var lastMonth = DateTime.ParseExact(statisticsMonth, "yyyyMM", null).AddMonths(-1).ToString("yyyyMM");
1429 - var memberStatisticsSql = $@" 1465 + var memberStatisticsParameters = new List<SugarParameter>
  1466 + {
  1467 + new SugarParameter("@statisticsMonth", statisticsMonth),
  1468 + new SugarParameter("@lastMonth", lastMonth)
  1469 + };
  1470 + var memberStatisticsSql = @"
1430 SELECT 1471 SELECT
1431 COUNT(*) as TotalMembers, 1472 COUNT(*) as TotalMembers,
1432 SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = @statisticsMonth THEN 1 ELSE 0 END) as NewMembersThisMonth, 1473 SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = @statisticsMonth THEN 1 ELSE 0 END) as NewMembersThisMonth,
1433 - SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = '{lastMonth}' THEN 1 ELSE 0 END) as NewMembersLastMonth, 1474 + SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = @lastMonth THEN 1 ELSE 0 END) as NewMembersLastMonth,
1434 SUM(CASE WHEN F_SleepDays IS NULL OR F_SleepDays <= 3 THEN 1 ELSE 0 END) as ActiveMembers0_3, 1475 SUM(CASE WHEN F_SleepDays IS NULL OR F_SleepDays <= 3 THEN 1 ELSE 0 END) as ActiveMembers0_3,
1435 SUM(CASE WHEN F_SleepDays > 3 AND F_SleepDays < 60 THEN 1 ELSE 0 END) as ActiveMembers4_59, 1476 SUM(CASE WHEN F_SleepDays > 3 AND F_SleepDays < 60 THEN 1 ELSE 0 END) as ActiveMembers4_59,
1436 SUM(CASE WHEN F_LastVisitTime IS NOT NULL AND DATEDIFF(NOW(), F_LastVisitTime) <= 30 THEN 1 ELSE 0 END) as ActiveMembers30d, 1477 SUM(CASE WHEN F_LastVisitTime IS NOT NULL AND DATEDIFF(NOW(), F_LastVisitTime) <= 30 THEN 1 ELSE 0 END) as ActiveMembers30d,
@@ -1447,7 +1488,7 @@ namespace NCC.Extend @@ -1447,7 +1488,7 @@ namespace NCC.Extend
1447 WHERE F_IsEffective = 1 1488 WHERE F_IsEffective = 1
1448 AND khlx = '3'"; 1489 AND khlx = '3'";
1449 1490
1450 - var memberStatistics = await _db.Ado.SqlQueryAsync<dynamic>(memberStatisticsSql, new { statisticsMonth }); 1491 + var memberStatistics = await _db.Ado.SqlQueryAsync<dynamic>(memberStatisticsSql, memberStatisticsParameters);
1451 var memberStats = memberStatistics.FirstOrDefault(); 1492 var memberStats = memberStatistics.FirstOrDefault();
1452 1493
1453 // 6. 会员类型分布统计 1494 // 6. 会员类型分布统计
@@ -1496,12 +1537,13 @@ namespace NCC.Extend @@ -1496,12 +1537,13 @@ namespace NCC.Extend
1496 FROM lq_kd_kdjlb kd 1537 FROM lq_kd_kdjlb kd
1497 INNER JOIN lq_khxx kh ON kh.F_Id = kd.kdhy 1538 INNER JOIN lq_khxx kh ON kh.F_Id = kd.kdhy
1498 WHERE kd.F_IsEffective = 1 1539 WHERE kd.F_IsEffective = 1
1499 - AND DATE_FORMAT(kd.kdrq, '%Y%m') = @statisticsMonth 1540 + AND kd.kdrq >= @StartDate
  1541 + AND kd.kdrq <= @EndDate
1500 GROUP BY kh.F_Id, kh.Khmc 1542 GROUP BY kh.F_Id, kh.Khmc
1501 ORDER BY Amount DESC 1543 ORDER BY Amount DESC
1502 LIMIT 1"; 1544 LIMIT 1";
1503 1545
1504 - var topBilling = (await _db.Ado.SqlQueryAsync<dynamic>(topBillingSql, new { statisticsMonth })).FirstOrDefault(); 1546 + var topBilling = (await _db.Ado.SqlQueryAsync<dynamic>(topBillingSql, parameters)).FirstOrDefault();
1505 1547
1506 var topConsumeSql = @" 1548 var topConsumeSql = @"
1507 SELECT 1549 SELECT
@@ -1511,12 +1553,13 @@ namespace NCC.Extend @@ -1511,12 +1553,13 @@ namespace NCC.Extend
1511 FROM lq_xh_hyhk xh 1553 FROM lq_xh_hyhk xh
1512 INNER JOIN lq_khxx kh ON kh.F_Id = xh.hy 1554 INNER JOIN lq_khxx kh ON kh.F_Id = xh.hy
1513 WHERE xh.F_IsEffective = 1 1555 WHERE xh.F_IsEffective = 1
1514 - AND DATE_FORMAT(xh.hksj, '%Y%m') = @statisticsMonth 1556 + AND xh.hksj >= @StartDate
  1557 + AND xh.hksj <= @EndDate
1515 GROUP BY kh.F_Id, kh.Khmc 1558 GROUP BY kh.F_Id, kh.Khmc
1516 ORDER BY Amount DESC 1559 ORDER BY Amount DESC
1517 LIMIT 1"; 1560 LIMIT 1";
1518 1561
1519 - var topConsume = (await _db.Ado.SqlQueryAsync<dynamic>(topConsumeSql, new { statisticsMonth })).FirstOrDefault(); 1562 + var topConsume = (await _db.Ado.SqlQueryAsync<dynamic>(topConsumeSql, parameters)).FirstOrDefault();
1520 1563
1521 var totalMembers = Convert.ToInt32(memberStats?.TotalMembers ?? 0); 1564 var totalMembers = Convert.ToInt32(memberStats?.TotalMembers ?? 0);
1522 var activeMembers0_3 = Convert.ToInt32(memberStats?.ActiveMembers0_3 ?? 0); 1565 var activeMembers0_3 = Convert.ToInt32(memberStats?.ActiveMembers0_3 ?? 0);
@@ -4514,6 +4557,125 @@ namespace NCC.Extend @@ -4514,6 +4557,125 @@ namespace NCC.Extend
4514 4557
4515 #region 门店数据结构分析 4558 #region 门店数据结构分析
4516 /// <summary> 4559 /// <summary>
  4560 + /// 获取门店基础信息
  4561 + /// </summary>
  4562 + /// <remarks>
  4563 + /// 获取单个门店的基础信息,包括门店基本信息、归属信息(事业部、科技部、教育部、大项目部)等
  4564 + ///
  4565 + /// 示例请求:
  4566 + /// ```json
  4567 + /// {
  4568 + /// "storeId": "门店ID",
  4569 + /// "statisticsMonth": "202512"
  4570 + /// }
  4571 + /// ```
  4572 + ///
  4573 + /// 参数说明:
  4574 + /// - storeId: 门店ID(必填)
  4575 + /// - statisticsMonth: 统计月份(YYYYMM格式,可选,不传则默认为当前月份)
  4576 + /// </remarks>
  4577 + /// <param name="input">查询参数</param>
  4578 + /// <returns>门店基础信息</returns>
  4579 + /// <response code="200">成功返回基础信息</response>
  4580 + /// <response code="400">参数错误</response>
  4581 + /// <response code="500">服务器错误</response>
  4582 + [HttpPost("get-store-base-info")]
  4583 + public async Task<StoreBaseInfo> GetStoreBaseInfo(StoreDataAnalysisInput input)
  4584 + {
  4585 + try
  4586 + {
  4587 + if (string.IsNullOrWhiteSpace(input.StoreId))
  4588 + {
  4589 + throw NCCException.Oh("门店ID不能为空");
  4590 + }
  4591 +
  4592 + // 确定统计月份
  4593 + var statisticsMonth = input.StatisticsMonth;
  4594 + if (string.IsNullOrWhiteSpace(statisticsMonth))
  4595 + {
  4596 + statisticsMonth = DateTime.Now.ToString("yyyyMM");
  4597 + }
  4598 +
  4599 + // 1. 获取门店基础信息
  4600 + var store = await _db.Queryable<LqMdxxEntity>()
  4601 + .Where(x => x.Id == input.StoreId)
  4602 + .FirstAsync();
  4603 +
  4604 + if (store == null)
  4605 + {
  4606 + throw NCCException.Oh("门店不存在");
  4607 + }
  4608 +
  4609 + // 2. 获取本月归属信息(优先从门店目标表获取,如果不存在则从门店表获取)
  4610 + var target = await _db.Queryable<LqMdTargetEntity>()
  4611 + .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth)
  4612 + .FirstAsync();
  4613 +
  4614 + string businessUnitName = "";
  4615 + string techDepartmentName = "";
  4616 + string educationDepartmentName = "";
  4617 + string majorProjectDepartmentName = "";
  4618 +
  4619 + // 从门店目标表获取归属ID
  4620 + var businessUnitId = target?.BusinessUnit ?? store.Syb;
  4621 + var techDepartmentId = target?.TechDepartment ?? store.Kjb;
  4622 + var educationDepartmentId = target?.EducationDepartment ?? store.Jyb;
  4623 + var majorProjectDepartmentId = target?.MajorProjectDepartment ?? store.Dxmb;
  4624 +
  4625 + // 关联组织表获取归属名称(批量查询优化性能)
  4626 + var organizeIds = new List<string>();
  4627 + if (!string.IsNullOrEmpty(businessUnitId)) organizeIds.Add(businessUnitId);
  4628 + if (!string.IsNullOrEmpty(techDepartmentId)) organizeIds.Add(techDepartmentId);
  4629 + if (!string.IsNullOrEmpty(educationDepartmentId)) organizeIds.Add(educationDepartmentId);
  4630 + if (!string.IsNullOrEmpty(majorProjectDepartmentId)) organizeIds.Add(majorProjectDepartmentId);
  4631 +
  4632 + var organizeDict = new Dictionary<string, string>();
  4633 + if (organizeIds.Any())
  4634 + {
  4635 + var organizes = await _db.Queryable<OrganizeEntity>()
  4636 + .Where(x => organizeIds.Contains(x.Id))
  4637 + .Select(x => new { x.Id, x.FullName })
  4638 + .ToListAsync();
  4639 + organizeDict = organizes.ToDictionary(x => x.Id, x => x.FullName ?? "");
  4640 + }
  4641 +
  4642 + businessUnitName = !string.IsNullOrEmpty(businessUnitId) && organizeDict.ContainsKey(businessUnitId)
  4643 + ? organizeDict[businessUnitId] : "";
  4644 + techDepartmentName = !string.IsNullOrEmpty(techDepartmentId) && organizeDict.ContainsKey(techDepartmentId)
  4645 + ? organizeDict[techDepartmentId] : "";
  4646 + educationDepartmentName = !string.IsNullOrEmpty(educationDepartmentId) && organizeDict.ContainsKey(educationDepartmentId)
  4647 + ? organizeDict[educationDepartmentId] : "";
  4648 + majorProjectDepartmentName = !string.IsNullOrEmpty(majorProjectDepartmentId) && organizeDict.ContainsKey(majorProjectDepartmentId)
  4649 + ? organizeDict[majorProjectDepartmentId] : "";
  4650 +
  4651 + var baseInfo = new StoreBaseInfo
  4652 + {
  4653 + StoreId = store.Id,
  4654 + StoreName = store.Dm ?? "未知门店",
  4655 + StoreCode = store.Mdbm ?? "",
  4656 + City = store.Cs ?? "",
  4657 + Address = store.Dz ?? "",
  4658 + Status = store.Zxzt ?? "",
  4659 + OpenTime = store.Kysj,
  4660 + EmployeeCount = store.Zzrs,
  4661 + StoreType = store.StoreType?.ToString() ?? "",
  4662 + StoreCategory = store.StoreCategory?.ToString() ?? "",
  4663 + BusinessUnit = businessUnitName,
  4664 + EducationDepartment = educationDepartmentName,
  4665 + TechDepartment = techDepartmentName,
  4666 + MajorProjectDepartment = majorProjectDepartmentName
  4667 + };
  4668 +
  4669 + return baseInfo;
  4670 + }
  4671 + catch (Exception ex)
  4672 + {
  4673 + _logger.LogError(ex, $"获取门店基础信息失败 - 门店ID: {input?.StoreId}, 月份: {input?.StatisticsMonth}");
  4674 + throw NCCException.Oh($"获取门店基础信息失败: {ex.Message}");
  4675 + }
  4676 + }
  4677 +
  4678 + /// <summary>
4517 /// 获取门店数据结构分析 4679 /// 获取门店数据结构分析
4518 /// </summary> 4680 /// </summary>
4519 /// <remarks> 4681 /// <remarks>
@@ -4573,6 +4735,49 @@ namespace NCC.Extend @@ -4573,6 +4735,49 @@ namespace NCC.Extend
4573 throw NCCException.Oh("门店不存在"); 4735 throw NCCException.Oh("门店不存在");
4574 } 4736 }
4575 4737
  4738 + // 2.4 目标业绩(从门店目标表获取)
  4739 + var target = await _db.Queryable<LqMdTargetEntity>()
  4740 + .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth)
  4741 + .FirstAsync();
  4742 +
  4743 + // 获取本月归属信息(优先从门店目标表获取,如果不存在则从门店表获取)
  4744 + string businessUnitName = "";
  4745 + string techDepartmentName = "";
  4746 + string educationDepartmentName = "";
  4747 + string majorProjectDepartmentName = "";
  4748 +
  4749 + // 从门店目标表获取归属ID
  4750 + var businessUnitId = target?.BusinessUnit ?? store.Syb;
  4751 + var techDepartmentId = target?.TechDepartment ?? store.Kjb;
  4752 + var educationDepartmentId = target?.EducationDepartment ?? store.Jyb;
  4753 + var majorProjectDepartmentId = target?.MajorProjectDepartment ?? store.Dxmb;
  4754 +
  4755 + // 关联组织表获取归属名称(批量查询优化性能)
  4756 + var organizeIds = new List<string>();
  4757 + if (!string.IsNullOrEmpty(businessUnitId)) organizeIds.Add(businessUnitId);
  4758 + if (!string.IsNullOrEmpty(techDepartmentId)) organizeIds.Add(techDepartmentId);
  4759 + if (!string.IsNullOrEmpty(educationDepartmentId)) organizeIds.Add(educationDepartmentId);
  4760 + if (!string.IsNullOrEmpty(majorProjectDepartmentId)) organizeIds.Add(majorProjectDepartmentId);
  4761 +
  4762 + var organizeDict = new Dictionary<string, string>();
  4763 + if (organizeIds.Any())
  4764 + {
  4765 + var organizes = await _db.Queryable<OrganizeEntity>()
  4766 + .Where(x => organizeIds.Contains(x.Id))
  4767 + .Select(x => new { x.Id, x.FullName })
  4768 + .ToListAsync();
  4769 + organizeDict = organizes.ToDictionary(x => x.Id, x => x.FullName ?? "");
  4770 + }
  4771 +
  4772 + businessUnitName = !string.IsNullOrEmpty(businessUnitId) && organizeDict.ContainsKey(businessUnitId)
  4773 + ? organizeDict[businessUnitId] : "";
  4774 + techDepartmentName = !string.IsNullOrEmpty(techDepartmentId) && organizeDict.ContainsKey(techDepartmentId)
  4775 + ? organizeDict[techDepartmentId] : "";
  4776 + educationDepartmentName = !string.IsNullOrEmpty(educationDepartmentId) && organizeDict.ContainsKey(educationDepartmentId)
  4777 + ? organizeDict[educationDepartmentId] : "";
  4778 + majorProjectDepartmentName = !string.IsNullOrEmpty(majorProjectDepartmentId) && organizeDict.ContainsKey(majorProjectDepartmentId)
  4779 + ? organizeDict[majorProjectDepartmentId] : "";
  4780 +
4576 var baseInfo = new StoreBaseInfo 4781 var baseInfo = new StoreBaseInfo
4577 { 4782 {
4578 StoreId = store.Id, 4783 StoreId = store.Id,
@@ -4585,10 +4790,10 @@ namespace NCC.Extend @@ -4585,10 +4790,10 @@ namespace NCC.Extend
4585 EmployeeCount = store.Zzrs, 4790 EmployeeCount = store.Zzrs,
4586 StoreType = store.StoreType?.ToString() ?? "", 4791 StoreType = store.StoreType?.ToString() ?? "",
4587 StoreCategory = store.StoreCategory?.ToString() ?? "", 4792 StoreCategory = store.StoreCategory?.ToString() ?? "",
4588 - BusinessUnit = store.Syb ?? "",  
4589 - EducationDepartment = store.Jyb ?? "",  
4590 - TechDepartment = store.Kjb ?? "",  
4591 - MajorProjectDepartment = store.Dxmb ?? "" 4793 + BusinessUnit = businessUnitName,
  4794 + EducationDepartment = educationDepartmentName,
  4795 + TechDepartment = techDepartmentName,
  4796 + MajorProjectDepartment = majorProjectDepartmentName
4592 }; 4797 };
4593 4798
4594 // 2. 获取业绩概览 4799 // 2. 获取业绩概览
@@ -4598,7 +4803,7 @@ namespace NCC.Extend @@ -4598,7 +4803,7 @@ namespace NCC.Extend
4598 .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime) 4803 .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
4599 .SumAsync(x => (decimal?)x.Sfyj) ?? 0m; 4804 .SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
4600 4805
4601 - var billingCount = await _db.Queryable<LqKdKdjlbEntity>() 4806 + int billingCount = await _db.Queryable<LqKdKdjlbEntity>()
4602 .Where(x => x.Djmd == input.StoreId && x.IsEffective == 1) 4807 .Where(x => x.Djmd == input.StoreId && x.IsEffective == 1)
4603 .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime) 4808 .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
4604 .CountAsync(); 4809 .CountAsync();
@@ -4609,7 +4814,7 @@ namespace NCC.Extend @@ -4609,7 +4814,7 @@ namespace NCC.Extend
4609 .Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime) 4814 .Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
4610 .SumAsync(x => (decimal?)x.Xfje) ?? 0m; 4815 .SumAsync(x => (decimal?)x.Xfje) ?? 0m;
4611 4816
4612 - var consumeCount = await _db.Queryable<LqXhHyhkEntity>() 4817 + int consumeCount = await _db.Queryable<LqXhHyhkEntity>()
4613 .Where(x => x.Md == input.StoreId && x.IsEffective == 1) 4818 .Where(x => x.Md == input.StoreId && x.IsEffective == 1)
4614 .Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime) 4819 .Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
4615 .CountAsync(); 4820 .CountAsync();
@@ -4620,16 +4825,11 @@ namespace NCC.Extend @@ -4620,16 +4825,11 @@ namespace NCC.Extend
4620 .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date) 4825 .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
4621 .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m; 4826 .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
4622 4827
4623 - var refundCount = await _db.Queryable<LqHytkHytkEntity>() 4828 + int refundCount = await _db.Queryable<LqHytkHytkEntity>()
4624 .Where(x => x.Md == input.StoreId && x.IsEffective == 1) 4829 .Where(x => x.Md == input.StoreId && x.IsEffective == 1)
4625 .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date) 4830 .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
4626 .CountAsync(); 4831 .CountAsync();
4627 4832
4628 - // 2.4 目标业绩(从门店目标表获取)  
4629 - var target = await _db.Queryable<LqMdTargetEntity>()  
4630 - .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth)  
4631 - .FirstAsync();  
4632 -  
4633 var targetPerformance = target?.StoreTarget ?? store.Xsyj ?? 0m; 4833 var targetPerformance = target?.StoreTarget ?? store.Xsyj ?? 0m;
4634 4834
4635 // 2.5 剩余权益总额 4835 // 2.5 剩余权益总额
@@ -4697,20 +4897,19 @@ namespace NCC.Extend @@ -4697,20 +4897,19 @@ namespace NCC.Extend
4697 AND consume.Md = '{input.StoreId}' 4897 AND consume.Md = '{input.StoreId}'
4698 AND DATE(consume.hksj) >= '{startDate:yyyy-MM-dd}' 4898 AND DATE(consume.hksj) >= '{startDate:yyyy-MM-dd}'
4699 AND DATE(consume.hksj) <= '{endDate:yyyy-MM-dd}' 4899 AND DATE(consume.hksj) <= '{endDate:yyyy-MM-dd}'
4700 - GROUP BY consume.Md",  
4701 - new { startDate, endDate }); 4900 + GROUP BY consume.Md");
4702 4901
4703 var operation = new OperationMetrics(); 4902 var operation = new OperationMetrics();
4704 if (dailyStats != null && dailyStats.Any()) 4903 if (dailyStats != null && dailyStats.Any())
4705 { 4904 {
4706 var stats = dailyStats.FirstOrDefault(); 4905 var stats = dailyStats.FirstOrDefault();
4707 - operation.HeadCount = Convert.ToInt32(Convert.ToDecimal(stats?.HeadCount ?? 0));  
4708 - operation.PersonCount = Convert.ToInt32(Convert.ToDecimal(stats?.PersonCount ?? 0));  
4709 - operation.ProjectCount = Convert.ToInt32(Convert.ToDecimal(stats?.ProjectCount ?? 0));  
4710 - operation.TotalProjectCount = Convert.ToDecimal(stats?.TotalProjectCount ?? 0);  
4711 - operation.OriginalProjectCount = Convert.ToDecimal(stats?.OriginalProjectCount ?? 0);  
4712 - operation.OvertimeProjectCount = Convert.ToDecimal(stats?.OvertimeProjectCount ?? 0);  
4713 - operation.AccompaniedProjectCount = Convert.ToDecimal(stats?.AccompaniedProjectCount ?? 0); 4906 + operation.HeadCount = Convert.ToInt32(Convert.ToDecimal(stats?.HeadCount ?? 0m));
  4907 + operation.PersonCount = Convert.ToInt32(Convert.ToDecimal(stats?.PersonCount ?? 0m));
  4908 + operation.ProjectCount = Convert.ToInt32(Convert.ToDecimal(stats?.ProjectCount ?? 0m));
  4909 + operation.TotalProjectCount = Convert.ToDecimal(stats?.TotalProjectCount ?? 0m);
  4910 + operation.OriginalProjectCount = Convert.ToDecimal(stats?.OriginalProjectCount ?? 0m);
  4911 + operation.OvertimeProjectCount = Convert.ToDecimal(stats?.OvertimeProjectCount ?? 0m);
  4912 + operation.AccompaniedProjectCount = Convert.ToDecimal(stats?.AccompaniedProjectCount ?? 0m);
4714 operation.AvgAmountPerPerson = operation.PersonCount > 0 ? consumeAmount / (decimal)operation.PersonCount : 0m; 4913 operation.AvgAmountPerPerson = operation.PersonCount > 0 ? consumeAmount / (decimal)operation.PersonCount : 0m;
4715 operation.AvgAmountPerProject = operation.ProjectCount > 0 ? consumeAmount / (decimal)operation.ProjectCount : 0m; 4914 operation.AvgAmountPerProject = operation.ProjectCount > 0 ? consumeAmount / (decimal)operation.ProjectCount : 0m;
4716 operation.AvgProjectPerHead = operation.HeadCount > 0 ? (decimal)operation.ProjectCount / (decimal)operation.HeadCount : 0m; 4915 operation.AvgProjectPerHead = operation.HeadCount > 0 ? (decimal)operation.ProjectCount / (decimal)operation.HeadCount : 0m;
@@ -4730,8 +4929,7 @@ namespace NCC.Extend @@ -4730,8 +4929,7 @@ namespace NCC.Extend
4730 FROM lq_khxx 4929 FROM lq_khxx
4731 WHERE F_IsEffective = 1 4930 WHERE F_IsEffective = 1
4732 AND gsmd = '{input.StoreId}' 4931 AND gsmd = '{input.StoreId}'
4733 - AND khlx = '3'",  
4734 - new { statisticsMonth }); 4932 + AND khlx = '3'");
4735 4933
4736 var member = new MemberAnalysis(); 4934 var member = new MemberAnalysis();
4737 if (memberStats != null && memberStats.Any()) 4935 if (memberStats != null && memberStats.Any())
@@ -5052,7 +5250,7 @@ namespace NCC.Extend @@ -5052,7 +5250,7 @@ namespace NCC.Extend
5052 GROUP BY kd.djmd, refund.RefundAmount 5250 GROUP BY kd.djmd, refund.RefundAmount
5053 ORDER BY (COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) - COALESCE(refund.RefundAmount, 0)) DESC"); 5251 ORDER BY (COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) - COALESCE(refund.RefundAmount, 0)) DESC");
5054 5252
5055 - var performanceRanking = 1; 5253 + int performanceRanking = 1;
5056 int totalStoreCount = 0; 5254 int totalStoreCount = 0;
5057 if (allStoresPerformance != null) 5255 if (allStoresPerformance != null)
5058 { 5256 {
@@ -5074,7 +5272,7 @@ namespace NCC.Extend @@ -5074,7 +5272,7 @@ namespace NCC.Extend
5074 .Select(x => x.Id) 5272 .Select(x => x.Id)
5075 .ToListAsync(); 5273 .ToListAsync();
5076 5274
5077 - var sameTypeStoreCount = sameTypeStores.Count; 5275 + int sameTypeStoreCount = sameTypeStores.Count;
5078 var avgPerformanceSameType = 0m; 5276 var avgPerformanceSameType = 0m;
5079 if (sameTypeStoreCount > 0) 5277 if (sameTypeStoreCount > 0)
5080 { 5278 {
@@ -5128,7 +5326,7 @@ namespace NCC.Extend @@ -5128,7 +5326,7 @@ namespace NCC.Extend
5128 .ToListAsync(); 5326 .ToListAsync();
5129 } 5327 }
5130 5328
5131 - var sameOrgStoreCount = sameOrgStoreIds.Count; 5329 + int sameOrgStoreCount = sameOrgStoreIds.Count;
5132 var avgPerformanceSameOrg = 0m; 5330 var avgPerformanceSameOrg = 0m;
5133 if (sameOrgStoreCount > 0) 5331 if (sameOrgStoreCount > 0)
5134 { 5332 {
@@ -5818,41 +6016,84 @@ namespace NCC.Extend @@ -5818,41 +6016,84 @@ namespace NCC.Extend
5818 throw NCCException.Oh("门店ID不能为空"); 6016 throw NCCException.Oh("门店ID不能为空");
5819 } 6017 }
5820 6018
  6019 + // 计算时间范围:近12个月
  6020 + var endDate = DateTime.Now;
  6021 + var startDate = endDate.AddMonths(-11);
  6022 + var startMonth = new DateTime(startDate.Year, startDate.Month, 1);
  6023 +
  6024 + // 使用参数化查询,一次性获取12个月的数据
  6025 + var parameters = new List<SugarParameter>
  6026 + {
  6027 + new SugarParameter("@StoreId", input.StoreId),
  6028 + new SugarParameter("@StartDate", startMonth),
  6029 + new SugarParameter("@EndDate", endDate)
  6030 + };
  6031 +
  6032 + // 优化:使用单个SQL查询获取所有月份的开单业绩
  6033 + var billingSql = @"
  6034 + SELECT
  6035 + DATE_FORMAT(kd.kdrq, '%Y%m') as Month,
  6036 + COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as BillingPerformance
  6037 + FROM lq_kd_kdjlb kd
  6038 + WHERE kd.djmd = @StoreId
  6039 + AND kd.F_IsEffective = 1
  6040 + AND kd.kdrq >= @StartDate
  6041 + AND kd.kdrq <= @EndDate
  6042 + GROUP BY DATE_FORMAT(kd.kdrq, '%Y%m')
  6043 + ORDER BY Month";
  6044 +
  6045 + var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql, parameters);
  6046 + var billingDict = billingData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.BillingPerformance ?? 0m));
  6047 +
  6048 + // 优化:使用单个SQL查询获取所有月份的消耗业绩
  6049 + var consumeSql = @"
  6050 + SELECT
  6051 + DATE_FORMAT(xh.hksj, '%Y%m') as Month,
  6052 + COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as ConsumePerformance
  6053 + FROM lq_xh_hyhk xh
  6054 + WHERE xh.md = @StoreId
  6055 + AND xh.F_IsEffective = 1
  6056 + AND xh.hksj >= @StartDate
  6057 + AND xh.hksj <= @EndDate
  6058 + GROUP BY DATE_FORMAT(xh.hksj, '%Y%m')
  6059 + ORDER BY Month";
  6060 +
  6061 + var consumeData = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql, parameters);
  6062 + var consumeDict = consumeData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.ConsumePerformance ?? 0m));
  6063 +
  6064 + // 优化:使用单个SQL查询获取所有月份的退卡金额
  6065 + var refundSql = @"
  6066 + SELECT
  6067 + DATE_FORMAT(tk.tksj, '%Y%m') as Month,
  6068 + COALESCE(SUM(CAST(COALESCE(tk.F_ActualRefundAmount, tk.tkje, 0) AS DECIMAL(18,2))), 0) as RefundAmount
  6069 + FROM lq_hytk_hytk tk
  6070 + WHERE tk.md = @StoreId
  6071 + AND tk.F_IsEffective = 1
  6072 + AND tk.tksj >= @StartDate
  6073 + AND tk.tksj <= @EndDate
  6074 + GROUP BY DATE_FORMAT(tk.tksj, '%Y%m')
  6075 + ORDER BY Month";
  6076 +
  6077 + var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql, parameters);
  6078 + var refundDict = refundData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.RefundAmount ?? 0m));
  6079 +
  6080 + // 构建12个月的趋势数据
5821 var monthlyTrend = new List<MonthlyTrendPoint>(); 6081 var monthlyTrend = new List<MonthlyTrendPoint>();
5822 for (int i = 11; i >= 0; i--) 6082 for (int i = 11; i >= 0; i--)
5823 { 6083 {
5824 var trendMonth = DateTime.Now.AddMonths(-i); 6084 var trendMonth = DateTime.Now.AddMonths(-i);
5825 var trendMonthStr = trendMonth.ToString("yyyyMM"); 6085 var trendMonthStr = trendMonth.ToString("yyyyMM");
5826 - var trendStartDate = new DateTime(trendMonth.Year, trendMonth.Month, 1);  
5827 - var trendEndDate = trendStartDate.AddMonths(1).AddDays(-1);  
5828 - var trendEndDateTime = trendEndDate.Date.AddDays(1).AddSeconds(-1);  
5829 6086
5830 - if (trendMonthStr == DateTime.Now.ToString("yyyyMM"))  
5831 - {  
5832 - trendEndDateTime = DateTime.Now;  
5833 - }  
5834 -  
5835 - var trendBilling = await _db.Queryable<LqKdKdjlbEntity>()  
5836 - .Where(x => x.Djmd == input.StoreId && x.IsEffective == 1)  
5837 - .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= trendStartDate && x.Kdrq.Value <= trendEndDateTime)  
5838 - .SumAsync(x => (decimal?)x.Sfyj) ?? 0m;  
5839 -  
5840 - var trendConsume = await _db.Queryable<LqXhHyhkEntity>()  
5841 - .Where(x => x.Md == input.StoreId && x.IsEffective == 1)  
5842 - .Where(x => x.Hksj.HasValue && x.Hksj.Value >= trendStartDate && x.Hksj.Value <= trendEndDateTime)  
5843 - .SumAsync(x => (decimal?)x.Xfje) ?? 0m;  
5844 -  
5845 - var trendRefund = await _db.Queryable<LqHytkHytkEntity>()  
5846 - .Where(x => x.Md == input.StoreId && x.IsEffective == 1)  
5847 - .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= trendStartDate.Date && x.Tksj.Value.Date <= trendEndDate.Date)  
5848 - .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? (x.Tkje ?? 0))) ?? 0m; 6087 + var billing = billingDict.GetValueOrDefault(trendMonthStr, 0m);
  6088 + var consume = consumeDict.GetValueOrDefault(trendMonthStr, 0m);
  6089 + var refund = refundDict.GetValueOrDefault(trendMonthStr, 0m);
5849 6090
5850 monthlyTrend.Add(new MonthlyTrendPoint 6091 monthlyTrend.Add(new MonthlyTrendPoint
5851 { 6092 {
5852 Month = trendMonthStr, 6093 Month = trendMonthStr,
5853 - BillingPerformance = trendBilling,  
5854 - ConsumePerformance = trendConsume,  
5855 - NetPerformance = trendBilling - trendRefund 6094 + BillingPerformance = billing,
  6095 + ConsumePerformance = consume,
  6096 + NetPerformance = billing - refund
5856 }); 6097 });
5857 } 6098 }
5858 6099
@@ -5951,7 +6192,7 @@ namespace NCC.Extend @@ -5951,7 +6192,7 @@ namespace NCC.Extend
5951 .Select(x => x.Id) 6192 .Select(x => x.Id)
5952 .ToListAsync(); 6193 .ToListAsync();
5953 6194
5954 - int sameTypeStoreCount = sameTypeStores != null ? sameTypeStores.Count : 0; 6195 + var sameTypeStoreCount = sameTypeStores != null ? sameTypeStores.Count : 0;
5955 var avgPerformanceSameType = 0m; 6196 var avgPerformanceSameType = 0m;
5956 if (sameTypeStoreCount > 0) 6197 if (sameTypeStoreCount > 0)
5957 { 6198 {
@@ -6005,7 +6246,7 @@ namespace NCC.Extend @@ -6005,7 +6246,7 @@ namespace NCC.Extend
6005 .ToListAsync(); 6246 .ToListAsync();
6006 } 6247 }
6007 6248
6008 - int sameOrgStoreCount = sameOrgStoreIds != null ? sameOrgStoreIds.Count : 0; 6249 + var sameOrgStoreCount = sameOrgStoreIds != null ? sameOrgStoreIds.Count : 0;
6009 var avgPerformanceSameOrg = 0m; 6250 var avgPerformanceSameOrg = 0m;
6010 if (sameOrgStoreCount > 0) 6251 if (sameOrgStoreCount > 0)
6011 { 6252 {
sql/优化仪表盘接口性能索引.sql 0 → 100644
  1 +-- ============================================
  2 +-- 优化 get-dashboard-data 接口性能的索引
  3 +-- ============================================
  4 +-- 说明:这些索引专门为仪表盘接口优化
  5 +-- 执行前请检查索引是否已存在,避免重复创建
  6 +
  7 +-- ============================================
  8 +-- 1. lq_kd_kdjlb (开单记录表) 索引
  9 +-- ============================================
  10 +-- 用于:门店业绩汇总、健康师业绩汇总、最高开单金额会员查询
  11 +-- 查询条件:F_IsEffective, kdrq (时间范围), djmd (门店ID)
  12 +
  13 +-- 索引:用于时间范围查询和门店统计(如果已存在 idx_kd_kdjlb_store_date_effective 可跳过)
  14 +-- CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_effective
  15 +-- ON lq_kd_kdjlb(djmd, kdrq, F_IsEffective);
  16 +
  17 +-- ============================================
  18 +-- 2. lq_hytk_hytk (退卡表) 索引
  19 +-- ============================================
  20 +-- 用于:退卡金额统计
  21 +-- 查询条件:F_IsEffective, tksj (退卡时间)
  22 +
  23 +-- 索引:用于时间范围查询
  24 +CREATE INDEX IF NOT EXISTS idx_hytk_effective_date
  25 +ON lq_hytk_hytk(F_IsEffective, tksj);
  26 +
  27 +-- ============================================
  28 +-- 3. lq_xh_hyhk (耗卡记录表) 索引
  29 +-- ============================================
  30 +-- 用于:消耗业绩汇总、最高消耗金额会员查询
  31 +-- 查询条件:F_IsEffective, hksj (耗卡时间), hy (会员ID)
  32 +
  33 +-- 索引1:用于时间范围查询
  34 +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_effective_date
  35 +ON lq_xh_hyhk(F_IsEffective, hksj);
  36 +
  37 +-- 索引2:用于会员统计(补充)
  38 +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_member_effective
  39 +ON lq_xh_hyhk(hy, F_IsEffective, hksj);
  40 +
  41 +-- ============================================
  42 +-- 4. lq_xh_pxmx (耗卡品项明细表) 索引
  43 +-- ============================================
  44 +-- 用于:消耗业绩汇总(JOIN)
  45 +-- 查询条件:F_ConsumeInfoId, F_IsEffective
  46 +
  47 +-- 索引:用于JOIN(如果已存在可跳过)
  48 +-- CREATE INDEX IF NOT EXISTS idx_xh_pxmx_consume_effective
  49 +-- ON lq_xh_pxmx(F_ConsumeInfoId, F_IsEffective);
  50 +
  51 +-- ============================================
  52 +-- 5. lq_khxx (客户信息表) 索引
  53 +-- ============================================
  54 +-- 用于:会员统计汇总、会员类型分布、最高剩余权益会员
  55 +-- 查询条件:F_IsEffective, khlx (客户类型), F_CreateTime, F_SleepDays, F_RemainingRightsAmount
  56 +
  57 +-- 索引1:用于会员类型和有效性过滤(最常用)
  58 +CREATE INDEX IF NOT EXISTS idx_khxx_type_effective
  59 +ON lq_khxx(khlx, F_IsEffective);
  60 +
  61 +-- 索引2:用于创建时间统计(新会员统计)
  62 +CREATE INDEX IF NOT EXISTS idx_khxx_create_time
  63 +ON lq_khxx(F_CreateTime, F_IsEffective, khlx);
  64 +
  65 +-- 索引3:用于剩余权益排序(最高剩余权益会员)
  66 +CREATE INDEX IF NOT EXISTS idx_khxx_remaining_effective
  67 +ON lq_khxx(F_RemainingRightsAmount, F_IsEffective);
  68 +
  69 +-- ============================================
  70 +-- 6. 验证索引创建
  71 +-- ============================================
  72 +-- 验证索引是否创建成功
  73 +-- SELECT
  74 +-- TABLE_NAME,
  75 +-- INDEX_NAME,
  76 +-- COLUMN_NAME,
  77 +-- SEQ_IN_INDEX
  78 +-- FROM INFORMATION_SCHEMA.STATISTICS
  79 +-- WHERE TABLE_SCHEMA = DATABASE()
  80 +-- AND TABLE_NAME IN ('lq_kd_kdjlb', 'lq_hytk_hytk', 'lq_xh_hyhk', 'lq_xh_pxmx', 'lq_khxx')
  81 +-- AND INDEX_NAME LIKE 'idx_%'
  82 +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
  83 +
sql/优化金三角业绩排行榜接口性能索引.sql 0 → 100644
  1 +-- ============================================
  2 +-- 优化 get-gold-triangle-performance-ranking 接口性能的索引
  3 +-- ============================================
  4 +-- 说明:这些索引专门为金三角业绩排行榜接口优化
  5 +-- 执行前请检查索引是否已存在,避免重复创建
  6 +
  7 +-- ============================================
  8 +-- 1. lq_ycsd_jsj (金三角设定表) 索引
  9 +-- ============================================
  10 +-- 用于:WHERE jsj.yf = @StatisticsMonth 查询条件
  11 +-- 查询条件:yf (月份)
  12 +
  13 +-- 索引:用于月份查询(最常用)
  14 +CREATE INDEX IF NOT EXISTS idx_ycsd_jsj_yf
  15 +ON lq_ycsd_jsj(yf);
  16 +
  17 +-- ============================================
  18 +-- 2. lq_jinsanjiao_user (金三角用户绑定表) 索引
  19 +-- ============================================
  20 +-- 用于:JOIN和过滤条件
  21 +-- 查询条件:status = 'ACTIVE', F_DeleteMark = 0, user_id (用于JOIN)
  22 +
  23 +-- 索引1:用于JOIN和状态过滤(最常用)
  24 +CREATE INDEX IF NOT EXISTS idx_jinsanjiao_user_user_status_delete
  25 +ON lq_jinsanjiao_user(user_id, status, F_DeleteMark);
  26 +
  27 +-- 索引2:用于反向JOIN(jsj_id用于关联金三角设定)
  28 +CREATE INDEX IF NOT EXISTS idx_jinsanjiao_user_jsj_status_delete
  29 +ON lq_jinsanjiao_user(jsj_id, status, F_DeleteMark);
  30 +
  31 +-- ============================================
  32 +-- 3. lq_kd_jksyj (开单健康师业绩表) 索引
  33 +-- ============================================
  34 +-- 用于:JOIN和过滤条件
  35 +-- 查询条件:jkszh (用于JOIN), F_IsEffective = 1
  36 +
  37 +-- 索引:用于JOIN和有效性过滤
  38 +CREATE INDEX IF NOT EXISTS idx_kd_jksyj_jkszh_effective
  39 +ON lq_kd_jksyj(jkszh, F_IsEffective);
  40 +
  41 +-- ============================================
  42 +-- 4. lq_kd_kdjlb (开单记录表) 索引
  43 +-- ============================================
  44 +-- 用于:JOIN和过滤条件
  45 +-- 查询条件:F_Id (用于JOIN), F_IsEffective = 1, kdrq (时间范围)
  46 +
  47 +-- 索引:用于JOIN、有效性过滤和时间范围查询
  48 +-- 注意:如果 idx_kd_kdjlb_store_date_effective 已存在,此索引可能冗余
  49 +-- 但为了JOIN性能,保留此索引
  50 +CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_id_effective_date
  51 +ON lq_kd_kdjlb(F_Id, F_IsEffective, kdrq);
  52 +
  53 +-- ============================================
  54 +-- 5. 验证索引创建
  55 +-- ============================================
  56 +-- 验证索引是否创建成功
  57 +-- SELECT
  58 +-- TABLE_NAME,
  59 +-- INDEX_NAME,
  60 +-- COLUMN_NAME,
  61 +-- SEQ_IN_INDEX
  62 +-- FROM INFORMATION_SCHEMA.STATISTICS
  63 +-- WHERE TABLE_SCHEMA = DATABASE()
  64 +-- AND TABLE_NAME IN ('lq_ycsd_jsj', 'lq_jinsanjiao_user', 'lq_kd_jksyj', 'lq_kd_kdjlb')
  65 +-- AND INDEX_NAME LIKE 'idx_%'
  66 +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
  67 +
sql/优化门店月度趋势接口性能索引.sql 0 → 100644
  1 +-- ============================================
  2 +-- 优化 get-store-monthly-trend 接口性能的索引
  3 +-- ============================================
  4 +-- 说明:这些索引专门为门店月度趋势接口优化
  5 +-- 执行前请检查索引是否已存在,避免重复创建
  6 +
  7 +-- ============================================
  8 +-- 1. lq_kd_kdjlb (开单记录表) 索引
  9 +-- ============================================
  10 +-- 用于:开单业绩按月统计
  11 +-- 查询条件:djmd (门店ID), F_IsEffective, kdrq (开单日期)
  12 +-- 分组:DATE_FORMAT(kdrq, '%Y%m')
  13 +
  14 +-- 索引:用于门店+时间范围查询(如果已存在 idx_kd_kdjlb_store_date_effective 可跳过)
  15 +-- CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_effective
  16 +-- ON lq_kd_kdjlb(djmd, kdrq, F_IsEffective);
  17 +
  18 +-- 补充索引:用于月份分组查询(优化GROUP BY性能)
  19 +CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_month
  20 +ON lq_kd_kdjlb(djmd, F_IsEffective, kdrq);
  21 +
  22 +-- ============================================
  23 +-- 2. lq_xh_hyhk (耗卡记录表) 索引
  24 +-- ============================================
  25 +-- 用于:消耗业绩按月统计
  26 +-- 查询条件:md (门店ID), F_IsEffective, hksj (耗卡时间)
  27 +-- 分组:DATE_FORMAT(hksj, '%Y%m')
  28 +
  29 +-- 索引:用于门店+时间范围查询
  30 +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_store_date_month
  31 +ON lq_xh_hyhk(md, F_IsEffective, hksj);
  32 +
  33 +-- ============================================
  34 +-- 3. lq_hytk_hytk (退卡表) 索引
  35 +-- ============================================
  36 +-- 用于:退卡金额按月统计
  37 +-- 查询条件:md (门店ID), F_IsEffective, tksj (退卡时间)
  38 +-- 分组:DATE_FORMAT(tksj, '%Y%m')
  39 +
  40 +-- 索引:用于门店+时间范围查询(如果已存在 idx_hytk_effective_date 可跳过)
  41 +-- CREATE INDEX IF NOT EXISTS idx_hytk_effective_date
  42 +-- ON lq_hytk_hytk(F_IsEffective, tksj);
  43 +
  44 +-- 补充索引:用于门店+时间范围查询(优化门店维度查询)
  45 +CREATE INDEX IF NOT EXISTS idx_hytk_store_date_month
  46 +ON lq_hytk_hytk(md, F_IsEffective, tksj);
  47 +
  48 +-- ============================================
  49 +-- 4. 验证索引创建
  50 +-- ============================================
  51 +-- 验证索引是否创建成功
  52 +-- SELECT
  53 +-- TABLE_NAME,
  54 +-- INDEX_NAME,
  55 +-- COLUMN_NAME,
  56 +-- SEQ_IN_INDEX
  57 +-- FROM INFORMATION_SCHEMA.STATISTICS
  58 +-- WHERE TABLE_SCHEMA = DATABASE()
  59 +-- AND TABLE_NAME IN ('lq_kd_kdjlb', 'lq_xh_hyhk', 'lq_hytk_hytk')
  60 +-- AND INDEX_NAME LIKE 'idx_%'
  61 +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
  62 +