Commit 111b5df6f51c0cff0309dfe13a2c06dfbfd67ec8

Authored by 李宇
2 parents 75e27d40 48372cd0

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

Showing 20 changed files with 2969 additions and 183 deletions
antis-ncc-admin/src/api/report.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 获取门店业绩趋势数据
  4 +export function getStorePerformanceTrend(data) {
  5 + return request({
  6 + url: '/api/Extend/LqReport/get-store-performance-trend',
  7 + method: 'post',
  8 + data
  9 + })
  10 +}
  11 +
  12 +// 获取门店业绩排行榜
  13 +export function getStorePerformanceRanking(data) {
  14 + return request({
  15 + url: '/api/Extend/LqReport/get-store-performance-ranking',
  16 + method: 'post',
  17 + data
  18 + })
  19 +}
  20 +
  21 +// 获取健康师业绩排行榜
  22 +export function getHealthCoachPerformanceRanking(data) {
  23 + return request({
  24 + url: '/api/Extend/LqReport/get-health-coach-performance-ranking',
  25 + method: 'post',
  26 + data
  27 + })
  28 +}
  29 +
  30 +// 获取健康师业绩趋势
  31 +export function getHealthCoachPerformanceTrend(data) {
  32 + return request({
  33 + url: '/api/Extend/LqReport/get-health-coach-performance-trend',
  34 + method: 'post',
  35 + data
  36 + })
  37 +}
  38 +
  39 +// 获取金三角业绩排行榜
  40 +export function getGoldTrianglePerformanceRanking(data) {
  41 + return request({
  42 + url: '/api/Extend/LqReport/get-gold-triangle-performance-ranking',
  43 + method: 'post',
  44 + data
  45 + })
  46 +}
  47 +
  48 +// 获取金三角业绩趋势
  49 +export function getGoldTrianglePerformanceTrend(data) {
  50 + return request({
  51 + url: '/api/Extend/LqReport/get-gold-triangle-performance-trend',
  52 + method: 'post',
  53 + data
  54 + })
  55 +}
  56 +
  57 +// 获取综合仪表盘数据
  58 +export function getDashboardData(data) {
  59 + return request({
  60 + url: '/api/Extend/LqReport/get-dashboard-data',
  61 + method: 'post',
  62 + data
  63 + })
  64 +}
... ...
antis-ncc-admin/src/router/index.js
... ... @@ -74,6 +74,22 @@ export const constantRoutes = [{
74 74 icon: 'icon-ym icon-ym-s-data'
75 75 }
76 76 }]
  77 + },
  78 + {
  79 + path: '/report',
  80 + component: Layout,
  81 + hidden: true,
  82 + children: [{
  83 + path: '',
  84 + component: (resolve) => require(['@/views/report/index'], resolve),
  85 + name: 'ReportDashboard',
  86 + meta: {
  87 + title: 'ReportDashboard',
  88 + affix: false,
  89 + zhTitle: '业务报表中心',
  90 + icon: 'el-icon-data-analysis'
  91 + }
  92 + }]
77 93 }
78 94 ]
79 95  
... ...
antis-ncc-admin/src/views/departmentConsumePerformanceStatistics/index.vue
... ... @@ -47,7 +47,7 @@
47 47 <div class="table-container">
48 48 <el-table :data="tableData" v-loading="loading" element-loading-text="加载中..." :height="tableHeight"
49 49 border stripe style="width: 100%">
50   - <el-table-column prop="EmployeeName" label="员工姓名" width="120" fixed="left"></el-table-column>
  50 + <el-table-column prop="EmployeeName" label="员工姓名" fixed="left"></el-table-column>
51 51 <el-table-column prop="StoreName" label="门店名称" width="150" fixed="left"></el-table-column>
52 52 <el-table-column prop="Position" label="岗位" width="100" fixed="left"></el-table-column>
53 53 <el-table-column prop="TotalPerformance" label="总业绩" width="100" align="right">
... ... @@ -60,7 +60,17 @@
60 60 {{ formatMoney(scope.row.ConsumePerformance) }}
61 61 </template>
62 62 </el-table-column>
63   - <el-table-column prop="OrderCount" label="订单数量" width="100" align="right"></el-table-column>
  63 + <el-table-column prop="OrderCount" label="消耗项目数" width="100" align="right"></el-table-column>
  64 + <el-table-column prop="HeadCount" label="人头数" width="100" align="right">
  65 + <template slot-scope="scope">
  66 + {{ formatNumber(scope.row.HeadCount) }}
  67 + </template>
  68 + </el-table-column>
  69 + <el-table-column prop="PersonCount" label="人次" width="100" align="right">
  70 + <template slot-scope="scope">
  71 + {{ formatNumber(scope.row.PersonCount) }}
  72 + </template>
  73 + </el-table-column>
64 74 <el-table-column prop="CreateTime" label="创建时间" width="150" align="center">
65 75 <template slot-scope="scope">
66 76 {{ formatDateTime(scope.row.CreateTime) }}
... ... @@ -200,6 +210,21 @@ export default {
200 210 maximumFractionDigits: 2
201 211 })
202 212 },
  213 + formatNumber(value) {
  214 + if (value === null || value === undefined) return '0'
  215 + const num = Number(value)
  216 + if (num === 0) return '0'
  217 + if (num % 1 === 0) {
  218 + // 整数
  219 + return num.toLocaleString('zh-CN')
  220 + } else {
  221 + // 小数
  222 + return num.toLocaleString('zh-CN', {
  223 + minimumFractionDigits: 1,
  224 + maximumFractionDigits: 2
  225 + })
  226 + }
  227 + },
203 228 formatDateTime(value) {
204 229 if (!value) return '-'
205 230 return new Date(value).toLocaleString('zh-CN')
... ...
antis-ncc-admin/src/views/report/index.vue 0 → 100644
  1 +<template>
  2 + <div class="report-dashboard">
  3 + <!-- 页面标题和筛选区域 -->
  4 + <div class="dashboard-header">
  5 + <div class="header-title">
  6 + <h1>📊 业务报表中心</h1>
  7 + <p class="subtitle">实时数据监控 · 业绩分析 · 决策支持</p>
  8 + </div>
  9 + <div class="header-filters">
  10 + <el-card class="filter-card">
  11 + <div class="filter-row">
  12 + <div class="filter-item">
  13 + <label>统计月份:</label>
  14 + <el-date-picker v-model="selectedMonth" type="month" placeholder="选择月份" format="yyyy年MM月"
  15 + value-format="yyyyMM" @change="onMonthChange" class="month-picker" />
  16 + </div>
  17 + <div class="filter-item">
  18 + <label>时间范围:</label>
  19 + <el-date-picker v-model="dateRange" type="monthrange" range-separator="至"
  20 + start-placeholder="开始月份" end-placeholder="结束月份" format="yyyy年MM月" value-format="yyyyMM"
  21 + @change="onDateRangeChange" class="range-picker" />
  22 + </div>
  23 + <div class="filter-item">
  24 + <el-button type="primary" @click="refreshData" :loading="loading">
  25 + <i class="el-icon-refresh"></i> 刷新数据
  26 + </el-button>
  27 + <el-button type="success" @click="exportData">
  28 + <i class="el-icon-download"></i> 导出报表
  29 + </el-button>
  30 + </div>
  31 + </div>
  32 + </el-card>
  33 + </div>
  34 + </div>
  35 +
  36 + <!-- 仪表盘概览 -->
  37 + <div class="dashboard-overview">
  38 + <el-row :gutter="20">
  39 + <el-col :span="6" v-for="(item, index) in overviewCards" :key="index">
  40 + <el-card class="overview-card" :class="`card-${index + 1}`">
  41 + <div class="card-content">
  42 + <div class="card-icon">
  43 + <i :class="item.icon"></i>
  44 + </div>
  45 + <div class="card-info">
  46 + <div class="card-title">{{ item.title }}</div>
  47 + <div class="card-value">{{ formatNumber(item.value) }}</div>
  48 + <div class="card-unit">{{ item.unit }}</div>
  49 + <div class="card-trend" :class="item.trend > 0 ? 'trend-up' : 'trend-down'">
  50 + <i :class="item.trend > 0 ? 'el-icon-caret-top' : 'el-icon-caret-bottom'"></i>
  51 + {{ Math.abs(item.trend) }}%
  52 + </div>
  53 + </div>
  54 + </div>
  55 + </el-card>
  56 + </el-col>
  57 + </el-row>
  58 + </div>
  59 +
  60 + <!-- 图表区域 -->
  61 + <div class="charts-section">
  62 + <el-row :gutter="20">
  63 + <!-- 门店业绩趋势 -->
  64 + <el-col :span="12">
  65 + <el-card class="chart-card">
  66 + <div slot="header" class="chart-header">
  67 + <span class="chart-title">
  68 + <i class="el-icon-trend-charts"></i>
  69 + 门店业绩趋势
  70 + </span>
  71 + <el-button-group class="chart-controls">
  72 + <el-button size="mini" @click="switchStoreTrendType('performance')"
  73 + :type="storeTrendType === 'performance' ? 'primary' : ''">业绩</el-button>
  74 + <el-button size="mini" @click="switchStoreTrendType('orders')"
  75 + :type="storeTrendType === 'orders' ? 'primary' : ''">订单</el-button>
  76 + </el-button-group>
  77 + </div>
  78 + <div class="chart-container" ref="storeTrendChart"></div>
  79 + </el-card>
  80 + </el-col>
  81 +
  82 + <!-- 门店业绩排行榜 -->
  83 + <el-col :span="12">
  84 + <el-card class="chart-card">
  85 + <div slot="header" class="chart-header">
  86 + <span class="chart-title">
  87 + <i class="el-icon-trophy"></i>
  88 + 门店业绩排行榜
  89 + </span>
  90 + <el-select v-model="rankingTopCount" @change="refreshRankingData" size="mini"
  91 + class="ranking-select">
  92 + <el-option label="前5名" :value="5"></el-option>
  93 + <el-option label="前10名" :value="10"></el-option>
  94 + <el-option label="前20名" :value="20"></el-option>
  95 + </el-select>
  96 + </div>
  97 + <div class="chart-container" ref="storeRankingChart"></div>
  98 + </el-card>
  99 + </el-col>
  100 + </el-row>
  101 +
  102 + <el-row :gutter="20" style="margin-top: 20px;">
  103 + <!-- 健康师业绩排行榜 -->
  104 + <el-col :span="12">
  105 + <el-card class="chart-card">
  106 + <div slot="header" class="chart-header">
  107 + <span class="chart-title">
  108 + <i class="el-icon-user"></i>
  109 + 健康师业绩排行榜
  110 + </span>
  111 + <el-select v-model="healthCoachTopCount" @change="refreshHealthCoachRanking" size="mini"
  112 + class="ranking-select">
  113 + <el-option label="前10名" :value="10"></el-option>
  114 + <el-option label="前20名" :value="20"></el-option>
  115 + <el-option label="前50名" :value="50"></el-option>
  116 + </el-select>
  117 + </div>
  118 + <div class="chart-container" ref="healthCoachRankingChart"></div>
  119 + </el-card>
  120 + </el-col>
  121 +
  122 + <!-- 金三角业绩排行榜 -->
  123 + <el-col :span="12">
  124 + <el-card class="chart-card">
  125 + <div slot="header" class="chart-header">
  126 + <span class="chart-title">
  127 + <i class="el-icon-star-on"></i>
  128 + 金三角业绩排行榜
  129 + </span>
  130 + <el-select v-model="goldTriangleTopCount" @change="refreshGoldTriangleRanking" size="mini"
  131 + class="ranking-select">
  132 + <el-option label="前5名" :value="5"></el-option>
  133 + <el-option label="前10名" :value="10"></el-option>
  134 + <el-option label="前15名" :value="15"></el-option>
  135 + </el-select>
  136 + </div>
  137 + <div class="chart-container" ref="goldTriangleRankingChart"></div>
  138 + </el-card>
  139 + </el-col>
  140 + </el-row>
  141 +
  142 + <el-row :gutter="20" style="margin-top: 20px;">
  143 + <!-- 金三角业绩趋势 -->
  144 + <el-col :span="24">
  145 + <el-card class="chart-card">
  146 + <div slot="header" class="chart-header">
  147 + <span class="chart-title">
  148 + <i class="el-icon-data-line"></i>
  149 + 金三角业绩趋势对比
  150 + </span>
  151 + <el-button-group class="chart-controls">
  152 + <el-button size="mini" @click="switchGoldTriangleTrendType('performance')"
  153 + :type="goldTriangleTrendType === 'performance' ? 'primary' : ''">业绩</el-button>
  154 + <el-button size="mini" @click="switchGoldTriangleTrendType('orders')"
  155 + :type="goldTriangleTrendType === 'orders' ? 'primary' : ''">订单</el-button>
  156 + <el-button size="mini" @click="switchGoldTriangleTrendType('members')"
  157 + :type="goldTriangleTrendType === 'members' ? 'primary' : ''">成员</el-button>
  158 + </el-button-group>
  159 + </div>
  160 + <div class="chart-container large-chart" ref="goldTriangleTrendChart"></div>
  161 + </el-card>
  162 + </el-col>
  163 + </el-row>
  164 + </div>
  165 +
  166 + <!-- 数据表格区域 -->
  167 + <div class="data-tables">
  168 + <el-row :gutter="20">
  169 + <el-col :span="24">
  170 + <el-card class="table-card">
  171 + <div slot="header" class="table-header">
  172 + <span class="table-title">
  173 + <i class="el-icon-s-grid"></i>
  174 + 详细数据表格
  175 + </span>
  176 + <el-tabs v-model="activeTableTab" @tab-click="handleTableTabClick" class="table-tabs">
  177 + <el-tab-pane label="门店业绩" name="store"></el-tab-pane>
  178 + <el-tab-pane label="健康师业绩" name="healthCoach"></el-tab-pane>
  179 + <el-tab-pane label="金三角业绩" name="goldTriangle"></el-tab-pane>
  180 + </el-tabs>
  181 + </div>
  182 + <div class="table-container">
  183 + <el-table :data="tableData" v-loading="tableLoading" stripe border height="400"
  184 + class="data-table">
  185 + <el-table-column v-for="column in tableColumns" :key="column.prop" :prop="column.prop"
  186 + :label="column.label" :width="column.width" :formatter="column.formatter"
  187 + :sortable="column.sortable" />
  188 + </el-table>
  189 + </div>
  190 + </el-card>
  191 + </el-col>
  192 + </el-row>
  193 + </div>
  194 + </div>
  195 +</template>
  196 +
  197 +<script>
  198 +import * as echarts from 'echarts'
  199 +import {
  200 + getStorePerformanceTrend,
  201 + getStorePerformanceRanking,
  202 + getHealthCoachPerformanceRanking,
  203 + getHealthCoachPerformanceTrend,
  204 + getGoldTrianglePerformanceRanking,
  205 + getGoldTrianglePerformanceTrend,
  206 + getDashboardData
  207 +} from '@/api/report'
  208 +
  209 +export default {
  210 + name: 'ReportDashboard',
  211 + data() {
  212 + return {
  213 + loading: false,
  214 + tableLoading: false,
  215 +
  216 + // 筛选条件
  217 + selectedMonth: '',
  218 + dateRange: [],
  219 +
  220 + // 图表类型切换
  221 + storeTrendType: 'performance',
  222 + goldTriangleTrendType: 'performance',
  223 +
  224 + // 排行榜数量
  225 + rankingTopCount: 10,
  226 + healthCoachTopCount: 20,
  227 + goldTriangleTopCount: 10,
  228 +
  229 + // 表格相关
  230 + activeTableTab: 'store',
  231 + tableData: [],
  232 + tableColumns: [],
  233 +
  234 + // 仪表盘数据
  235 + overviewCards: [
  236 + {
  237 + title: '门店总数',
  238 + value: 0,
  239 + unit: '家',
  240 + icon: 'el-icon-shop',
  241 + trend: 0
  242 + },
  243 + {
  244 + title: '总业绩',
  245 + value: 0,
  246 + unit: '万元',
  247 + icon: 'el-icon-money',
  248 + trend: 0
  249 + },
  250 + {
  251 + title: '健康师总数',
  252 + value: 0,
  253 + unit: '人',
  254 + icon: 'el-icon-user',
  255 + trend: 0
  256 + },
  257 + {
  258 + title: '金三角总数',
  259 + value: 0,
  260 + unit: '个',
  261 + icon: 'el-icon-star-on',
  262 + trend: 0
  263 + }
  264 + ],
  265 +
  266 + // 图表实例
  267 + charts: {}
  268 + }
  269 + },
  270 +
  271 + mounted() {
  272 + this.initPage()
  273 + },
  274 +
  275 + methods: {
  276 + // 初始化页面
  277 + async initPage() {
  278 + // 设置默认月份
  279 + const now = new Date()
  280 + this.selectedMonth = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`
  281 +
  282 + // 设置默认时间范围(最近6个月)
  283 + const startMonth = new Date(now.getFullYear(), now.getMonth() - 5, 1)
  284 + this.dateRange = [
  285 + `${startMonth.getFullYear()}${String(startMonth.getMonth() + 1).padStart(2, '0')}`,
  286 + this.selectedMonth
  287 + ]
  288 +
  289 + // 加载数据
  290 + await this.loadDashboardData()
  291 + await this.loadAllCharts()
  292 + },
  293 +
  294 + // 加载仪表盘数据
  295 + async loadDashboardData() {
  296 + try {
  297 + this.loading = true
  298 + const response = await getDashboardData({
  299 + statisticsMonth: this.selectedMonth
  300 + })
  301 +
  302 + if (response.data && response.data.Success) {
  303 + const data = response.data.Data
  304 +
  305 + // 更新概览卡片
  306 + this.overviewCards[0].value = data.StorePerformance.StoreCount
  307 + this.overviewCards[1].value = Math.round(data.StorePerformance.TotalPerformance / 10000 * 100) / 100
  308 + this.overviewCards[2].value = data.HealthCoachPerformance.HealthCoachCount
  309 + this.overviewCards[3].value = data.GoldTrianglePerformance.GoldTriangleCount
  310 + }
  311 + } catch (error) {
  312 + this.$message.error('加载仪表盘数据失败')
  313 + console.error('Dashboard data error:', error)
  314 + } finally {
  315 + this.loading = false
  316 + }
  317 + },
  318 +
  319 + // 加载所有图表
  320 + async loadAllCharts() {
  321 + await Promise.all([
  322 + this.loadStoreTrendChart(),
  323 + this.loadStoreRankingChart(),
  324 + this.loadHealthCoachRankingChart(),
  325 + this.loadGoldTriangleRankingChart(),
  326 + this.loadGoldTriangleTrendChart()
  327 + ])
  328 + },
  329 +
  330 + // 加载门店业绩趋势图
  331 + async loadStoreTrendChart() {
  332 + try {
  333 + const response = await getStorePerformanceTrend({
  334 + startMonth: this.dateRange[0],
  335 + endMonth: this.dateRange[1]
  336 + })
  337 +
  338 + if (response.data && response.data.Success) {
  339 + this.renderStoreTrendChart(response.data.Data)
  340 + }
  341 + } catch (error) {
  342 + console.error('Store trend chart error:', error)
  343 + }
  344 + },
  345 +
  346 + // 渲染门店业绩趋势图
  347 + renderStoreTrendChart(data) {
  348 + const chartDom = this.$refs.storeTrendChart
  349 + if (!chartDom) return
  350 +
  351 + const chart = echarts.init(chartDom)
  352 + this.charts.storeTrend = chart
  353 +
  354 + const months = []
  355 + const series = []
  356 +
  357 + data.forEach(store => {
  358 + months.push.apply(months, store.Data.map(item => item.Month))
  359 + series.push({
  360 + name: store.StoreName,
  361 + type: 'line',
  362 + smooth: true,
  363 + data: store.Data.map(item =>
  364 + this.storeTrendType === 'performance' ? item.TotalPerformance : item.FirstOrderCount + item.UpgradeOrderCount
  365 + )
  366 + })
  367 + })
  368 +
  369 + const uniqueMonths = Array.from(new Set(months)).sort()
  370 +
  371 + const option = {
  372 + title: {
  373 + text: this.storeTrendType === 'performance' ? '门店业绩趋势' : '门店订单趋势',
  374 + left: 'center',
  375 + textStyle: {
  376 + fontSize: 16,
  377 + fontWeight: 'bold'
  378 + }
  379 + },
  380 + tooltip: {
  381 + trigger: 'axis',
  382 + axisPointer: {
  383 + type: 'cross'
  384 + }
  385 + },
  386 + legend: {
  387 + top: 30,
  388 + type: 'scroll'
  389 + },
  390 + grid: {
  391 + left: '3%',
  392 + right: '4%',
  393 + bottom: '3%',
  394 + top: '15%',
  395 + containLabel: true
  396 + },
  397 + xAxis: {
  398 + type: 'category',
  399 + data: uniqueMonths,
  400 + axisLabel: {
  401 + formatter: value => `${value.slice(0, 4)}-${value.slice(4)}`
  402 + }
  403 + },
  404 + yAxis: {
  405 + type: 'value',
  406 + axisLabel: {
  407 + formatter: this.storeTrendType === 'performance' ? '{value}元' : '{value}单'
  408 + }
  409 + },
  410 + series: series.slice(0, 10) // 限制显示前10个门店
  411 + }
  412 +
  413 + chart.setOption(option)
  414 + },
  415 +
  416 + // 加载门店业绩排行榜
  417 + async loadStoreRankingChart() {
  418 + try {
  419 + const response = await getStorePerformanceRanking({
  420 + statisticsMonth: this.selectedMonth,
  421 + topCount: this.rankingTopCount
  422 + })
  423 +
  424 + if (response.data && response.data.Success) {
  425 + this.renderStoreRankingChart(response.data.Data)
  426 + }
  427 + } catch (error) {
  428 + console.error('Store ranking chart error:', error)
  429 + }
  430 + },
  431 +
  432 + // 渲染门店业绩排行榜
  433 + renderStoreRankingChart(data) {
  434 + const chartDom = this.$refs.storeRankingChart
  435 + if (!chartDom) return
  436 +
  437 + const chart = echarts.init(chartDom)
  438 + this.charts.storeRanking = chart
  439 +
  440 + const option = {
  441 + title: {
  442 + text: '门店业绩排行榜',
  443 + left: 'center',
  444 + textStyle: {
  445 + fontSize: 16,
  446 + fontWeight: 'bold'
  447 + }
  448 + },
  449 + tooltip: {
  450 + trigger: 'axis',
  451 + axisPointer: {
  452 + type: 'shadow'
  453 + }
  454 + },
  455 + grid: {
  456 + left: '3%',
  457 + right: '4%',
  458 + bottom: '3%',
  459 + top: '15%',
  460 + containLabel: true
  461 + },
  462 + xAxis: {
  463 + type: 'value',
  464 + axisLabel: {
  465 + formatter: '{value}元'
  466 + }
  467 + },
  468 + yAxis: {
  469 + type: 'category',
  470 + data: data.map(item => item.StoreName),
  471 + axisLabel: {
  472 + formatter: value => value.length > 6 ? value.slice(0, 6) + '...' : value
  473 + }
  474 + },
  475 + series: [{
  476 + type: 'bar',
  477 + data: data.map(item => item.TotalPerformance),
  478 + itemStyle: {
  479 + color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
  480 + { offset: 0, color: '#83bff6' },
  481 + { offset: 0.5, color: '#188df0' },
  482 + { offset: 1, color: '#188df0' }
  483 + ])
  484 + }
  485 + }]
  486 + }
  487 +
  488 + chart.setOption(option)
  489 + },
  490 +
  491 + // 加载健康师业绩排行榜
  492 + async loadHealthCoachRankingChart() {
  493 + try {
  494 + const response = await getHealthCoachPerformanceRanking({
  495 + statisticsMonth: this.selectedMonth,
  496 + topCount: this.healthCoachTopCount
  497 + })
  498 +
  499 + if (response.data && response.data.Success) {
  500 + this.renderHealthCoachRankingChart(response.data.Data)
  501 + }
  502 + } catch (error) {
  503 + console.error('Health coach ranking chart error:', error)
  504 + }
  505 + },
  506 +
  507 + // 渲染健康师业绩排行榜
  508 + renderHealthCoachRankingChart(data) {
  509 + const chartDom = this.$refs.healthCoachRankingChart
  510 + if (!chartDom) return
  511 +
  512 + const chart = echarts.init(chartDom)
  513 + this.charts.healthCoachRanking = chart
  514 +
  515 + const option = {
  516 + title: {
  517 + text: '健康师业绩排行榜',
  518 + left: 'center',
  519 + textStyle: {
  520 + fontSize: 16,
  521 + fontWeight: 'bold'
  522 + }
  523 + },
  524 + tooltip: {
  525 + trigger: 'axis',
  526 + axisPointer: {
  527 + type: 'shadow'
  528 + }
  529 + },
  530 + grid: {
  531 + left: '3%',
  532 + right: '4%',
  533 + bottom: '3%',
  534 + top: '15%',
  535 + containLabel: true
  536 + },
  537 + xAxis: {
  538 + type: 'value',
  539 + axisLabel: {
  540 + formatter: '{value}元'
  541 + }
  542 + },
  543 + yAxis: {
  544 + type: 'category',
  545 + data: data.map(item => item.UserName),
  546 + axisLabel: {
  547 + formatter: value => value.length > 4 ? value.slice(0, 4) + '...' : value
  548 + }
  549 + },
  550 + series: [{
  551 + type: 'bar',
  552 + data: data.map(item => item.TotalPerformance),
  553 + itemStyle: {
  554 + color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
  555 + { offset: 0, color: '#ff9f7f' },
  556 + { offset: 0.5, color: '#ff6b6b' },
  557 + { offset: 1, color: '#ff6b6b' }
  558 + ])
  559 + }
  560 + }]
  561 + }
  562 +
  563 + chart.setOption(option)
  564 + },
  565 +
  566 + // 加载金三角业绩排行榜
  567 + async loadGoldTriangleRankingChart() {
  568 + try {
  569 + const response = await getGoldTrianglePerformanceRanking({
  570 + statisticsMonth: this.selectedMonth,
  571 + topCount: this.goldTriangleTopCount
  572 + })
  573 +
  574 + if (response.data && response.data.Success) {
  575 + this.renderGoldTriangleRankingChart(response.data.Data)
  576 + }
  577 + } catch (error) {
  578 + console.error('Gold triangle ranking chart error:', error)
  579 + }
  580 + },
  581 +
  582 + // 渲染金三角业绩排行榜
  583 + renderGoldTriangleRankingChart(data) {
  584 + const chartDom = this.$refs.goldTriangleRankingChart
  585 + if (!chartDom) return
  586 +
  587 + const chart = echarts.init(chartDom)
  588 + this.charts.goldTriangleRanking = chart
  589 +
  590 + const option = {
  591 + title: {
  592 + text: '金三角业绩排行榜',
  593 + left: 'center',
  594 + textStyle: {
  595 + fontSize: 16,
  596 + fontWeight: 'bold'
  597 + }
  598 + },
  599 + tooltip: {
  600 + trigger: 'axis',
  601 + axisPointer: {
  602 + type: 'shadow'
  603 + }
  604 + },
  605 + grid: {
  606 + left: '3%',
  607 + right: '4%',
  608 + bottom: '3%',
  609 + top: '15%',
  610 + containLabel: true
  611 + },
  612 + xAxis: {
  613 + type: 'value',
  614 + axisLabel: {
  615 + formatter: '{value}元'
  616 + }
  617 + },
  618 + yAxis: {
  619 + type: 'category',
  620 + data: data.map(item => item.GoldTriangleName),
  621 + axisLabel: {
  622 + formatter: value => value.length > 6 ? value.slice(0, 6) + '...' : value
  623 + }
  624 + },
  625 + series: [{
  626 + type: 'bar',
  627 + data: data.map(item => item.TotalPerformance),
  628 + itemStyle: {
  629 + color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
  630 + { offset: 0, color: '#ffd93d' },
  631 + { offset: 0.5, color: '#ffb347' },
  632 + { offset: 1, color: '#ff8c00' }
  633 + ])
  634 + }
  635 + }]
  636 + }
  637 +
  638 + chart.setOption(option)
  639 + },
  640 +
  641 + // 加载金三角业绩趋势图
  642 + async loadGoldTriangleTrendChart() {
  643 + try {
  644 + const response = await getGoldTrianglePerformanceTrend({
  645 + startMonth: this.dateRange[0],
  646 + endMonth: this.dateRange[1]
  647 + })
  648 +
  649 + if (response.data && response.data.Success) {
  650 + this.renderGoldTriangleTrendChart(response.data.Data)
  651 + }
  652 + } catch (error) {
  653 + console.error('Gold triangle trend chart error:', error)
  654 + }
  655 + },
  656 +
  657 + // 渲染金三角业绩趋势图
  658 + renderGoldTriangleTrendChart(data) {
  659 + const chartDom = this.$refs.goldTriangleTrendChart
  660 + if (!chartDom) return
  661 +
  662 + const chart = echarts.init(chartDom)
  663 + this.charts.goldTriangleTrend = chart
  664 +
  665 + const months = []
  666 + const series = []
  667 +
  668 + data.forEach(team => {
  669 + months.push.apply(months, team.Data.map(item => item.Month))
  670 + series.push({
  671 + name: team.GoldTriangleName,
  672 + type: 'line',
  673 + smooth: true,
  674 + data: team.Data.map(item => {
  675 + switch (this.goldTriangleTrendType) {
  676 + case 'performance': return item.TotalPerformance
  677 + case 'orders': return item.OrderCount
  678 + case 'members': return item.MemberCount
  679 + default: return item.TotalPerformance
  680 + }
  681 + })
  682 + })
  683 + })
  684 +
  685 + const uniqueMonths = Array.from(new Set(months)).sort()
  686 +
  687 + const option = {
  688 + title: {
  689 + text: this.getGoldTriangleTrendTitle(),
  690 + left: 'center',
  691 + textStyle: {
  692 + fontSize: 16,
  693 + fontWeight: 'bold'
  694 + }
  695 + },
  696 + tooltip: {
  697 + trigger: 'axis',
  698 + axisPointer: {
  699 + type: 'cross'
  700 + }
  701 + },
  702 + legend: {
  703 + top: 30,
  704 + type: 'scroll'
  705 + },
  706 + grid: {
  707 + left: '3%',
  708 + right: '4%',
  709 + bottom: '3%',
  710 + top: '15%',
  711 + containLabel: true
  712 + },
  713 + xAxis: {
  714 + type: 'category',
  715 + data: uniqueMonths,
  716 + axisLabel: {
  717 + formatter: value => `${value.slice(0, 4)}-${value.slice(4)}`
  718 + }
  719 + },
  720 + yAxis: {
  721 + type: 'value',
  722 + axisLabel: {
  723 + formatter: this.getGoldTriangleTrendFormatter()
  724 + }
  725 + },
  726 + series: series.slice(0, 8) // 限制显示前8个金三角
  727 + }
  728 +
  729 + chart.setOption(option)
  730 + },
  731 +
  732 + // 获取金三角趋势图标题
  733 + getGoldTriangleTrendTitle() {
  734 + switch (this.goldTriangleTrendType) {
  735 + case 'performance': return '金三角业绩趋势'
  736 + case 'orders': return '金三角订单趋势'
  737 + case 'members': return '金三角成员趋势'
  738 + default: return '金三角业绩趋势'
  739 + }
  740 + },
  741 +
  742 + // 获取金三角趋势图格式化器
  743 + getGoldTriangleTrendFormatter() {
  744 + switch (this.goldTriangleTrendType) {
  745 + case 'performance': return '{value}元'
  746 + case 'orders': return '{value}单'
  747 + case 'members': return '{value}人'
  748 + default: return '{value}元'
  749 + }
  750 + },
  751 +
  752 + // 切换门店趋势类型
  753 + switchStoreTrendType(type) {
  754 + this.storeTrendType = type
  755 + const chart = this.charts.storeTrend
  756 + if (chart && chart.getOption && chart.getOption().series) {
  757 + this.renderStoreTrendChart(chart.getOption().series)
  758 + } else {
  759 + this.renderStoreTrendChart([])
  760 + }
  761 + },
  762 +
  763 + // 切换金三角趋势类型
  764 + switchGoldTriangleTrendType(type) {
  765 + this.goldTriangleTrendType = type
  766 + const chart = this.charts.goldTriangleTrend
  767 + if (chart && chart.getOption && chart.getOption().series) {
  768 + this.renderGoldTriangleTrendChart(chart.getOption().series)
  769 + } else {
  770 + this.renderGoldTriangleTrendChart([])
  771 + }
  772 + },
  773 +
  774 + // 刷新排行榜数据
  775 + async refreshRankingData() {
  776 + await this.loadStoreRankingChart()
  777 + },
  778 +
  779 + // 刷新健康师排行榜数据
  780 + async refreshHealthCoachRanking() {
  781 + await this.loadHealthCoachRankingChart()
  782 + },
  783 +
  784 + // 刷新金三角排行榜数据
  785 + async refreshGoldTriangleRanking() {
  786 + await this.loadGoldTriangleRankingChart()
  787 + },
  788 +
  789 + // 月份变化
  790 + async onMonthChange() {
  791 + await this.loadDashboardData()
  792 + await Promise.all([
  793 + this.loadStoreRankingChart(),
  794 + this.loadHealthCoachRankingChart(),
  795 + this.loadGoldTriangleRankingChart()
  796 + ])
  797 + },
  798 +
  799 + // 时间范围变化
  800 + async onDateRangeChange() {
  801 + await Promise.all([
  802 + this.loadStoreTrendChart(),
  803 + this.loadGoldTriangleTrendChart()
  804 + ])
  805 + },
  806 +
  807 + // 刷新数据
  808 + async refreshData() {
  809 + await this.loadDashboardData()
  810 + await this.loadAllCharts()
  811 + this.$message.success('数据刷新成功')
  812 + },
  813 +
  814 + // 导出数据
  815 + exportData() {
  816 + this.$message.info('导出功能开发中...')
  817 + },
  818 +
  819 + // 处理表格标签切换
  820 + handleTableTabClick(tab) {
  821 + this.activeTableTab = tab.name
  822 + this.loadTableData()
  823 + },
  824 +
  825 + // 加载表格数据
  826 + async loadTableData() {
  827 + this.tableLoading = true
  828 + try {
  829 + let response
  830 + switch (this.activeTableTab) {
  831 + case 'store':
  832 + response = await getStorePerformanceRanking({
  833 + statisticsMonth: this.selectedMonth,
  834 + topCount: 50
  835 + })
  836 + this.tableColumns = [
  837 + { prop: 'Ranking', label: '排名', width: 80 },
  838 + { prop: 'StoreName', label: '门店名称', width: 150 },
  839 + { prop: 'TotalPerformance', label: '总业绩', width: 120, formatter: this.formatMoney },
  840 + { prop: 'TotalOrderPerformance', label: '订单业绩', width: 120, formatter: this.formatMoney },
  841 + { prop: 'FirstOrderCount', label: '首开单数', width: 100 },
  842 + { prop: 'UpgradeOrderCount', label: '升单数', width: 100 }
  843 + ]
  844 + break
  845 + case 'healthCoach':
  846 + response = await getHealthCoachPerformanceRanking({
  847 + statisticsMonth: this.selectedMonth,
  848 + topCount: 50
  849 + })
  850 + this.tableColumns = [
  851 + { prop: 'Ranking', label: '排名', width: 80 },
  852 + { prop: 'UserName', label: '姓名', width: 100 },
  853 + { prop: 'StoreName', label: '门店', width: 120 },
  854 + { prop: 'TotalPerformance', label: '总业绩', width: 120, formatter: this.formatMoney },
  855 + { prop: 'FirstOrderPerformance', label: '新客业绩', width: 120, formatter: this.formatMoney },
  856 + { prop: 'UpgradeOrderPerformance', label: '升单业绩', width: 120, formatter: this.formatMoney }
  857 + ]
  858 + break
  859 + case 'goldTriangle':
  860 + response = await getGoldTrianglePerformanceRanking({
  861 + statisticsMonth: this.selectedMonth,
  862 + topCount: 50
  863 + })
  864 + this.tableColumns = [
  865 + { prop: 'Ranking', label: '排名', width: 80 },
  866 + { prop: 'GoldTriangleName', label: '金三角名称', width: 150 },
  867 + { prop: 'StoreName', label: '门店', width: 120 },
  868 + { prop: 'TotalPerformance', label: '总业绩', width: 120, formatter: this.formatMoney },
  869 + { prop: 'OrderCount', label: '订单数', width: 100 },
  870 + { prop: 'MemberCount', label: '成员数', width: 100 }
  871 + ]
  872 + break
  873 + }
  874 +
  875 + if (response && response.data && response.data.Success) {
  876 + this.tableData = response.data.Data
  877 + }
  878 + } catch (error) {
  879 + this.$message.error('加载表格数据失败')
  880 + console.error('Table data error:', error)
  881 + } finally {
  882 + this.tableLoading = false
  883 + }
  884 + },
  885 +
  886 + // 格式化数字
  887 + formatNumber(value) {
  888 + if (value >= 10000) {
  889 + return (value / 10000).toFixed(1) + '万'
  890 + }
  891 + return value.toString()
  892 + },
  893 +
  894 + // 格式化金额
  895 + formatMoney(row, column, cellValue) {
  896 + if (cellValue) {
  897 + return '¥' + this.formatNumber(cellValue)
  898 + }
  899 + return '¥0'
  900 + }
  901 + },
  902 +
  903 + beforeDestroy() {
  904 + // 销毁图表实例
  905 + Object.values(this.charts).forEach(chart => {
  906 + if (chart) {
  907 + chart.dispose()
  908 + }
  909 + })
  910 + }
  911 +}
  912 +</script>
  913 +
  914 +<style lang="scss" scoped>
  915 +.report-dashboard {
  916 + padding: 20px;
  917 + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  918 + min-height: 100vh;
  919 +
  920 + .dashboard-header {
  921 + margin-bottom: 30px;
  922 +
  923 + .header-title {
  924 + text-align: center;
  925 + margin-bottom: 20px;
  926 +
  927 + h1 {
  928 + color: white;
  929 + font-size: 32px;
  930 + font-weight: bold;
  931 + margin: 0;
  932 + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
  933 + }
  934 +
  935 + .subtitle {
  936 + color: rgba(255, 255, 255, 0.9);
  937 + font-size: 16px;
  938 + margin: 10px 0 0 0;
  939 + }
  940 + }
  941 +
  942 + .header-filters {
  943 + .filter-card {
  944 + background: rgba(255, 255, 255, 0.95);
  945 + backdrop-filter: blur(10px);
  946 + border-radius: 15px;
  947 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  948 +
  949 + .filter-row {
  950 + display: flex;
  951 + align-items: center;
  952 + gap: 20px;
  953 + flex-wrap: wrap;
  954 +
  955 + .filter-item {
  956 + display: flex;
  957 + align-items: center;
  958 + gap: 10px;
  959 +
  960 + label {
  961 + font-weight: 600;
  962 + color: #333;
  963 + white-space: nowrap;
  964 + }
  965 +
  966 + .month-picker,
  967 + .range-picker {
  968 + width: 200px;
  969 + }
  970 + }
  971 + }
  972 + }
  973 + }
  974 + }
  975 +
  976 + .dashboard-overview {
  977 + margin-bottom: 30px;
  978 +
  979 + .overview-card {
  980 + background: rgba(255, 255, 255, 0.95);
  981 + backdrop-filter: blur(10px);
  982 + border-radius: 15px;
  983 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  984 + transition: all 0.3s ease;
  985 +
  986 + &:hover {
  987 + transform: translateY(-5px);
  988 + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
  989 + }
  990 +
  991 + &.card-1 {
  992 + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  993 + color: white;
  994 + }
  995 +
  996 + &.card-2 {
  997 + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  998 + color: white;
  999 + }
  1000 +
  1001 + &.card-3 {
  1002 + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  1003 + color: white;
  1004 + }
  1005 +
  1006 + &.card-4 {
  1007 + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  1008 + color: white;
  1009 + }
  1010 +
  1011 + .card-content {
  1012 + display: flex;
  1013 + align-items: center;
  1014 + padding: 20px;
  1015 +
  1016 + .card-icon {
  1017 + font-size: 48px;
  1018 + margin-right: 20px;
  1019 + opacity: 0.8;
  1020 + }
  1021 +
  1022 + .card-info {
  1023 + flex: 1;
  1024 +
  1025 + .card-title {
  1026 + font-size: 14px;
  1027 + margin-bottom: 8px;
  1028 + opacity: 0.9;
  1029 + }
  1030 +
  1031 + .card-value {
  1032 + font-size: 28px;
  1033 + font-weight: bold;
  1034 + margin-bottom: 4px;
  1035 + }
  1036 +
  1037 + .card-unit {
  1038 + font-size: 12px;
  1039 + opacity: 0.8;
  1040 + margin-bottom: 8px;
  1041 + }
  1042 +
  1043 + .card-trend {
  1044 + font-size: 12px;
  1045 + display: flex;
  1046 + align-items: center;
  1047 + gap: 4px;
  1048 +
  1049 + &.trend-up {
  1050 + color: #67c23a;
  1051 + }
  1052 +
  1053 + &.trend-down {
  1054 + color: #f56c6c;
  1055 + }
  1056 + }
  1057 + }
  1058 + }
  1059 + }
  1060 + }
  1061 +
  1062 + .charts-section {
  1063 + margin-bottom: 30px;
  1064 +
  1065 + .chart-card {
  1066 + background: rgba(255, 255, 255, 0.95);
  1067 + backdrop-filter: blur(10px);
  1068 + border-radius: 15px;
  1069 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  1070 +
  1071 + .chart-header {
  1072 + display: flex;
  1073 + justify-content: space-between;
  1074 + align-items: center;
  1075 +
  1076 + .chart-title {
  1077 + font-size: 16px;
  1078 + font-weight: bold;
  1079 + color: #333;
  1080 + display: flex;
  1081 + align-items: center;
  1082 + gap: 8px;
  1083 + }
  1084 +
  1085 + .chart-controls,
  1086 + .ranking-select {
  1087 + margin-left: auto;
  1088 + }
  1089 + }
  1090 +
  1091 + .chart-container {
  1092 + height: 350px;
  1093 + width: 100%;
  1094 +
  1095 + &.large-chart {
  1096 + height: 400px;
  1097 + }
  1098 + }
  1099 + }
  1100 + }
  1101 +
  1102 + .data-tables {
  1103 + .table-card {
  1104 + background: rgba(255, 255, 255, 0.95);
  1105 + backdrop-filter: blur(10px);
  1106 + border-radius: 15px;
  1107 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  1108 +
  1109 + .table-header {
  1110 + display: flex;
  1111 + justify-content: space-between;
  1112 + align-items: center;
  1113 +
  1114 + .table-title {
  1115 + font-size: 16px;
  1116 + font-weight: bold;
  1117 + color: #333;
  1118 + display: flex;
  1119 + align-items: center;
  1120 + gap: 8px;
  1121 + }
  1122 +
  1123 + .table-tabs {
  1124 + margin-left: auto;
  1125 + }
  1126 + }
  1127 +
  1128 + .table-container {
  1129 + .data-table {
  1130 + border-radius: 8px;
  1131 + overflow: hidden;
  1132 + }
  1133 + }
  1134 + }
  1135 + }
  1136 +}
  1137 +
  1138 +// 响应式设计
  1139 +@media (max-width: 1200px) {
  1140 + .report-dashboard {
  1141 + .dashboard-overview {
  1142 + .overview-card {
  1143 + margin-bottom: 20px;
  1144 + }
  1145 + }
  1146 +
  1147 + .charts-section {
  1148 + .chart-card {
  1149 + margin-bottom: 20px;
  1150 + }
  1151 + }
  1152 + }
  1153 +}
  1154 +
  1155 +@media (max-width: 768px) {
  1156 + .report-dashboard {
  1157 + padding: 10px;
  1158 +
  1159 + .dashboard-header {
  1160 + .header-title h1 {
  1161 + font-size: 24px;
  1162 + }
  1163 +
  1164 + .filter-row {
  1165 + flex-direction: column;
  1166 + align-items: stretch;
  1167 +
  1168 + .filter-item {
  1169 + justify-content: space-between;
  1170 +
  1171 + .month-picker,
  1172 + .range-picker {
  1173 + width: 150px;
  1174 + }
  1175 + }
  1176 + }
  1177 + }
  1178 +
  1179 + .dashboard-overview {
  1180 + .overview-card .card-content {
  1181 + padding: 15px;
  1182 +
  1183 + .card-icon {
  1184 + font-size: 36px;
  1185 + margin-right: 15px;
  1186 + }
  1187 +
  1188 + .card-info .card-value {
  1189 + font-size: 24px;
  1190 + }
  1191 + }
  1192 + }
  1193 +
  1194 + .charts-section {
  1195 + .chart-container {
  1196 + height: 300px;
  1197 +
  1198 + &.large-chart {
  1199 + height: 350px;
  1200 + }
  1201 + }
  1202 + }
  1203 + }
  1204 +}
  1205 +</style>
... ...
antis-ncc-admin/src/views/report/test.vue 0 → 100644
  1 +<template>
  2 + <div class="report-test">
  3 + <el-card class="test-card">
  4 + <div slot="header" class="test-header">
  5 + <h2>📊 报表功能测试</h2>
  6 + <p>测试报表服务接口是否正常工作</p>
  7 + </div>
  8 +
  9 + <div class="test-content">
  10 + <el-row :gutter="20">
  11 + <el-col :span="12">
  12 + <el-card class="api-test-card">
  13 + <div slot="header">
  14 + <span>API接口测试</span>
  15 + </div>
  16 + <div class="test-buttons">
  17 + <el-button type="primary" @click="testDashboardData" :loading="loading.dashboard">
  18 + 测试仪表盘数据
  19 + </el-button>
  20 + <el-button type="success" @click="testStoreRanking" :loading="loading.storeRanking">
  21 + 测试门店排行榜
  22 + </el-button>
  23 + <el-button type="warning" @click="testHealthCoachRanking"
  24 + :loading="loading.healthCoachRanking">
  25 + 测试健康师排行榜
  26 + </el-button>
  27 + <el-button type="info" @click="testGoldTriangleRanking"
  28 + :loading="loading.goldTriangleRanking">
  29 + 测试金三角排行榜
  30 + </el-button>
  31 + </div>
  32 + </el-card>
  33 + </el-col>
  34 +
  35 + <el-col :span="12">
  36 + <el-card class="result-card">
  37 + <div slot="header">
  38 + <span>测试结果</span>
  39 + <el-button size="mini" @click="clearResults">清空结果</el-button>
  40 + </div>
  41 + <div class="test-results">
  42 + <div v-for="(result, index) in testResults" :key="index" class="result-item">
  43 + <div class="result-header">
  44 + <span class="result-title">{{ result.title }}</span>
  45 + <el-tag :type="result.success ? 'success' : 'danger'" size="mini">
  46 + {{ result.success ? '成功' : '失败' }}
  47 + </el-tag>
  48 + </div>
  49 + <div class="result-content">
  50 + <pre>{{ result.data }}</pre>
  51 + </div>
  52 + </div>
  53 + </div>
  54 + </el-card>
  55 + </el-col>
  56 + </el-row>
  57 +
  58 + <div class="navigation-section">
  59 + <el-card>
  60 + <div slot="header">
  61 + <span>页面导航</span>
  62 + </div>
  63 + <div class="nav-buttons">
  64 + <el-button type="primary" size="large" @click="goToReport">
  65 + <i class="el-icon-data-analysis"></i>
  66 + 进入报表页面
  67 + </el-button>
  68 + <el-button type="success" size="large" @click="goToSalaryCalculation">
  69 + <i class="el-icon-money"></i>
  70 + 进入工资统计
  71 + </el-button>
  72 + </div>
  73 + </el-card>
  74 + </div>
  75 + </div>
  76 + </el-card>
  77 + </div>
  78 +</template>
  79 +
  80 +<script>
  81 +import {
  82 + getDashboardData,
  83 + getStorePerformanceRanking,
  84 + getHealthCoachPerformanceRanking,
  85 + getGoldTrianglePerformanceRanking
  86 +} from '@/api/report'
  87 +
  88 +export default {
  89 + name: 'ReportTest',
  90 + data() {
  91 + return {
  92 + loading: {
  93 + dashboard: false,
  94 + storeRanking: false,
  95 + healthCoachRanking: false,
  96 + goldTriangleRanking: false
  97 + },
  98 + testResults: []
  99 + }
  100 + },
  101 +
  102 + methods: {
  103 + // 测试仪表盘数据
  104 + async testDashboardData() {
  105 + this.loading.dashboard = true
  106 + try {
  107 + const response = await getDashboardData({
  108 + statisticsMonth: '202509'
  109 + })
  110 +
  111 + this.addTestResult('仪表盘数据', true, response.data)
  112 + } catch (error) {
  113 + this.addTestResult('仪表盘数据', false, error.message)
  114 + } finally {
  115 + this.loading.dashboard = false
  116 + }
  117 + },
  118 +
  119 + // 测试门店排行榜
  120 + async testStoreRanking() {
  121 + this.loading.storeRanking = true
  122 + try {
  123 + const response = await getStorePerformanceRanking({
  124 + statisticsMonth: '202509',
  125 + topCount: 10
  126 + })
  127 +
  128 + this.addTestResult('门店排行榜', true, response.data)
  129 + } catch (error) {
  130 + this.addTestResult('门店排行榜', false, error.message)
  131 + } finally {
  132 + this.loading.storeRanking = false
  133 + }
  134 + },
  135 +
  136 + // 测试健康师排行榜
  137 + async testHealthCoachRanking() {
  138 + this.loading.healthCoachRanking = true
  139 + try {
  140 + const response = await getHealthCoachPerformanceRanking({
  141 + statisticsMonth: '202509',
  142 + topCount: 20
  143 + })
  144 +
  145 + this.addTestResult('健康师排行榜', true, response.data)
  146 + } catch (error) {
  147 + this.addTestResult('健康师排行榜', false, error.message)
  148 + } finally {
  149 + this.loading.healthCoachRanking = false
  150 + }
  151 + },
  152 +
  153 + // 测试金三角排行榜
  154 + async testGoldTriangleRanking() {
  155 + this.loading.goldTriangleRanking = true
  156 + try {
  157 + const response = await getGoldTrianglePerformanceRanking({
  158 + statisticsMonth: '202509',
  159 + topCount: 10
  160 + })
  161 +
  162 + this.addTestResult('金三角排行榜', true, response.data)
  163 + } catch (error) {
  164 + this.addTestResult('金三角排行榜', false, error.message)
  165 + } finally {
  166 + this.loading.goldTriangleRanking = false
  167 + }
  168 + },
  169 +
  170 + // 添加测试结果
  171 + addTestResult(title, success, data) {
  172 + this.testResults.unshift({
  173 + title,
  174 + success,
  175 + data: JSON.stringify(data, null, 2),
  176 + timestamp: new Date().toLocaleTimeString()
  177 + })
  178 +
  179 + // 限制结果数量
  180 + if (this.testResults.length > 10) {
  181 + this.testResults = this.testResults.slice(0, 10)
  182 + }
  183 + },
  184 +
  185 + // 清空结果
  186 + clearResults() {
  187 + this.testResults = []
  188 + },
  189 +
  190 + // 进入报表页面
  191 + goToReport() {
  192 + this.$router.push('/report')
  193 + },
  194 +
  195 + // 进入工资统计
  196 + goToSalaryCalculation() {
  197 + this.$router.push('/salaryCalculation')
  198 + }
  199 + }
  200 +}
  201 +</script>
  202 +
  203 +<style lang="scss" scoped>
  204 +.report-test {
  205 + padding: 20px;
  206 + background: #f5f5f5;
  207 + min-height: 100vh;
  208 +
  209 + .test-card {
  210 + max-width: 1200px;
  211 + margin: 0 auto;
  212 +
  213 + .test-header {
  214 + text-align: center;
  215 +
  216 + h2 {
  217 + margin: 0 0 10px 0;
  218 + color: #333;
  219 + }
  220 +
  221 + p {
  222 + margin: 0;
  223 + color: #666;
  224 + }
  225 + }
  226 +
  227 + .test-content {
  228 +
  229 + .api-test-card,
  230 + .result-card {
  231 + height: 400px;
  232 +
  233 + .test-buttons {
  234 + display: flex;
  235 + flex-direction: column;
  236 + gap: 15px;
  237 +
  238 + .el-button {
  239 + width: 100%;
  240 + }
  241 + }
  242 +
  243 + .test-results {
  244 + height: 320px;
  245 + overflow-y: auto;
  246 +
  247 + .result-item {
  248 + margin-bottom: 15px;
  249 + padding: 10px;
  250 + border: 1px solid #e4e7ed;
  251 + border-radius: 4px;
  252 + background: #fafafa;
  253 +
  254 + .result-header {
  255 + display: flex;
  256 + justify-content: space-between;
  257 + align-items: center;
  258 + margin-bottom: 8px;
  259 +
  260 + .result-title {
  261 + font-weight: bold;
  262 + color: #333;
  263 + }
  264 + }
  265 +
  266 + .result-content {
  267 + pre {
  268 + margin: 0;
  269 + font-size: 12px;
  270 + color: #666;
  271 + white-space: pre-wrap;
  272 + word-break: break-all;
  273 + max-height: 200px;
  274 + overflow-y: auto;
  275 + }
  276 + }
  277 + }
  278 + }
  279 + }
  280 + }
  281 +
  282 + .navigation-section {
  283 + margin-top: 20px;
  284 +
  285 + .nav-buttons {
  286 + display: flex;
  287 + justify-content: center;
  288 + gap: 20px;
  289 +
  290 + .el-button {
  291 + padding: 15px 30px;
  292 + font-size: 16px;
  293 + }
  294 + }
  295 + }
  296 + }
  297 +}
  298 +</style>
... ...
antis-ncc-admin/src/views/salaryStatistics/index.vue
... ... @@ -57,25 +57,25 @@
57 57 <el-table-column prop="StoreName" label="门店名称" width="120" fixed="left"></el-table-column>
58 58 <el-table-column prop="EmployeeName" label="员工姓名" width="100" fixed="left"></el-table-column>
59 59 <el-table-column prop="Position" label="岗位" width="100" fixed="left"></el-table-column>
60   - <el-table-column prop="GoldTriangleTeam" label="金三角战队" width="120"></el-table-column>
  60 + <el-table-column prop="GoldTriangleTeam" label="金三角战队" fixed="left" width="120"></el-table-column>
61 61  
62 62 <!-- 业绩相关 -->
63   - <el-table-column prop="TotalPerformance" label="总业绩" width="100" align="right">
  63 + <el-table-column prop="TotalPerformance" label="总业绩" width="100" align="center">
64 64 <template slot-scope="scope">
65 65 {{ formatMoney(scope.row.TotalPerformance) }}
66 66 </template>
67 67 </el-table-column>
68   - <el-table-column prop="BasePerformance" label="基础业绩" width="100" align="right">
  68 + <el-table-column prop="BasePerformance" label="基础业绩" width="100" align="center">
69 69 <template slot-scope="scope">
70 70 {{ formatMoney(scope.row.BasePerformance) }}
71 71 </template>
72 72 </el-table-column>
73   - <el-table-column prop="CooperationPerformance" label="合作业绩" width="100" align="right">
  73 + <el-table-column prop="CooperationPerformance" label="合作业绩" width="100" align="center">
74 74 <template slot-scope="scope">
75 75 {{ formatMoney(scope.row.CooperationPerformance) }}
76 76 </template>
77 77 </el-table-column>
78   - <el-table-column prop="RewardPerformance" label="奖励业绩" width="100" align="right">
  78 + <el-table-column prop="RewardPerformance" label="奖励业绩" width="100" align="center">
79 79 <template slot-scope="scope">
80 80 {{ formatMoney(scope.row.RewardPerformance) }}
81 81 </template>
... ... @@ -83,17 +83,17 @@
83 83  
84 84 <!-- 新客数据 -->
85 85 <el-table-column label="新客数据" align="center">
86   - <el-table-column prop="NewCustomerPerformance" label="新客业绩" width="100" align="right">
  86 + <el-table-column prop="NewCustomerPerformance" label="新客业绩" width="100" align="center">
87 87 <template slot-scope="scope">
88 88 {{ formatMoney(scope.row.NewCustomerPerformance) }}
89 89 </template>
90 90 </el-table-column>
91   - <el-table-column prop="NewCustomerConversionRate" label="新客成交率" width="100" align="right">
  91 + <el-table-column prop="NewCustomerConversionRate" label="新客成交率" width="100" align="center">
92 92 <template slot-scope="scope">
93 93 {{ formatPercent(scope.row.NewCustomerConversionRate) }}
94 94 </template>
95 95 </el-table-column>
96   - <el-table-column prop="NewCustomerPoint" label="新客提点" width="100" align="right">
  96 + <el-table-column prop="NewCustomerPoint" label="新客提点" width="100" align="center">
97 97 <template slot-scope="scope">
98 98 {{ formatPercent(scope.row.NewCustomerPoint) }}
99 99 </template>
... ... @@ -102,240 +102,242 @@
102 102  
103 103 <!-- 升单数据 -->
104 104 <el-table-column label="升单数据" align="center">
105   - <el-table-column prop="UpgradePerformance" label="升单业绩" width="100" align="right">
  105 + <el-table-column prop="UpgradePerformance" label="升单业绩" width="100" align="center">
106 106 <template slot-scope="scope">
107 107 {{ formatMoney(scope.row.UpgradePerformance) }}
108 108 </template>
109 109 </el-table-column>
110   - <el-table-column prop="UpgradePoint" label="升单提点" width="100" align="right">
  110 + <el-table-column prop="UpgradePoint" label="升单提点" width="100" align="center">
111 111 <template slot-scope="scope">
112 112 {{ formatPercent(scope.row.UpgradePoint) }}
113 113 </template>
114 114 </el-table-column>
115 115 </el-table-column>
116   -
117   - <!-- 其他数据 -->
118   - <el-table-column prop="Consumption" label="消耗" width="100" align="right">
119   - <template slot-scope="scope">
120   - {{ formatMoney(scope.row.Consumption) }}
121   - </template>
  116 + <!--消耗数据-->
  117 + <el-table-column label="消耗数据" align="center">
  118 + <el-table-column prop="Consumption" label="消耗业绩" width="100" align="center">
  119 + <template slot-scope="scope">
  120 + {{ formatMoney(scope.row.Consumption) }}
  121 + </template>
  122 + </el-table-column>
  123 + <el-table-column prop="ProjectCount" label="项目数" width="80" align="center"></el-table-column>
122 124 </el-table-column>
123   - <el-table-column prop="ProjectCount" label="项目数" width="80" align="right"></el-table-column>
124   - <el-table-column prop="StoreTotalPerformance" label="门店总业绩" width="120" align="right">
  125 + <!-- 其他数据 -->
  126 + <el-table-column prop="StoreTotalPerformance" label="门店总业绩" width="120" align="center">
125 127 <template slot-scope="scope">
126 128 {{ formatMoney(scope.row.StoreTotalPerformance) }}
127 129 </template>
128 130 </el-table-column>
129   - <el-table-column prop="TeamPerformance" label="队伍业绩" width="120" align="right">
  131 + <el-table-column prop="TeamPerformance" label="队伍业绩" width="120" align="center">
130 132 <template slot-scope="scope">
131 133 {{ formatMoney(scope.row.TeamPerformance) }}
132 134 </template>
133 135 </el-table-column>
134   - <el-table-column prop="Percentage" label="占比" width="100" align="right">
  136 + <el-table-column prop="Percentage" label="占比" width="100" align="center">
135 137 <template slot-scope="scope">
136   - {{ formatPercent(scope.row.Percentage) }}
  138 + {{ scope.row.Percentage }}%
137 139 </template>
138 140 </el-table-column>
139   - <el-table-column prop="CustomerCount" label="到店人头" width="100" align="right"></el-table-column>
140   - <el-table-column prop="WorkingDays" label="在店天数" width="100" align="right"></el-table-column>
141   - <el-table-column prop="LeaveDays" label="请假天数" width="100" align="right"></el-table-column>
142   - <el-table-column prop="CommissionPoint" label="提点" width="100" align="right">
  141 + <el-table-column prop="CustomerCount" label="到店人头" width="100" align="center"></el-table-column>
  142 + <el-table-column prop="WorkingDays" label="在店天数" width="100" align="center"></el-table-column>
  143 + <el-table-column prop="LeaveDays" label="请假天数" width="100" align="center"></el-table-column>
  144 + <el-table-column prop="CommissionPoint" label="提点" width="100" align="center">
143 145 <template slot-scope="scope">
144 146 {{ formatPercent(scope.row.CommissionPoint) }}
145 147 </template>
146 148 </el-table-column>
147   - <el-table-column prop="BasePerformanceCommission" label="基础业绩提成" width="120" align="right">
  149 + <el-table-column prop="BasePerformanceCommission" label="基础业绩提成" width="120" align="center">
148 150 <template slot-scope="scope">
149 151 {{ formatMoney(scope.row.BasePerformanceCommission) }}
150 152 </template>
151 153 </el-table-column>
152   - <el-table-column prop="CooperationPerformanceCommission" label="合作业绩提成" width="120" align="right">
  154 + <el-table-column prop="CooperationPerformanceCommission" label="合作业绩提成" width="120" align="center">
153 155 <template slot-scope="scope">
154 156 {{ formatMoney(scope.row.CooperationPerformanceCommission) }}
155 157 </template>
156 158 </el-table-column>
157   - <el-table-column prop="ConsultantCommission" label="顾问提成" width="100" align="right">
  159 + <el-table-column prop="ConsultantCommission" label="顾问提成" width="100" align="center">
158 160 <template slot-scope="scope">
159 161 {{ formatMoney(scope.row.ConsultantCommission) }}
160 162 </template>
161 163 </el-table-column>
162   - <el-table-column prop="StoreTZoneCommission" label="门店T区提成" width="120" align="right">
  164 + <el-table-column prop="StoreTZoneCommission" label="门店T区提成" width="120" align="center">
163 165 <template slot-scope="scope">
164 166 {{ formatMoney(scope.row.StoreTZoneCommission) }}
165 167 </template>
166 168 </el-table-column>
167   - <el-table-column prop="TotalCommission" label="提成合计" width="120" align="right">
  169 + <el-table-column prop="TotalCommission" label="提成合计" width="120" align="center">
168 170 <template slot-scope="scope">
169 171 {{ formatMoney(scope.row.TotalCommission) }}
170 172 </template>
171 173 </el-table-column>
172   - <el-table-column prop="HealthCoachBaseSalary" label="健康师底薪" width="120" align="right">
  174 + <el-table-column prop="HealthCoachBaseSalary" label="健康师底薪" width="120" align="center">
173 175 <template slot-scope="scope">
174 176 {{ formatMoney(scope.row.HealthCoachBaseSalary) }}
175 177 </template>
176 178 </el-table-column>
177   - <el-table-column prop="HandworkFee" label="手工费" width="100" align="right">
  179 + <el-table-column prop="HandworkFee" label="手工费" width="100" align="center">
178 180 <template slot-scope="scope">
179 181 {{ formatMoney(scope.row.HandworkFee) }}
180 182 </template>
181 183 </el-table-column>
182   - <el-table-column prop="OutherHandworkFee" label="额外手工费" width="120" align="right">
  184 + <el-table-column prop="OutherHandworkFee" label="额外手工费" width="120" align="center">
183 185 <template slot-scope="scope">
184 186 {{ formatMoney(scope.row.OutherHandworkFee) }}
185 187 </template>
186 188 </el-table-column>
187   - <el-table-column prop="TransportationAllowance" label="车补" width="100" align="right">
  189 + <el-table-column prop="TransportationAllowance" label="车补" width="100" align="center">
188 190 <template slot-scope="scope">
189 191 {{ formatMoney(scope.row.TransportationAllowance) }}
190 192 </template>
191 193 </el-table-column>
192   - <el-table-column prop="LessRest" label="少休费" width="100" align="right">
  194 + <el-table-column prop="LessRest" label="少休费" width="100" align="center">
193 195 <template slot-scope="scope">
194 196 {{ formatMoney(scope.row.LessRest) }}
195 197 </template>
196 198 </el-table-column>
197   - <el-table-column prop="FullAttendance" label="全勤奖" width="100" align="right">
  199 + <el-table-column prop="FullAttendance" label="全勤奖" width="100" align="center">
198 200 <template slot-scope="scope">
199 201 {{ formatMoney(scope.row.FullAttendance) }}
200 202 </template>
201 203 </el-table-column>
202   - <el-table-column prop="CalculatedGrossSalary" label="核算应发工资" width="120" align="right">
  204 + <el-table-column prop="CalculatedGrossSalary" label="核算应发工资" width="120" align="center">
203 205 <template slot-scope="scope">
204 206 {{ formatMoney(scope.row.CalculatedGrossSalary) }}
205 207 </template>
206 208 </el-table-column>
207   - <el-table-column prop="GuaranteedSalary" label="保底工资" width="120" align="right">
  209 + <el-table-column prop="GuaranteedSalary" label="保底工资" width="120" align="center">
208 210 <template slot-scope="scope">
209 211 {{ formatMoney(scope.row.GuaranteedSalary) }}
210 212 </template>
211 213 </el-table-column>
212   - <el-table-column prop="GuaranteedLeaveDeduction" label="保底请假扣款" width="120" align="right">
  214 + <el-table-column prop="GuaranteedLeaveDeduction" label="保底请假扣款" width="120" align="center">
213 215 <template slot-scope="scope">
214 216 {{ formatMoney(scope.row.GuaranteedLeaveDeduction) }}
215 217 </template>
216 218 </el-table-column>
217   - <el-table-column prop="GuaranteedBaseSalary" label="保底底薪" width="120" align="right">
  219 + <el-table-column prop="GuaranteedBaseSalary" label="保底底薪" width="120" align="center">
218 220 <template slot-scope="scope">
219 221 {{ formatMoney(scope.row.GuaranteedBaseSalary) }}
220 222 </template>
221 223 </el-table-column>
222   - <el-table-column prop="GuaranteedSupplement" label="保底补差" width="120" align="right">
  224 + <el-table-column prop="GuaranteedSupplement" label="保底补差" width="120" align="center">
223 225 <template slot-scope="scope">
224 226 {{ formatMoney(scope.row.GuaranteedSupplement) }}
225 227 </template>
226 228 </el-table-column>
227   - <el-table-column prop="FinalGrossSalary" label="最终应发工资" width="120" align="right">
  229 + <el-table-column prop="FinalGrossSalary" label="最终应发工资" width="120" align="center">
228 230 <template slot-scope="scope">
229 231 {{ formatMoney(scope.row.FinalGrossSalary) }}
230 232 </template>
231 233 </el-table-column>
232   - <el-table-column prop="MonthlyTrainingSubsidy" label="当月培训补贴" width="120" align="right">
  234 + <el-table-column prop="MonthlyTrainingSubsidy" label="当月培训补贴" width="120" align="center">
233 235 <template slot-scope="scope">
234 236 {{ formatMoney(scope.row.MonthlyTrainingSubsidy) }}
235 237 </template>
236 238 </el-table-column>
237   - <el-table-column prop="MonthlyTransportSubsidy" label="当月交通补贴" width="120" align="right">
  239 + <el-table-column prop="MonthlyTransportSubsidy" label="当月交通补贴" width="120" align="center">
238 240 <template slot-scope="scope">
239 241 {{ formatMoney(scope.row.MonthlyTransportSubsidy) }}
240 242 </template>
241 243 </el-table-column>
242   - <el-table-column prop="LastMonthTrainingSubsidy" label="上月培训补贴" width="120" align="right">
  244 + <el-table-column prop="LastMonthTrainingSubsidy" label="上月培训补贴" width="120" align="center">
243 245 <template slot-scope="scope">
244 246 {{ formatMoney(scope.row.LastMonthTrainingSubsidy) }}
245 247 </template>
246 248 </el-table-column>
247   - <el-table-column prop="LastMonthTransportSubsidy" label="上月交通补贴" width="120" align="right">
  249 + <el-table-column prop="LastMonthTransportSubsidy" label="上月交通补贴" width="120" align="center">
248 250 <template slot-scope="scope">
249 251 {{ formatMoney(scope.row.LastMonthTransportSubsidy) }}
250 252 </template>
251 253 </el-table-column>
252   - <el-table-column prop="TotalSubsidy" label="补贴合计" width="120" align="right">
  254 + <el-table-column prop="TotalSubsidy" label="补贴合计" width="120" align="center">
253 255 <template slot-scope="scope">
254 256 {{ formatMoney(scope.row.TotalSubsidy) }}
255 257 </template>
256 258 </el-table-column>
257   - <el-table-column prop="MissingCard" label="缺卡扣款" width="120" align="right">
  259 + <el-table-column prop="MissingCard" label="缺卡扣款" width="120" align="center">
258 260 <template slot-scope="scope">
259 261 {{ formatMoney(scope.row.MissingCard) }}
260 262 </template>
261 263 </el-table-column>
262   - <el-table-column prop="LateArrival" label="迟到扣款" width="120" align="right">
  264 + <el-table-column prop="LateArrival" label="迟到扣款" width="120" align="center">
263 265 <template slot-scope="scope">
264 266 {{ formatMoney(scope.row.LateArrival) }}
265 267 </template>
266 268 </el-table-column>
267   - <el-table-column prop="LeaveDeduction" label="请假扣款" width="120" align="right">
  269 + <el-table-column prop="LeaveDeduction" label="请假扣款" width="120" align="center">
268 270 <template slot-scope="scope">
269 271 {{ formatMoney(scope.row.LeaveDeduction) }}
270 272 </template>
271 273 </el-table-column>
272   - <el-table-column prop="SocialInsuranceDeduction" label="扣社保" width="120" align="right">
  274 + <el-table-column prop="SocialInsuranceDeduction" label="扣社保" width="120" align="center">
273 275 <template slot-scope="scope">
274 276 {{ formatMoney(scope.row.SocialInsuranceDeduction) }}
275 277 </template>
276 278 </el-table-column>
277   - <el-table-column prop="RewardDeduction" label="扣除奖励" width="120" align="right">
  279 + <el-table-column prop="RewardDeduction" label="扣除奖励" width="120" align="center">
278 280 <template slot-scope="scope">
279 281 {{ formatMoney(scope.row.RewardDeduction) }}
280 282 </template>
281 283 </el-table-column>
282   - <el-table-column prop="AccommodationDeduction" label="扣住宿费" width="120" align="right">
  284 + <el-table-column prop="AccommodationDeduction" label="扣住宿费" width="120" align="center">
283 285 <template slot-scope="scope">
284 286 {{ formatMoney(scope.row.AccommodationDeduction) }}
285 287 </template>
286 288 </el-table-column>
287   - <el-table-column prop="StudyPeriodDeduction" label="扣学习期费用" width="120" align="right">
  289 + <el-table-column prop="StudyPeriodDeduction" label="扣学习期费用" width="120" align="center">
288 290 <template slot-scope="scope">
289 291 {{ formatMoney(scope.row.StudyPeriodDeduction) }}
290 292 </template>
291 293 </el-table-column>
292   - <el-table-column prop="WorkClothesDeduction" label="扣工作服费用" width="120" align="right">
  294 + <el-table-column prop="WorkClothesDeduction" label="扣工作服费用" width="120" align="center">
293 295 <template slot-scope="scope">
294 296 {{ formatMoney(scope.row.WorkClothesDeduction) }}
295 297 </template>
296 298 </el-table-column>
297   - <el-table-column prop="TotalDeduction" label="扣款合计" width="120" align="right">
  299 + <el-table-column prop="TotalDeduction" label="扣款合计" width="120" align="center">
298 300 <template slot-scope="scope">
299 301 {{ formatMoney(scope.row.TotalDeduction) }}
300 302 </template>
301 303 </el-table-column>
302   - <el-table-column prop="Bonus" label="发奖金" width="100" align="right">
  304 + <el-table-column prop="Bonus" label="发奖金" width="100" align="center">
303 305 <template slot-scope="scope">
304 306 {{ formatMoney(scope.row.Bonus) }}
305 307 </template>
306 308 </el-table-column>
307   - <el-table-column prop="ReturnPhoneDeposit" label="退手机押金" width="120" align="right">
  309 + <el-table-column prop="ReturnPhoneDeposit" label="退手机押金" width="120" align="center">
308 310 <template slot-scope="scope">
309 311 {{ formatMoney(scope.row.ReturnPhoneDeposit) }}
310 312 </template>
311 313 </el-table-column>
312   - <el-table-column prop="ReturnAccommodationDeposit" label="退住宿押金" width="120" align="right">
  314 + <el-table-column prop="ReturnAccommodationDeposit" label="退住宿押金" width="120" align="center">
313 315 <template slot-scope="scope">
314 316 {{ formatMoney(scope.row.ReturnAccommodationDeposit) }}
315 317 </template>
316 318 </el-table-column>
317   - <el-table-column prop="ActualSalary" label="实发工资" width="120" align="right">
  319 + <el-table-column prop="ActualSalary" label="实发工资" width="120" align="center">
318 320 <template slot-scope="scope">
319 321 {{ formatMoney(scope.row.ActualSalary) }}
320 322 </template>
321 323 </el-table-column>
322 324 <el-table-column prop="MonthlyPaymentStatus" label="当月是否发放" width="120" align="center"></el-table-column>
323   - <el-table-column prop="PaidAmount" label="支付金额" width="120" align="right">
  325 + <el-table-column prop="PaidAmount" label="支付金额" width="120" align="center">
324 326 <template slot-scope="scope">
325 327 {{ formatMoney(scope.row.PaidAmount) }}
326 328 </template>
327 329 </el-table-column>
328   - <el-table-column prop="PendingAmount" label="待支付金额" width="120" align="right">
  330 + <el-table-column prop="PendingAmount" label="待支付金额" width="120" align="center">
329 331 <template slot-scope="scope">
330 332 {{ formatMoney(scope.row.PendingAmount) }}
331 333 </template>
332 334 </el-table-column>
333   - <el-table-column prop="LastMonthSupplement" label="补发上月" width="120" align="right">
  335 + <el-table-column prop="LastMonthSupplement" label="补发上月" width="120" align="center">
334 336 <template slot-scope="scope">
335 337 {{ formatMoney(scope.row.LastMonthSupplement) }}
336 338 </template>
337 339 </el-table-column>
338   - <el-table-column prop="MonthlyTotalPayment" label="当月支付总额" width="120" align="right">
  340 + <el-table-column prop="MonthlyTotalPayment" label="当月支付总额" width="120" align="center">
339 341 <template slot-scope="scope">
340 342 {{ formatMoney(scope.row.MonthlyTotalPayment) }}
341 343 </template>
... ... @@ -368,7 +370,7 @@ export default {
368 370 return {
369 371 loading: false,
370 372 tableData: [],
371   - tableHeight: 500,
  373 + tableHeight: 890,
372 374 queryParams: {
373 375 statisticsMonth: '',
374 376 storeName: '',
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs
... ... @@ -24,6 +24,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
24 24 public string id { get; set; }
25 25  
26 26 /// <summary>
  27 + /// 关键字
  28 + /// </summary>
  29 + public string keyWord { get; set; }
  30 +
  31 + /// <summary>
27 32 /// 客户名称
28 33 /// </summary>
29 34 public string khmc { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryStatistics/LqSalaryStatisticsListOutput.cs
... ... @@ -85,7 +85,7 @@ namespace NCC.Extend.Entitys.Dto.LqSalaryStatistics
85 85 /// <summary>
86 86 /// 项目数
87 87 /// </summary>
88   - public int ProjectCount { get; set; }
  88 + public decimal ProjectCount { get; set; }
89 89  
90 90 /// <summary>
91 91 /// 门店总业绩
... ... @@ -100,7 +100,7 @@ namespace NCC.Extend.Entitys.Dto.LqSalaryStatistics
100 100 /// <summary>
101 101 /// 占比
102 102 /// </summary>
103   - public decimal PerformanceRatio { get; set; }
  103 + public decimal Percentage { get; set; }
104 104  
105 105 /// <summary>
106 106 /// 新客成交率
... ... @@ -125,17 +125,18 @@ namespace NCC.Extend.Entitys.Dto.LqSalaryStatistics
125 125 /// <summary>
126 126 /// 到店人头
127 127 /// </summary>
128   - public int AttendanceDays { get; set; }
  128 + public decimal CustomerCount { get; set; }
129 129  
130 130 /// <summary>
131 131 /// 在店天数
132 132 /// </summary>
133   - public int StoreDays { get; set; }
  133 + public decimal WorkingDays { get; set; }
  134 +
134 135  
135 136 /// <summary>
136 137 /// 请假天数
137 138 /// </summary>
138   - public int LeaveDays { get; set; }
  139 + public decimal LeaveDays { get; set; }
139 140  
140 141 /// <summary>
141 142 /// 提点
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LqDepartmentConsumePerformanceStatisticsListOutput.cs
... ... @@ -55,7 +55,17 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
55 55 /// <summary>
56 56 /// 订单数量
57 57 /// </summary>
58   - public int OrderCount { get; set; }
  58 + public decimal OrderCount { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 人头数(月度去重客户数)
  62 + /// </summary>
  63 + public decimal HeadCount { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 人次(日度去重到店数)
  67 + /// </summary>
  68 + public decimal PersonCount { get; set; }
59 69  
60 70 /// <summary>
61 71 /// 创建时间
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_mx/LqHytkMxEntity.cs
... ... @@ -54,6 +54,13 @@ namespace NCC.Extend.Entitys.lq_hytk_mx
54 54 [SugarColumn(ColumnName = "tkje")]
55 55 public decimal? Tkje { get; set; }
56 56  
  57 +
  58 + /// <summary>
  59 + /// 退卡时间
  60 + /// </summary>
  61 + [SugarColumn(ColumnName = "tksj")]
  62 + public DateTime? Tksj { get; set; }
  63 +
57 64 /// <summary>
58 65 /// 项目次数
59 66 /// </summary>
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_pxmx/LqKdPxmxEntity.cs
... ... @@ -41,6 +41,13 @@ namespace NCC.Extend.Entitys.lq_kd_pxmx
41 41 [SugarColumn(ColumnName = "pxjg")]
42 42 public decimal Pxjg { get; set; }
43 43  
  44 +
  45 + /// <summary>
  46 + /// 业绩时间
  47 + /// </summary>
  48 + [SugarColumn(ColumnName = "yjsj")]
  49 + public DateTime? Yjsj { get; set; }
  50 +
44 51 /// <summary>
45 52 /// 会员id
46 53 /// </summary>
... ... @@ -53,6 +60,9 @@ namespace NCC.Extend.Entitys.lq_kd_pxmx
53 60 [SugarColumn(ColumnName = "F_CreateTIme")]
54 61 public DateTime? CreateTIme { get; set; }
55 62  
  63 +
  64 +
  65 +
56 66 /// <summary>
57 67 /// 项目次数
58 68 /// </summary>
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs
... ... @@ -141,25 +141,25 @@ namespace NCC.Extend.Entitys.lq_salary_statistics
141 141 /// 项目数
142 142 /// </summary>
143 143 [SugarColumn(ColumnName = "F_ProjectCount")]
144   - public int ProjectCount { get; set; }
  144 + public decimal ProjectCount { get; set; }
145 145  
146 146 /// <summary>
147 147 /// 到店人头
148 148 /// </summary>
149 149 [SugarColumn(ColumnName = "F_CustomerCount")]
150   - public int CustomerCount { get; set; }
  150 + public decimal CustomerCount { get; set; }
151 151  
152 152 /// <summary>
153 153 /// 在店天数
154 154 /// </summary>
155 155 [SugarColumn(ColumnName = "F_WorkingDays")]
156   - public int WorkingDays { get; set; }
  156 + public decimal WorkingDays { get; set; }
157 157  
158 158 /// <summary>
159 159 /// 请假天数
160 160 /// </summary>
161 161 [SugarColumn(ColumnName = "F_LeaveDays")]
162   - public int LeaveDays { get; set; }
  162 + public decimal LeaveDays { get; set; }
163 163  
164 164 /// <summary>
165 165 /// 提点
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_statistics_department_consume_performance/LqStatisticsDepartmentConsumePerformanceEntity.cs
... ... @@ -100,5 +100,17 @@ namespace NCC.Extend.Entitys.lq_statistics_department_consume_performance
100 100 /// </summary>
101 101 [SugarColumn(ColumnName = "F_IsNewStore")]
102 102 public string IsNewStore { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 人头数(月度去重客户数)
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_HeadCount")]
  108 + public decimal HeadCount { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 人次(日度去重到店数)
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_PersonCount")]
  114 + public decimal PersonCount { get; set; }
103 115 }
104 116 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqReport/ILqReportService.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +using System.Threading.Tasks;
  3 +
  4 +namespace NCC.Extend.Interfaces.LqReport
  5 +{
  6 + /// <summary>
  7 + /// 绿纤报表服务接口
  8 + /// </summary>
  9 + public interface ILqReportService
  10 + {
  11 + /// <summary>
  12 + /// 获取门店业绩趋势数据
  13 + /// </summary>
  14 + /// <param name="input">查询参数</param>
  15 + /// <returns>门店业绩趋势数据</returns>
  16 + Task<object> GetStorePerformanceTrend(StorePerformanceTrendInput input);
  17 +
  18 + /// <summary>
  19 + /// 获取门店业绩排行榜
  20 + /// </summary>
  21 + /// <param name="input">查询参数</param>
  22 + /// <returns>门店业绩排行榜</returns>
  23 + Task<object> GetStorePerformanceRanking(StorePerformanceRankingInput input);
  24 +
  25 + /// <summary>
  26 + /// 获取健康师业绩排行榜
  27 + /// </summary>
  28 + /// <param name="input">查询参数</param>
  29 + /// <returns>健康师业绩排行榜</returns>
  30 + Task<object> GetHealthCoachPerformanceRanking(HealthCoachPerformanceRankingInput input);
  31 +
  32 + /// <summary>
  33 + /// 获取健康师业绩趋势
  34 + /// </summary>
  35 + /// <param name="input">查询参数</param>
  36 + /// <returns>健康师业绩趋势</returns>
  37 + Task<object> GetHealthCoachPerformanceTrend(HealthCoachPerformanceTrendInput input);
  38 +
  39 + /// <summary>
  40 + /// 获取金三角业绩排行榜
  41 + /// </summary>
  42 + /// <param name="input">查询参数</param>
  43 + /// <returns>金三角业绩排行榜</returns>
  44 + Task<object> GetGoldTrianglePerformanceRanking(GoldTrianglePerformanceRankingInput input);
  45 +
  46 + /// <summary>
  47 + /// 获取金三角业绩趋势
  48 + /// </summary>
  49 + /// <param name="input">查询参数</param>
  50 + /// <returns>金三角业绩趋势</returns>
  51 + Task<object> GetGoldTrianglePerformanceTrend(GoldTrianglePerformanceTrendInput input);
  52 +
  53 + /// <summary>
  54 + /// 获取综合仪表盘数据
  55 + /// </summary>
  56 + /// <param name="input">查询参数</param>
  57 + /// <returns>综合仪表盘数据</returns>
  58 + Task<object> GetDashboardData(DashboardDataInput input);
  59 + }
  60 +
  61 + #region 输入参数类
  62 +
  63 + /// <summary>
  64 + /// 门店业绩趋势查询参数
  65 + /// </summary>
  66 + public class StorePerformanceTrendInput
  67 + {
  68 + /// <summary>
  69 + /// 开始月份(YYYYMM格式)
  70 + /// </summary>
  71 + public string StartMonth { get; set; }
  72 +
  73 + /// <summary>
  74 + /// 结束月份(YYYYMM格式)
  75 + /// </summary>
  76 + public string EndMonth { get; set; }
  77 +
  78 + /// <summary>
  79 + /// 门店ID列表(可选)
  80 + /// </summary>
  81 + public List<string> StoreIds { get; set; }
  82 + }
  83 +
  84 + /// <summary>
  85 + /// 门店业绩排行榜查询参数
  86 + /// </summary>
  87 + public class StorePerformanceRankingInput
  88 + {
  89 + /// <summary>
  90 + /// 统计月份(YYYYMM格式)
  91 + /// </summary>
  92 + public string StatisticsMonth { get; set; }
  93 +
  94 + /// <summary>
  95 + /// 排行榜数量(默认10)
  96 + /// </summary>
  97 + public int TopCount { get; set; } = 10;
  98 + }
  99 +
  100 + /// <summary>
  101 + /// 健康师业绩排行榜查询参数
  102 + /// </summary>
  103 + public class HealthCoachPerformanceRankingInput
  104 + {
  105 + /// <summary>
  106 + /// 统计月份(YYYYMM格式)
  107 + /// </summary>
  108 + public string StatisticsMonth { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 排行榜数量(默认20)
  112 + /// </summary>
  113 + public int TopCount { get; set; } = 20;
  114 +
  115 + /// <summary>
  116 + /// 门店ID(可选)
  117 + /// </summary>
  118 + public string StoreId { get; set; }
  119 + }
  120 +
  121 + /// <summary>
  122 + /// 健康师业绩趋势查询参数
  123 + /// </summary>
  124 + public class HealthCoachPerformanceTrendInput
  125 + {
  126 + /// <summary>
  127 + /// 健康师用户ID
  128 + /// </summary>
  129 + public string UserId { get; set; }
  130 +
  131 + /// <summary>
  132 + /// 开始月份(YYYYMM格式)
  133 + /// </summary>
  134 + public string StartMonth { get; set; }
  135 +
  136 + /// <summary>
  137 + /// 结束月份(YYYYMM格式)
  138 + /// </summary>
  139 + public string EndMonth { get; set; }
  140 + }
  141 +
  142 + /// <summary>
  143 + /// 金三角业绩排行榜查询参数
  144 + /// </summary>
  145 + public class GoldTrianglePerformanceRankingInput
  146 + {
  147 + /// <summary>
  148 + /// 统计月份(YYYYMM格式)
  149 + /// </summary>
  150 + public string StatisticsMonth { get; set; }
  151 +
  152 + /// <summary>
  153 + /// 排行榜数量(默认10)
  154 + /// </summary>
  155 + public int TopCount { get; set; } = 10;
  156 + }
  157 +
  158 + /// <summary>
  159 + /// 金三角业绩趋势查询参数
  160 + /// </summary>
  161 + public class GoldTrianglePerformanceTrendInput
  162 + {
  163 + /// <summary>
  164 + /// 开始月份(YYYYMM格式)
  165 + /// </summary>
  166 + public string StartMonth { get; set; }
  167 +
  168 + /// <summary>
  169 + /// 结束月份(YYYYMM格式)
  170 + /// </summary>
  171 + public string EndMonth { get; set; }
  172 +
  173 + /// <summary>
  174 + /// 金三角ID列表(可选)
  175 + /// </summary>
  176 + public List<string> GoldTriangleIds { get; set; }
  177 + }
  178 +
  179 + /// <summary>
  180 + /// 综合仪表盘数据查询参数
  181 + /// </summary>
  182 + public class DashboardDataInput
  183 + {
  184 + /// <summary>
  185 + /// 统计月份(YYYYMM格式)
  186 + /// </summary>
  187 + public string StatisticsMonth { get; set; }
  188 + }
  189 +
  190 + #endregion
  191 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs
... ... @@ -379,6 +379,7 @@ namespace NCC.Extend.LqHytkHytk
379 379 BillingItemId = item.billingItemId,
380 380 CreateTime = DateTime.Now,
381 381 CreateUser = userInfo.userId,
  382 + Tksj = input.tksj,
382 383 DeleteMark = 0,
383 384 Px = item.px,
384 385 Pxmc = item.pxmc,
... ... @@ -475,7 +476,7 @@ namespace NCC.Extend.LqHytkHytk
475 476  
476 477 #region 删除退卡信息
477 478 /// <summary>
478   - /// 删除退卡信息
  479 + /// 删除退卡信息(逻辑删除)
479 480 /// </summary>
480 481 /// <param name="id">主键</param>
481 482 /// <returns></returns>
... ... @@ -491,6 +492,79 @@ namespace NCC.Extend.LqHytkHytk
491 492 }
492 493 #endregion
493 494  
  495 + #region 物理删除退卡信息
  496 + /// <summary>
  497 + /// 物理删除退卡信息
  498 + /// </summary>
  499 + /// <remarks>
  500 + /// 彻底删除退卡记录及其所有关联数据,包括:
  501 + /// - 退卡主表记录
  502 + /// - 退卡品项明细记录
  503 + /// - 退卡健康师业绩记录
  504 + /// - 退卡科技部老师业绩记录
  505 + ///
  506 + /// 注意:此操作不可逆,请谨慎使用
  507 + /// </remarks>
  508 + /// <param name="id">退卡记录主键ID</param>
  509 + /// <returns>删除结果</returns>
  510 + /// <response code="200">删除成功</response>
  511 + /// <response code="404">退卡记录不存在</response>
  512 + /// <response code="500">服务器内部错误</response>
  513 + [HttpDelete("physical/{id}")]
  514 + public async Task<dynamic> PhysicalDelete(string id)
  515 + {
  516 + try
  517 + {
  518 + // 检查退卡记录是否存在
  519 + var entity = await _db.Queryable<LqHytkHytkEntity>().FirstAsync(p => p.Id == id);
  520 + if (entity == null)
  521 + {
  522 + throw NCCException.Oh("退卡记录不存在");
  523 + }
  524 +
  525 + // 开启事务
  526 + _db.BeginTran();
  527 +
  528 + // 1. 删除退卡品项明细记录
  529 + await _db.Deleteable<LqHytkMxEntity>()
  530 + .Where(x => x.RefundInfoId == id)
  531 + .ExecuteCommandAsync();
  532 +
  533 + // 2. 删除退卡健康师业绩记录
  534 + await _db.Deleteable<LqHytkJksyjEntity>()
  535 + .Where(x => x.Gltkbh == id)
  536 + .ExecuteCommandAsync();
  537 +
  538 + // 3. 删除退卡科技部老师业绩记录
  539 + await _db.Deleteable<LqHytkKjbsyjEntity>()
  540 + .Where(x => x.Gltkbh == id)
  541 + .ExecuteCommandAsync();
  542 +
  543 + // 4. 删除退卡主表记录
  544 + await _db.Deleteable<LqHytkHytkEntity>()
  545 + .Where(x => x.Id == id)
  546 + .ExecuteCommandAsync();
  547 +
  548 + // 提交事务
  549 + _db.CommitTran();
  550 +
  551 + return new
  552 + {
  553 + success = true,
  554 + message = "退卡记录已彻底删除",
  555 + deletedId = id,
  556 + deletedTime = DateTime.Now
  557 + };
  558 + }
  559 + catch (Exception ex)
  560 + {
  561 + // 回滚事务
  562 + _db.RollbackTran();
  563 + throw NCCException.Oh($"物理删除退卡记录失败: {ex.Message}");
  564 + }
  565 + }
  566 + #endregion
  567 +
494 568 #region 获取退卡信息详情
495 569 /// <summary>
496 570 /// 获取退卡信息详情
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -292,6 +292,7 @@ namespace NCC.Extend.LqKdKdjlb
292 292 {
293 293 Id = YitIdHelper.NextId().ToString(),
294 294 Glkdbh = newEntity.Id,
  295 + Yjsj = input.kdrq,
295 296 CreateTIme = DateTime.Now,
296 297 MemberId = entity.Kdhy,
297 298 IsEnabled = 0,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
... ... @@ -85,6 +85,7 @@ namespace NCC.Extend.LqKhxx
85 85 {
86 86 var sidx = input.sidx == null ? "id" : input.sidx;
87 87 var data = await _db.Queryable<LqKhxxEntity>()
  88 + .WhereIF(!string.IsNullOrEmpty(input.keyWord), p => p.Khmc.Contains(input.keyWord) || p.Sjh.Contains(input.keyWord) || p.Dah.Contains(input.keyWord))
88 89 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
89 90 .WhereIF(!string.IsNullOrEmpty(input.khmc), p => p.Khmc.Contains(input.khmc))
90 91 .WhereIF(!string.IsNullOrEmpty(input.sjh), p => p.Sjh.Contains(input.sjh))
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using System.Linq;
  4 +using System.Threading.Tasks;
  5 +using Microsoft.AspNetCore.Authorization;
  6 +using Microsoft.AspNetCore.Mvc;
  7 +using Microsoft.Extensions.Logging;
  8 +using NCC.Common.Core.Manager;
  9 +using NCC.Common.Enum;
  10 +using NCC.Common.Extension;
  11 +using NCC.Common.Filter;
  12 +using NCC.Dependency;
  13 +using NCC.DynamicApiController;
  14 +using NCC.FriendlyException;
  15 +using NCC.Extend.Entitys.lq_statistics_gold_triangle;
  16 +using NCC.Extend.Entitys.lq_statistics_personal_performance;
  17 +using NCC.Extend.Entitys.lq_statistics_store_total_performance;
  18 +using NCC.Extend.Entitys.lq_statistics_department_consume_performance;
  19 +using NCC.Extend.Entitys.lq_statistics_tech_performance;
  20 +using NCC.Extend.Entitys.lq_salary_statistics;
  21 +using NCC.Extend.Interfaces.LqReport;
  22 +using NCC.Extend.Entitys.lq_mdxx;
  23 +using NCC.Extend.Entitys.lq_ycsd_jsj;
  24 +using NCC.Extend.Entitys.lq_jinsanjiao_user;
  25 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  26 +using NCC.Extend.Entitys.lq_xh_hyhk;
  27 +using NCC.Extend.Entitys.lq_khxx;
  28 +using SqlSugar;
  29 +
  30 +namespace NCC.Extend
  31 +{
  32 + /// <summary>
  33 + /// 报表服务
  34 + /// </summary>
  35 + [ApiDescriptionSettings(Tag = "绿纤报表服务", Name = "LqReport", Order = 200, Groups = new[] { "Default" })]
  36 + [Route("api/Extend/[controller]")]
  37 + public class LqReportService : ILqReportService, IDynamicApiController, ITransient
  38 + {
  39 + private readonly ISqlSugarClient _db;
  40 + private readonly IUserManager _userManager;
  41 + private readonly ILogger<LqReportService> _logger;
  42 +
  43 + public LqReportService(ISqlSugarClient db, IUserManager userManager, ILogger<LqReportService> logger)
  44 + {
  45 + _db = db;
  46 + _userManager = userManager;
  47 + _logger = logger;
  48 + }
  49 +
  50 + #region 门店业绩报表
  51 +
  52 + /// <summary>
  53 + /// 获取门店业绩趋势数据
  54 + /// </summary>
  55 + /// <remarks>
  56 + /// 获取各门店月度业绩趋势数据,用于折线图展示
  57 + ///
  58 + /// 示例请求:
  59 + /// ```json
  60 + /// POST /api/Extend/LqReport/get-store-performance-trend
  61 + /// {
  62 + /// "startMonth": "202501",
  63 + /// "endMonth": "202512",
  64 + /// "storeIds": ["store1", "store2"]
  65 + /// }
  66 + /// ```
  67 + ///
  68 + /// 参数说明:
  69 + /// - startMonth: 开始月份(YYYYMM格式)
  70 + /// - endMonth: 结束月份(YYYYMM格式)
  71 + /// - storeIds: 门店ID列表(可选)
  72 + /// </remarks>
  73 + /// <param name="input">查询参数</param>
  74 + /// <returns>门店业绩趋势数据</returns>
  75 + /// <response code="200">成功返回趋势数据</response>
  76 + /// <response code="400">参数错误</response>
  77 + /// <response code="500">服务器内部错误</response>
  78 + [HttpPost("get-store-performance-trend")]
  79 + public async Task<object> GetStorePerformanceTrend(StorePerformanceTrendInput input)
  80 + {
  81 + try
  82 + {
  83 + var sql = @"
  84 + SELECT
  85 + s.F_StoreId,
  86 + s.F_StoreName,
  87 + s.F_StatisticsMonth,
  88 + s.F_TotalPerformance,
  89 + s.F_TotalOrderPerformance,
  90 + s.F_FirstOrderCount,
  91 + s.F_UpgradeOrderCount,
  92 + s.F_ItemQuantity
  93 + FROM lq_statistics_store_total_performance s
  94 + WHERE s.F_StatisticsMonth >= @startMonth
  95 + AND s.F_StatisticsMonth <= @endMonth";
  96 +
  97 + object parameters = new { startMonth = input.StartMonth, endMonth = input.EndMonth };
  98 +
  99 + if (input.StoreIds != null && input.StoreIds.Any())
  100 + {
  101 + sql += " AND s.F_StoreId IN @storeIds";
  102 + parameters = new { startMonth = input.StartMonth, endMonth = input.EndMonth, storeIds = input.StoreIds };
  103 + }
  104 +
  105 + sql += " ORDER BY s.F_StoreName, s.F_StatisticsMonth";
  106 +
  107 + var data = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters);
  108 +
  109 + // 按门店分组处理数据
  110 + var result = data.GroupBy(x => x.F_StoreName)
  111 + .Select(g => new
  112 + {
  113 + StoreName = g.Key,
  114 + Data = g.OrderBy(x => x.F_StatisticsMonth).Select(x => new
  115 + {
  116 + Month = x.F_StatisticsMonth,
  117 + TotalPerformance = x.F_TotalPerformance,
  118 + OrderPerformance = x.F_TotalOrderPerformance,
  119 + FirstOrderCount = x.F_FirstOrderCount,
  120 + UpgradeOrderCount = x.F_UpgradeOrderCount,
  121 + ItemQuantity = x.F_ItemQuantity
  122 + }).ToList()
  123 + }).ToList();
  124 +
  125 + return new
  126 + {
  127 + Success = true,
  128 + Data = result,
  129 + Message = "门店业绩趋势数据获取成功"
  130 + };
  131 + }
  132 + catch (Exception ex)
  133 + {
  134 + _logger.LogError(ex, $"获取门店业绩趋势数据失败 - 开始月份: {input?.StartMonth}, 结束月份: {input?.EndMonth}");
  135 + throw NCCException.Oh($"获取门店业绩趋势数据失败: {ex.Message}");
  136 + }
  137 + }
  138 +
  139 + /// <summary>
  140 + /// 获取门店业绩排行榜
  141 + /// </summary>
  142 + /// <remarks>
  143 + /// 获取指定月份门店业绩排行榜,用于条形图展示
  144 + ///
  145 + /// 示例请求:
  146 + /// ```json
  147 + /// POST /api/Extend/LqReport/get-store-performance-ranking
  148 + /// {
  149 + /// "statisticsMonth": "202509",
  150 + /// "topCount": 10
  151 + /// }
  152 + /// ```
  153 + ///
  154 + /// 参数说明:
  155 + /// - statisticsMonth: 统计月份(YYYYMM格式)
  156 + /// - topCount: 排行榜数量(默认10)
  157 + /// </remarks>
  158 + /// <param name="input">查询参数</param>
  159 + /// <returns>门店业绩排行榜</returns>
  160 + /// <response code="200">成功返回排行榜数据</response>
  161 + /// <response code="400">参数错误</response>
  162 + /// <response code="500">服务器内部错误</response>
  163 + [HttpPost("get-store-performance-ranking")]
  164 + public async Task<object> GetStorePerformanceRanking(StorePerformanceRankingInput input)
  165 + {
  166 + try
  167 + {
  168 + var sql = @"
  169 + SELECT
  170 + s.F_StoreId,
  171 + s.F_StoreName,
  172 + s.F_TotalPerformance,
  173 + s.F_TotalOrderPerformance,
  174 + s.F_FirstOrderCount,
  175 + s.F_UpgradeOrderCount,
  176 + s.F_ItemQuantity,
  177 + (@row_number := @row_number + 1) as Ranking
  178 + FROM lq_statistics_store_total_performance s
  179 + CROSS JOIN (SELECT @row_number := 0) r
  180 + WHERE s.F_StatisticsMonth = @statisticsMonth
  181 + ORDER BY s.F_TotalPerformance DESC";
  182 +
  183 + if (input.TopCount > 0)
  184 + {
  185 + sql += $" LIMIT {input.TopCount}";
  186 + }
  187 +
  188 + var data = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { statisticsMonth = input.StatisticsMonth });
  189 +
  190 + var result = data.Select(x => new
  191 + {
  192 + Ranking = x.Ranking,
  193 + StoreId = x.F_StoreId,
  194 + StoreName = x.F_StoreName,
  195 + TotalPerformance = x.F_TotalPerformance,
  196 + OrderPerformance = x.F_TotalOrderPerformance,
  197 + FirstOrderCount = x.F_FirstOrderCount,
  198 + UpgradeOrderCount = x.F_UpgradeOrderCount,
  199 + ItemQuantity = x.F_ItemQuantity
  200 + }).ToList();
  201 +
  202 + return new
  203 + {
  204 + Success = true,
  205 + Data = result,
  206 + Message = "门店业绩排行榜获取成功"
  207 + };
  208 + }
  209 + catch (Exception ex)
  210 + {
  211 + _logger.LogError(ex, $"获取门店业绩排行榜失败 - 月份: {input?.StatisticsMonth}");
  212 + throw NCCException.Oh($"获取门店业绩排行榜失败: {ex.Message}");
  213 + }
  214 + }
  215 +
  216 + #endregion
  217 +
  218 + #region 人员业绩报表
  219 +
  220 + /// <summary>
  221 + /// 获取健康师业绩排行榜
  222 + /// </summary>
  223 + /// <remarks>
  224 + /// 获取指定月份健康师业绩排行榜,用于条形图展示
  225 + ///
  226 + /// 示例请求:
  227 + /// ```json
  228 + /// POST /api/Extend/LqReport/get-health-coach-performance-ranking
  229 + /// {
  230 + /// "statisticsMonth": "202509",
  231 + /// "topCount": 20,
  232 + /// "storeId": "store1"
  233 + /// }
  234 + /// ```
  235 + ///
  236 + /// 参数说明:
  237 + /// - statisticsMonth: 统计月份(YYYYMM格式)
  238 + /// - topCount: 排行榜数量(默认20)
  239 + /// - storeId: 门店ID(可选)
  240 + /// </remarks>
  241 + /// <param name="input">查询参数</param>
  242 + /// <returns>健康师业绩排行榜</returns>
  243 + /// <response code="200">成功返回排行榜数据</response>
  244 + /// <response code="400">参数错误</response>
  245 + /// <response code="500">服务器内部错误</response>
  246 + [HttpPost("get-health-coach-performance-ranking")]
  247 + public async Task<object> GetHealthCoachPerformanceRanking(HealthCoachPerformanceRankingInput input)
  248 + {
  249 + try
  250 + {
  251 + var sql = @"
  252 + SELECT
  253 + p.F_EmployeeId,
  254 + p.F_EmployeeName,
  255 + p.F_StoreId,
  256 + p.F_StoreName,
  257 + p.F_TotalPerformance,
  258 + p.F_FirstOrderPerformance,
  259 + p.F_UpgradeOrderPerformance,
  260 + p.F_FirstOrderCount,
  261 + p.F_UpgradeOrderCount,
  262 + (@row_number := @row_number + 1) as Ranking
  263 + FROM lq_statistics_personal_performance p
  264 + CROSS JOIN (SELECT @row_number := 0) r
  265 + WHERE p.F_StatisticsMonth = @statisticsMonth";
  266 +
  267 + object parameters = new { statisticsMonth = input.StatisticsMonth };
  268 +
  269 + if (!string.IsNullOrEmpty(input.StoreId))
  270 + {
  271 + sql += " AND p.F_StoreId = @storeId";
  272 + parameters = new { statisticsMonth = input.StatisticsMonth, storeId = input.StoreId };
  273 + }
  274 +
  275 + sql += " ORDER BY p.F_TotalPerformance DESC";
  276 +
  277 + if (input.TopCount > 0)
  278 + {
  279 + sql += $" LIMIT {input.TopCount}";
  280 + }
  281 +
  282 + var data = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters);
  283 +
  284 + var result = data.Select(x => new
  285 + {
  286 + Ranking = x.Ranking,
  287 + UserId = x.F_EmployeeId,
  288 + UserName = x.F_EmployeeName,
  289 + StoreId = x.F_StoreId,
  290 + StoreName = x.F_StoreName,
  291 + TotalPerformance = x.F_TotalPerformance,
  292 + FirstOrderPerformance = x.F_FirstOrderPerformance,
  293 + UpgradeOrderPerformance = x.F_UpgradeOrderPerformance,
  294 + FirstOrderCount = x.F_FirstOrderCount,
  295 + UpgradeOrderCount = x.F_UpgradeOrderCount
  296 + }).ToList();
  297 +
  298 + return new
  299 + {
  300 + Success = true,
  301 + Data = result,
  302 + Message = "健康师业绩排行榜获取成功"
  303 + };
  304 + }
  305 + catch (Exception ex)
  306 + {
  307 + _logger.LogError(ex, $"获取健康师业绩排行榜失败 - 月份: {input?.StatisticsMonth}");
  308 + throw NCCException.Oh($"获取健康师业绩排行榜失败: {ex.Message}");
  309 + }
  310 + }
  311 +
  312 + /// <summary>
  313 + /// 获取健康师业绩趋势数据
  314 + /// </summary>
  315 + /// <remarks>
  316 + /// 获取指定健康师月度业绩趋势数据,用于折线图展示
  317 + ///
  318 + /// 示例请求:
  319 + /// ```json
  320 + /// POST /api/Extend/LqReport/get-health-coach-performance-trend
  321 + /// {
  322 + /// "userId": "user123",
  323 + /// "startMonth": "202501",
  324 + /// "endMonth": "202512"
  325 + /// }
  326 + /// ```
  327 + ///
  328 + /// 参数说明:
  329 + /// - userId: 健康师用户ID
  330 + /// - startMonth: 开始月份(YYYYMM格式)
  331 + /// - endMonth: 结束月份(YYYYMM格式)
  332 + /// </remarks>
  333 + /// <param name="input">查询参数</param>
  334 + /// <returns>健康师业绩趋势数据</returns>
  335 + /// <response code="200">成功返回趋势数据</response>
  336 + /// <response code="400">参数错误</response>
  337 + /// <response code="500">服务器内部错误</response>
  338 + [HttpPost("get-health-coach-performance-trend")]
  339 + public async Task<object> GetHealthCoachPerformanceTrend(HealthCoachPerformanceTrendInput input)
  340 + {
  341 + try
  342 + {
  343 + var sql = @"
  344 + SELECT
  345 + p.F_StatisticsMonth,
  346 + p.F_TotalPerformance,
  347 + p.F_FirstOrderPerformance,
  348 + p.F_UpgradeOrderPerformance,
  349 + p.F_FirstOrderCount,
  350 + p.F_UpgradeOrderCount
  351 + FROM lq_statistics_personal_performance p
  352 + WHERE p.F_UserId = @userId
  353 + AND p.F_StatisticsMonth >= @startMonth
  354 + AND p.F_StatisticsMonth <= @endMonth
  355 + ORDER BY p.F_StatisticsMonth";
  356 +
  357 + var data = await _db.Ado.SqlQueryAsync<dynamic>(sql, new
  358 + {
  359 + userId = input.UserId,
  360 + startMonth = input.StartMonth,
  361 + endMonth = input.EndMonth
  362 + });
  363 +
  364 + var result = data.Select(x => new
  365 + {
  366 + Month = x.F_StatisticsMonth,
  367 + TotalPerformance = x.F_TotalPerformance,
  368 + FirstOrderPerformance = x.F_FirstOrderPerformance,
  369 + UpgradeOrderPerformance = x.F_UpgradeOrderPerformance,
  370 + FirstOrderCount = x.F_FirstOrderCount,
  371 + UpgradeOrderCount = x.F_UpgradeOrderCount
  372 + }).ToList();
  373 +
  374 + return new
  375 + {
  376 + Success = true,
  377 + Data = result,
  378 + Message = "健康师业绩趋势数据获取成功"
  379 + };
  380 + }
  381 + catch (Exception ex)
  382 + {
  383 + _logger.LogError(ex, $"获取健康师业绩趋势数据失败 - 用户ID: {input?.UserId}");
  384 + throw NCCException.Oh($"获取健康师业绩趋势数据失败: {ex.Message}");
  385 + }
  386 + }
  387 +
  388 + #endregion
  389 +
  390 + #region 金三角报表
  391 +
  392 + /// <summary>
  393 + /// 获取金三角业绩排行榜
  394 + /// </summary>
  395 + /// <remarks>
  396 + /// 获取指定月份金三角业绩排行榜,用于条形图展示
  397 + ///
  398 + /// 示例请求:
  399 + /// ```json
  400 + /// POST /api/Extend/LqReport/get-gold-triangle-performance-ranking
  401 + /// {
  402 + /// "statisticsMonth": "202509",
  403 + /// "topCount": 10
  404 + /// }
  405 + /// ```
  406 + ///
  407 + /// 参数说明:
  408 + /// - statisticsMonth: 统计月份(YYYYMM格式)
  409 + /// - topCount: 排行榜数量(默认10)
  410 + /// </remarks>
  411 + /// <param name="input">查询参数</param>
  412 + /// <returns>金三角业绩排行榜</returns>
  413 + /// <response code="200">成功返回排行榜数据</response>
  414 + /// <response code="400">参数错误</response>
  415 + /// <response code="500">服务器内部错误</response>
  416 + [HttpPost("get-gold-triangle-performance-ranking")]
  417 + public async Task<object> GetGoldTrianglePerformanceRanking(GoldTrianglePerformanceRankingInput input)
  418 + {
  419 + try
  420 + {
  421 + var sql = @"
  422 + SELECT
  423 + g.F_GoldTriangleId,
  424 + g.F_GoldTriangleName,
  425 + g.F_StoreId,
  426 + g.F_StoreName,
  427 + g.F_TotalPerformance,
  428 + g.F_OrderCount,
  429 + (@row_number := @row_number + 1) as Ranking
  430 + FROM lq_statistics_gold_triangle g
  431 + CROSS JOIN (SELECT @row_number := 0) r
  432 + WHERE g.F_StatisticsMonth = @statisticsMonth
  433 + ORDER BY g.F_TotalPerformance DESC";
  434 +
  435 + if (input.TopCount > 0)
  436 + {
  437 + sql += $" LIMIT {input.TopCount}";
  438 + }
  439 +
  440 + var data = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { statisticsMonth = input.StatisticsMonth });
  441 +
  442 + var result = data.Select(x => new
  443 + {
  444 + Ranking = x.Ranking,
  445 + GoldTriangleId = x.F_GoldTriangleId,
  446 + GoldTriangleName = x.F_GoldTriangleName,
  447 + StoreId = x.F_StoreId,
  448 + StoreName = x.F_StoreName,
  449 + TotalPerformance = x.F_TotalPerformance,
  450 + OrderCount = x.F_OrderCount
  451 + }).ToList();
  452 +
  453 + return new
  454 + {
  455 + Success = true,
  456 + Data = result,
  457 + Message = "金三角业绩排行榜获取成功"
  458 + };
  459 + }
  460 + catch (Exception ex)
  461 + {
  462 + _logger.LogError(ex, $"获取金三角业绩排行榜失败 - 月份: {input?.StatisticsMonth}");
  463 + throw NCCException.Oh($"获取金三角业绩排行榜失败: {ex.Message}");
  464 + }
  465 + }
  466 +
  467 + /// <summary>
  468 + /// 获取金三角业绩趋势数据
  469 + /// </summary>
  470 + /// <remarks>
  471 + /// 获取各金三角月度业绩趋势数据,用于折线图展示
  472 + ///
  473 + /// 示例请求:
  474 + /// ```json
  475 + /// POST /api/Extend/LqReport/get-gold-triangle-performance-trend
  476 + /// {
  477 + /// "startMonth": "202501",
  478 + /// "endMonth": "202512",
  479 + /// "goldTriangleIds": ["gt1", "gt2"]
  480 + /// }
  481 + /// ```
  482 + ///
  483 + /// 参数说明:
  484 + /// - startMonth: 开始月份(YYYYMM格式)
  485 + /// - endMonth: 结束月份(YYYYMM格式)
  486 + /// - goldTriangleIds: 金三角ID列表(可选)
  487 + /// </remarks>
  488 + /// <param name="input">查询参数</param>
  489 + /// <returns>金三角业绩趋势数据</returns>
  490 + /// <response code="200">成功返回趋势数据</response>
  491 + /// <response code="400">参数错误</response>
  492 + /// <response code="500">服务器内部错误</response>
  493 + [HttpPost("get-gold-triangle-performance-trend")]
  494 + public async Task<object> GetGoldTrianglePerformanceTrend(GoldTrianglePerformanceTrendInput input)
  495 + {
  496 + try
  497 + {
  498 + var sql = @"
  499 + SELECT
  500 + g.F_GoldTriangleId,
  501 + g.F_GoldTriangleName,
  502 + g.F_StatisticsMonth,
  503 + g.F_TotalPerformance,
  504 + g.F_OrderCount
  505 + FROM lq_statistics_gold_triangle g
  506 + WHERE g.F_StatisticsMonth >= @startMonth
  507 + AND g.F_StatisticsMonth <= @endMonth";
  508 +
  509 + object parameters = new { startMonth = input.StartMonth, endMonth = input.EndMonth };
  510 +
  511 + if (input.GoldTriangleIds != null && input.GoldTriangleIds.Any())
  512 + {
  513 + sql += " AND g.F_GoldTriangleId IN @goldTriangleIds";
  514 + parameters = new { startMonth = input.StartMonth, endMonth = input.EndMonth, goldTriangleIds = input.GoldTriangleIds };
  515 + }
  516 +
  517 + sql += " ORDER BY g.F_GoldTriangleName, g.F_StatisticsMonth";
  518 +
  519 + var data = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters);
  520 +
  521 + // 按金三角分组处理数据
  522 + var result = data.GroupBy(x => x.F_GoldTriangleName)
  523 + .Select(g => new
  524 + {
  525 + GoldTriangleName = g.Key,
  526 + GoldTriangleId = g.First().F_GoldTriangleId,
  527 + Data = g.OrderBy(x => x.F_StatisticsMonth).Select(x => new
  528 + {
  529 + Month = x.F_StatisticsMonth,
  530 + TotalPerformance = x.F_TotalPerformance,
  531 + OrderCount = x.F_OrderCount
  532 + }).ToList()
  533 + }).ToList();
  534 +
  535 + return new
  536 + {
  537 + Success = true,
  538 + Data = result,
  539 + Message = "金三角业绩趋势数据获取成功"
  540 + };
  541 + }
  542 + catch (Exception ex)
  543 + {
  544 + _logger.LogError(ex, $"获取金三角业绩趋势数据失败 - 开始月份: {input?.StartMonth}, 结束月份: {input?.EndMonth}");
  545 + throw NCCException.Oh($"获取金三角业绩趋势数据失败: {ex.Message}");
  546 + }
  547 + }
  548 +
  549 + #endregion
  550 +
  551 + #region 综合仪表盘
  552 +
  553 + /// <summary>
  554 + /// 获取综合仪表盘数据
  555 + /// </summary>
  556 + /// <remarks>
  557 + /// 获取关键业务指标的综合仪表盘数据
  558 + ///
  559 + /// 示例请求:
  560 + /// ```json
  561 + /// POST /api/Extend/LqReport/get-dashboard-data
  562 + /// {
  563 + /// "statisticsMonth": "202509"
  564 + /// }
  565 + /// ```
  566 + ///
  567 + /// 参数说明:
  568 + /// - statisticsMonth: 统计月份(YYYYMM格式)
  569 + /// </remarks>
  570 + /// <param name="input">查询参数</param>
  571 + /// <returns>综合仪表盘数据</returns>
  572 + /// <response code="200">成功返回仪表盘数据</response>
  573 + /// <response code="400">参数错误</response>
  574 + /// <response code="500">服务器内部错误</response>
  575 + [HttpPost("get-dashboard-data")]
  576 + public async Task<object> GetDashboardData(DashboardDataInput input)
  577 + {
  578 + try
  579 + {
  580 + var statisticsMonth = input.StatisticsMonth;
  581 +
  582 + // 1. 门店业绩汇总
  583 + var storePerformanceSql = @"
  584 + SELECT
  585 + COUNT(*) as StoreCount,
  586 + SUM(F_TotalPerformance) as TotalPerformance,
  587 + SUM(F_FirstOrderCount) as TotalFirstOrderCount,
  588 + SUM(F_UpgradeOrderCount) as TotalUpgradeOrderCount,
  589 + AVG(F_TotalPerformance) as AvgPerformance
  590 + FROM lq_statistics_store_total_performance
  591 + WHERE F_StatisticsMonth = @statisticsMonth";
  592 +
  593 + var storePerformance = await _db.Ado.SqlQueryAsync<dynamic>(storePerformanceSql, new { statisticsMonth });
  594 +
  595 + // 2. 健康师业绩汇总
  596 + var healthCoachPerformanceSql = @"
  597 + SELECT
  598 + COUNT(*) as HealthCoachCount,
  599 + SUM(F_TotalPerformance) as TotalPerformance,
  600 + SUM(F_FirstOrderPerformance) as TotalFirstOrderPerformance,
  601 + SUM(F_UpgradeOrderPerformance) as TotalUpgradeOrderPerformance,
  602 + AVG(F_TotalPerformance) as AvgPerformance
  603 + FROM lq_statistics_personal_performance
  604 + WHERE F_StatisticsMonth = @statisticsMonth";
  605 +
  606 + var healthCoachPerformance = await _db.Ado.SqlQueryAsync<dynamic>(healthCoachPerformanceSql, new { statisticsMonth });
  607 +
  608 + // 3. 金三角业绩汇总
  609 + var goldTrianglePerformanceSql = @"
  610 + SELECT
  611 + COUNT(*) as GoldTriangleCount,
  612 + SUM(F_TotalPerformance) as TotalPerformance,
  613 + SUM(F_OrderCount) as TotalOrderCount,
  614 + AVG(F_TotalPerformance) as AvgPerformance
  615 + FROM lq_statistics_gold_triangle
  616 + WHERE F_StatisticsMonth = @statisticsMonth";
  617 +
  618 + var goldTrianglePerformance = await _db.Ado.SqlQueryAsync<dynamic>(goldTrianglePerformanceSql, new { statisticsMonth });
  619 +
  620 + // 4. 消耗业绩汇总
  621 + var consumePerformanceSql = @"
  622 + SELECT
  623 + COUNT(*) as ConsumeRecordCount,
  624 + SUM(F_ConsumePerformance) as TotalConsumePerformance,
  625 + SUM(F_ConsumeQuantity) as TotalConsumeQuantity,
  626 + SUM(F_HeadCount) as TotalHeadCount,
  627 + SUM(F_PersonCount) as TotalPersonCount
  628 + FROM lq_statistics_department_consume_performance
  629 + WHERE F_StatisticsMonth = @statisticsMonth";
  630 +
  631 + var consumePerformance = await _db.Ado.SqlQueryAsync<dynamic>(consumePerformanceSql, new { statisticsMonth });
  632 +
  633 + var result = new
  634 + {
  635 + StatisticsMonth = statisticsMonth,
  636 + StorePerformance = new
  637 + {
  638 + StoreCount = storePerformance.FirstOrDefault()?.StoreCount ?? 0,
  639 + TotalPerformance = storePerformance.FirstOrDefault()?.TotalPerformance ?? 0,
  640 + TotalFirstOrderCount = storePerformance.FirstOrDefault()?.TotalFirstOrderCount ?? 0,
  641 + TotalUpgradeOrderCount = storePerformance.FirstOrDefault()?.TotalUpgradeOrderCount ?? 0,
  642 + AvgPerformance = storePerformance.FirstOrDefault()?.AvgPerformance ?? 0
  643 + },
  644 + HealthCoachPerformance = new
  645 + {
  646 + HealthCoachCount = healthCoachPerformance.FirstOrDefault()?.HealthCoachCount ?? 0,
  647 + TotalPerformance = healthCoachPerformance.FirstOrDefault()?.TotalPerformance ?? 0,
  648 + TotalFirstOrderPerformance = healthCoachPerformance.FirstOrDefault()?.TotalFirstOrderPerformance ?? 0,
  649 + TotalUpgradeOrderPerformance = healthCoachPerformance.FirstOrDefault()?.TotalUpgradeOrderPerformance ?? 0,
  650 + AvgPerformance = healthCoachPerformance.FirstOrDefault()?.AvgPerformance ?? 0
  651 + },
  652 + GoldTrianglePerformance = new
  653 + {
  654 + GoldTriangleCount = goldTrianglePerformance.FirstOrDefault()?.GoldTriangleCount ?? 0,
  655 + TotalPerformance = goldTrianglePerformance.FirstOrDefault()?.TotalPerformance ?? 0,
  656 + TotalOrderCount = goldTrianglePerformance.FirstOrDefault()?.TotalOrderCount ?? 0,
  657 + TotalMemberCount = goldTrianglePerformance.FirstOrDefault()?.TotalMemberCount ?? 0,
  658 + AvgPerformance = goldTrianglePerformance.FirstOrDefault()?.AvgPerformance ?? 0
  659 + },
  660 + ConsumePerformance = new
  661 + {
  662 + ConsumeRecordCount = consumePerformance.FirstOrDefault()?.ConsumeRecordCount ?? 0,
  663 + TotalConsumePerformance = consumePerformance.FirstOrDefault()?.TotalConsumePerformance ?? 0,
  664 + TotalConsumeQuantity = consumePerformance.FirstOrDefault()?.TotalConsumeQuantity ?? 0,
  665 + TotalHeadCount = consumePerformance.FirstOrDefault()?.TotalHeadCount ?? 0,
  666 + TotalPersonCount = consumePerformance.FirstOrDefault()?.TotalPersonCount ?? 0
  667 + }
  668 + };
  669 +
  670 + return new
  671 + {
  672 + Success = true,
  673 + Data = result,
  674 + Message = "综合仪表盘数据获取成功"
  675 + };
  676 + }
  677 + catch (Exception ex)
  678 + {
  679 + _logger.LogError(ex, $"获取综合仪表盘数据失败 - 月份: {input?.StatisticsMonth}");
  680 + throw NCCException.Oh($"获取综合仪表盘数据失败: {ex.Message}");
  681 + }
  682 + }
  683 +
  684 + #endregion
  685 + }
  686 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -1413,72 +1413,135 @@ namespace NCC.Extend.LqStatistics
1413 1413  
1414 1414 try
1415 1415 {
1416   - // 使用数据库聚合方式,直接在数据库中完成所有统计计算
1417   - // 按照开单记录统计,避免重复计算
  1416 + // 使用子查询避免重复计算,按开单记录统计业绩
1418 1417 var sql = @"
1419 1418 SELECT
1420   - jksyj.jkszh AS EmployeeId,
1421   - u.F_REALNAME AS EmployeeName,
1422   - u.F_MDID AS StoreId,
1423   - COALESCE(md.dm, '') AS StoreName,
1424   - COALESCE(jsj.F_Id, '') AS GoldTriangleId,
1425   - COALESCE(jsj.jsj, '') AS GoldTriangleName,
1426   - CASE
1427   - WHEN jsjUser.is_leader = 1 THEN '顾问'
1428   - ELSE COALESCE(u.F_GW, '')
1429   - END AS Position,
1430   - COUNT(DISTINCT jksyj.glkdbh) AS OrderCount,
1431   - COUNT(DISTINCT CASE WHEN kd.sfskdd = '是' THEN jksyj.glkdbh END) AS FirstOrderCount,
1432   - COUNT(DISTINCT CASE WHEN kd.sfskdd = '否' THEN jksyj.glkdbh END) AS UpgradeOrderCount,
1433   - SUM(CASE WHEN kd.sfskdd = '是' THEN CAST(jksyj.jksyj AS DECIMAL(18,2)) ELSE 0 END) AS FirstOrderPerformance,
1434   - SUM(CASE WHEN kd.sfskdd = '否' THEN CAST(jksyj.jksyj AS DECIMAL(18,2)) ELSE 0 END) AS UpgradeOrderPerformance,
1435   - MAX(jksyj.yjsj) AS LastOrderDate,
1436   - MIN(jksyj.yjsj) AS FirstOrderDate,
1437   - SUM(
1438   - CASE
1439   - WHEN xmzl.fl3 = '合作业绩' THEN CAST(jksyj.jksyj AS DECIMAL(18,2))
1440   - ELSE 0
1441   - END
1442   - ) AS CooperationPerformance,
1443   - SUM(
  1419 + order_stats.EmployeeId,
  1420 + order_stats.EmployeeName,
  1421 + order_stats.StoreId,
  1422 + order_stats.StoreName,
  1423 + order_stats.GoldTriangleId,
  1424 + order_stats.GoldTriangleName,
  1425 + order_stats.Position,
  1426 + order_stats.OrderCount,
  1427 + order_stats.FirstOrderCount,
  1428 + order_stats.UpgradeOrderCount,
  1429 + order_stats.FirstOrderPerformance,
  1430 + order_stats.UpgradeOrderPerformance,
  1431 + order_stats.LastOrderDate,
  1432 + order_stats.FirstOrderDate,
  1433 + COALESCE(coop_stats.CooperationPerformance, 0) AS CooperationPerformance,
  1434 + COALESCE(base_stats.BasePerformance, 0) AS BasePerformance,
  1435 + order_stats.TotalPerformance
  1436 + FROM (
  1437 + -- 按开单记录统计基础数据,避免重复计算
  1438 + SELECT
  1439 + order_summary.jkszh AS EmployeeId,
  1440 + u.F_REALNAME AS EmployeeName,
  1441 + u.F_MDID AS StoreId,
  1442 + COALESCE(md.dm, '') AS StoreName,
  1443 + COALESCE(jsjUser.F_Id, '') AS GoldTriangleId,
  1444 + COALESCE(jsjUser.jsj, '') AS GoldTriangleName,
1444 1445 CASE
1445   - WHEN xmzl.fl3 IS NULL OR xmzl.fl3 != '合作业绩' THEN CAST(jksyj.jksyj AS DECIMAL(18,2))
1446   - ELSE 0
1447   - END
1448   - ) AS BasePerformance,
1449   - SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) AS TotalPerformance
1450   - FROM lq_kd_jksyj jksyj
1451   - INNER JOIN lq_kd_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id AND pxmx.F_IsEffective = 1
1452   - INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id
1453   - INNER JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id
1454   - INNER JOIN BASE_USER u ON jksyj.jkszh = u.F_Id
1455   - LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
1456   - LEFT JOIN lq_ycsd_jsj jsj ON u.F_MDID = jsj.md AND jsj.yf = @statisticsMonth
  1446 + WHEN jsjUser.is_leader = 1 THEN '顾问'
  1447 + ELSE COALESCE(u.F_GW, '')
  1448 + END AS Position,
  1449 + COUNT(*) AS OrderCount,
  1450 + COUNT(CASE WHEN order_summary.sfskdd = '是' THEN 1 END) AS FirstOrderCount,
  1451 + COUNT(CASE WHEN order_summary.sfskdd = '否' THEN 1 END) AS UpgradeOrderCount,
  1452 + SUM(CASE WHEN order_summary.sfskdd = '是' THEN order_summary.order_performance ELSE 0 END) AS FirstOrderPerformance,
  1453 + SUM(CASE WHEN order_summary.sfskdd = '否' THEN order_summary.order_performance ELSE 0 END) AS UpgradeOrderPerformance,
  1454 + MAX(order_summary.yjsj) AS LastOrderDate,
  1455 + MIN(order_summary.yjsj) AS FirstOrderDate,
  1456 + SUM(order_summary.order_performance) AS TotalPerformance
  1457 + FROM (
  1458 + -- 先按开单记录汇总业绩,避免重复计算
  1459 + SELECT
  1460 + jksyj.jkszh,
  1461 + jksyj.glkdbh,
  1462 + kd.sfskdd,
  1463 + MAX(jksyj.yjsj) as yjsj,
  1464 + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) as order_performance
  1465 + FROM lq_kd_jksyj jksyj
  1466 + INNER JOIN lq_kd_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id AND pxmx.F_IsEffective = 1
  1467 + INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id
  1468 + WHERE jksyj.yjsj IS NOT NULL
  1469 + AND jksyj.jksyj IS NOT NULL
  1470 + AND jksyj.jksyj != ''
  1471 + AND jksyj.jksyj != '0'
  1472 + AND jksyj.F_kdpxid IS NOT NULL
  1473 + AND jksyj.F_kdpxid != ''
  1474 + AND jksyj.F_IsEffective = 1
  1475 + AND YEAR(jksyj.yjsj) = @year
  1476 + AND MONTH(jksyj.yjsj) = @month
  1477 + GROUP BY jksyj.jkszh, jksyj.glkdbh, kd.sfskdd
  1478 + ) order_summary
  1479 + INNER JOIN BASE_USER u ON order_summary.jkszh = u.F_Id
  1480 + LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
  1481 + LEFT JOIN (
  1482 + SELECT
  1483 + jsjUser.user_id,
  1484 + MIN(jsjUser.jsj_id) as F_Id,
  1485 + MIN(jsj.jsj) as jsj,
  1486 + MIN(jsjUser.is_leader) as is_leader
  1487 + FROM lq_jinsanjiao_user jsjUser
  1488 + INNER JOIN lq_ycsd_jsj jsj ON jsjUser.jsj_id COLLATE utf8mb4_general_ci = jsj.F_Id COLLATE utf8mb4_general_ci AND jsj.yf = @statisticsMonth
  1489 + WHERE jsjUser.F_Month = @statisticsMonth
  1490 + AND jsjUser.status = 'ACTIVE'
  1491 + AND jsjUser.F_DeleteMark = 0
  1492 + GROUP BY jsjUser.user_id
  1493 + ) jsjUser ON order_summary.jkszh = jsjUser.user_id
  1494 + GROUP BY
  1495 + order_summary.jkszh,
  1496 + u.F_REALNAME,
  1497 + u.F_MDID,
  1498 + md.dm,
  1499 + jsjUser.F_Id,
  1500 + jsjUser.jsj,
  1501 + jsjUser.is_leader,
  1502 + u.F_GW
  1503 + ) order_stats
1457 1504 LEFT JOIN (
1458   - SELECT DISTINCT user_id, F_Month, MAX(is_leader) as is_leader
1459   - FROM lq_jinsanjiao_user
1460   - WHERE F_Month = @statisticsMonth
1461   - GROUP BY user_id, F_Month
1462   - ) jsjUser ON jksyj.jkszh = jsjUser.user_id
1463   - WHERE jksyj.yjsj IS NOT NULL
1464   - AND jksyj.jksyj IS NOT NULL
1465   - AND jksyj.jksyj != ''
1466   - AND jksyj.jksyj != '0'
1467   - AND jksyj.F_kdpxid IS NOT NULL
1468   - AND jksyj.F_kdpxid != ''
1469   - AND jksyj.F_IsEffective = 1
1470   - AND YEAR(jksyj.yjsj) = @year
1471   - AND MONTH(jksyj.yjsj) = @month
1472   - GROUP BY
1473   - jksyj.jkszh,
1474   - u.F_REALNAME,
1475   - u.F_MDID,
1476   - md.dm,
1477   - jsj.F_Id,
1478   - jsj.jsj,
1479   - jsjUser.is_leader,
1480   - u.F_GW
1481   - ORDER BY TotalPerformance DESC";
  1505 + -- 合作业绩统计
  1506 + SELECT
  1507 + jksyj.jkszh AS EmployeeId,
  1508 + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) AS CooperationPerformance
  1509 + FROM lq_kd_jksyj jksyj
  1510 + INNER JOIN lq_kd_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id AND pxmx.F_IsEffective = 1
  1511 + INNER JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id
  1512 + WHERE jksyj.yjsj IS NOT NULL
  1513 + AND jksyj.jksyj IS NOT NULL
  1514 + AND jksyj.jksyj != ''
  1515 + AND jksyj.jksyj != '0'
  1516 + AND jksyj.F_kdpxid IS NOT NULL
  1517 + AND jksyj.F_kdpxid != ''
  1518 + AND jksyj.F_IsEffective = 1
  1519 + AND YEAR(jksyj.yjsj) = @year
  1520 + AND MONTH(jksyj.yjsj) = @month
  1521 + AND xmzl.fl3 = '合作业绩'
  1522 + GROUP BY jksyj.jkszh
  1523 + ) coop_stats ON order_stats.EmployeeId = coop_stats.EmployeeId
  1524 + LEFT JOIN (
  1525 + -- 基础业绩统计
  1526 + SELECT
  1527 + jksyj.jkszh AS EmployeeId,
  1528 + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) AS BasePerformance
  1529 + FROM lq_kd_jksyj jksyj
  1530 + INNER JOIN lq_kd_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id AND pxmx.F_IsEffective = 1
  1531 + LEFT JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id
  1532 + WHERE jksyj.yjsj IS NOT NULL
  1533 + AND jksyj.jksyj IS NOT NULL
  1534 + AND jksyj.jksyj != ''
  1535 + AND jksyj.jksyj != '0'
  1536 + AND jksyj.F_kdpxid IS NOT NULL
  1537 + AND jksyj.F_kdpxid != ''
  1538 + AND jksyj.F_IsEffective = 1
  1539 + AND YEAR(jksyj.yjsj) = @year
  1540 + AND MONTH(jksyj.yjsj) = @month
  1541 + AND (xmzl.fl3 IS NULL OR xmzl.fl3 != '合作业绩')
  1542 + GROUP BY jksyj.jkszh
  1543 + ) base_stats ON order_stats.EmployeeId = base_stats.EmployeeId
  1544 + ORDER BY order_stats.TotalPerformance DESC";
1482 1545  
1483 1546 // 解析统计月份
1484 1547 var year = int.Parse(statisticsMonth.Substring(0, 4));
... ... @@ -2139,13 +2202,39 @@ namespace NCC.Extend.LqStatistics
2139 2202 @statisticsMonth as F_StatisticsMonth,
2140 2203 COALESCE(SUM(jksyj.jksyj), 0) as F_ConsumePerformance,
2141 2204 COALESCE(SUM(jksyj.F_kdpxNumber), 0) as F_ConsumeQuantity,
2142   - COALESCE(SUM(jksyj.F_LaborCost), 0) as F_ManualFee
  2205 + COALESCE(SUM(jksyj.F_LaborCost), 0) as F_ManualFee,
  2206 + COALESCE(headcount_stats.F_HeadCount, 0) as F_HeadCount,
  2207 + COALESCE(personcount_stats.F_PersonCount, 0) as F_PersonCount
2143 2208 FROM lq_xh_jksyj jksyj
2144 2209 INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
2145 2210 LEFT JOIN lq_mdxx md ON hyhk.md = md.F_Id
  2211 + LEFT JOIN (
  2212 + -- 人头统计:月度去重客户数
  2213 + SELECT
  2214 + jksyj.jkszh,
  2215 + hyhk.md,
  2216 + COUNT(DISTINCT hyhk.hy) as F_HeadCount
  2217 + FROM lq_xh_jksyj jksyj
  2218 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
  2219 + WHERE jksyj.F_IsEffective = 1
  2220 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @statisticsMonth
  2221 + GROUP BY jksyj.jkszh, hyhk.md
  2222 + ) headcount_stats ON jksyj.jkszh = headcount_stats.jkszh AND hyhk.md = headcount_stats.md
  2223 + LEFT JOIN (
  2224 + -- 人次统计:日度去重到店数
  2225 + SELECT
  2226 + jksyj.jkszh,
  2227 + hyhk.md,
  2228 + COUNT(DISTINCT DATE(hyhk.hksj)) as F_PersonCount
  2229 + FROM lq_xh_jksyj jksyj
  2230 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
  2231 + WHERE jksyj.F_IsEffective = 1
  2232 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @statisticsMonth
  2233 + GROUP BY jksyj.jkszh, hyhk.md
  2234 + ) personcount_stats ON jksyj.jkszh = personcount_stats.jkszh AND hyhk.md = personcount_stats.md
2146 2235 WHERE jksyj.F_IsEffective = 1
2147 2236 AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @statisticsMonth
2148   - GROUP BY jksyj.jkszh, jksyj.jksxm, hyhk.md, md.mdbm, md.dm";
  2237 + GROUP BY jksyj.jkszh, jksyj.jksxm, hyhk.md, md.mdbm, md.dm, headcount_stats.F_HeadCount, personcount_stats.F_PersonCount";
2149 2238  
2150 2239 var healthCoachData = await _db.Ado.SqlQueryAsync<dynamic>(healthCoachSql, new { statisticsMonth });
2151 2240 foreach (var item in healthCoachData)
... ... @@ -2196,6 +2285,8 @@ namespace NCC.Extend.LqStatistics
2196 2285 ConsumeQuantity = Convert.ToDecimal(item.F_ConsumeQuantity ?? 0),
2197 2286 ManualFee = Convert.ToDecimal(item.F_ManualFee ?? 0),
2198 2287 IsNewStore = isNewStore,
  2288 + HeadCount = Convert.ToDecimal(item.F_HeadCount ?? 0),
  2289 + PersonCount = Convert.ToDecimal(item.F_PersonCount ?? 0),
2199 2290 CreateTime = DateTime.Now
2200 2291 });
2201 2292 }
... ... @@ -2213,7 +2304,9 @@ namespace NCC.Extend.LqStatistics
2213 2304 @statisticsMonth as F_StatisticsMonth,
2214 2305 COALESCE(SUM(kjbsyj.kjblsyj), 0) as F_ConsumePerformance,
2215 2306 COALESCE(SUM(kjbsyj.F_hdpxNumber), 0) as F_ConsumeQuantity,
2216   - COALESCE(SUM(kjbsyj.F_LaborCost), 0) as F_ManualFee
  2307 + COALESCE(SUM(kjbsyj.F_LaborCost), 0) as F_ManualFee,
  2308 + 0 as F_HeadCount,
  2309 + 0 as F_PersonCount
2217 2310 FROM lq_xh_kjbsyj kjbsyj
2218 2311 INNER JOIN lq_xh_hyhk hyhk ON kjbsyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
2219 2312 LEFT JOIN lq_mdxx md ON hyhk.md = md.F_Id
... ... @@ -2270,6 +2363,8 @@ namespace NCC.Extend.LqStatistics
2270 2363 ConsumeQuantity = Convert.ToDecimal(item.F_ConsumeQuantity ?? 0),
2271 2364 ManualFee = Convert.ToDecimal(item.F_ManualFee ?? 0),
2272 2365 IsNewStore = isNewStore,
  2366 + HeadCount = Convert.ToDecimal(item.F_HeadCount ?? 0),
  2367 + PersonCount = Convert.ToDecimal(item.F_PersonCount ?? 0),
2273 2368 CreateTime = DateTime.Now
2274 2369 });
2275 2370 }
... ... @@ -2408,24 +2503,47 @@ namespace NCC.Extend.LqStatistics
2408 2503 // 统计门店总业绩数据
2409 2504 var storePerformanceSql = @"
2410 2505 SELECT
2411   - kd.djmd as F_StoreId,
2412   - md.dm as F_StoreName,
2413   - @statisticsMonth as F_StatisticsMonth,
2414   - COALESCE(SUM(kd.zdyj), 0) as F_TotalPerformance,
2415   - COALESCE(SUM(kd.qk), 0) as F_DebtAmount,
2416   - COALESCE(SUM(kd.sfyj), 0) as F_TotalOrderPerformance,
2417   - COALESCE(SUM(kd.F_DeductAmount), 0) as F_StorageDeductionAmount,
2418   - COUNT(pxmx.F_Id) as F_ItemQuantity,
2419   - COUNT(DISTINCT CASE WHEN kd.sfskdd = '是' THEN kd.F_Id END) as F_FirstOrderCount,
2420   - COUNT(DISTINCT CASE WHEN kd.sfskdd = '否' THEN kd.F_Id END) as F_UpgradeOrderCount,
2421   - SUM(CASE WHEN kd.sfskdd = '是' THEN COALESCE(kd.zdyj, 0) ELSE 0 END) as F_FirstOrderPerformance,
2422   - SUM(CASE WHEN kd.sfskdd = '否' THEN COALESCE(kd.zdyj, 0) ELSE 0 END) as F_UpgradeOrderPerformance
2423   - FROM lq_kd_kdjlb kd
2424   - LEFT JOIN lq_mdxx md ON kd.djmd = md.F_Id
2425   - LEFT JOIN lq_kd_pxmx pxmx ON kd.F_Id = pxmx.glkdbh AND pxmx.F_IsEffective = 1
2426   - WHERE kd.F_IsEffective = 1
2427   - AND DATE_FORMAT(kd.kdrq, '%Y%m') = @statisticsMonth
2428   - GROUP BY kd.djmd, md.dm";
  2506 + store_data.F_StoreId,
  2507 + store_data.F_StoreName,
  2508 + store_data.F_StatisticsMonth,
  2509 + store_data.F_TotalPerformance,
  2510 + store_data.F_DebtAmount,
  2511 + store_data.F_TotalOrderPerformance,
  2512 + store_data.F_StorageDeductionAmount,
  2513 + COALESCE(item_data.F_ItemQuantity, 0) as F_ItemQuantity,
  2514 + store_data.F_FirstOrderCount,
  2515 + store_data.F_UpgradeOrderCount,
  2516 + store_data.F_FirstOrderPerformance,
  2517 + store_data.F_UpgradeOrderPerformance
  2518 + FROM (
  2519 + SELECT
  2520 + kd.djmd as F_StoreId,
  2521 + md.dm as F_StoreName,
  2522 + @statisticsMonth as F_StatisticsMonth,
  2523 + COALESCE(SUM(kd.zdyj), 0) as F_TotalPerformance,
  2524 + COALESCE(SUM(kd.qk), 0) as F_DebtAmount,
  2525 + COALESCE(SUM(kd.sfyj), 0) as F_TotalOrderPerformance,
  2526 + COALESCE(SUM(kd.F_DeductAmount), 0) as F_StorageDeductionAmount,
  2527 + COUNT(DISTINCT CASE WHEN kd.sfskdd = '是' THEN kd.F_Id END) as F_FirstOrderCount,
  2528 + COUNT(DISTINCT CASE WHEN kd.sfskdd = '否' THEN kd.F_Id END) as F_UpgradeOrderCount,
  2529 + SUM(CASE WHEN kd.sfskdd = '是' THEN COALESCE(kd.zdyj, 0) ELSE 0 END) as F_FirstOrderPerformance,
  2530 + SUM(CASE WHEN kd.sfskdd = '否' THEN COALESCE(kd.zdyj, 0) ELSE 0 END) as F_UpgradeOrderPerformance
  2531 + FROM lq_kd_kdjlb kd
  2532 + LEFT JOIN lq_mdxx md ON kd.djmd = md.F_Id
  2533 + WHERE kd.F_IsEffective = 1
  2534 + AND DATE_FORMAT(kd.kdrq, '%Y%m') = @statisticsMonth
  2535 + GROUP BY kd.djmd, md.dm
  2536 + ) store_data
  2537 + LEFT JOIN (
  2538 + SELECT
  2539 + kd.djmd as F_StoreId,
  2540 + COUNT(pxmx.F_ProjectNumber) as F_ItemQuantity
  2541 + FROM lq_kd_kdjlb kd
  2542 + LEFT JOIN lq_kd_pxmx pxmx ON kd.F_Id = pxmx.glkdbh AND pxmx.F_IsEffective = 1
  2543 + WHERE kd.F_IsEffective = 1
  2544 + AND DATE_FORMAT(kd.kdrq, '%Y%m') = @statisticsMonth
  2545 + GROUP BY kd.djmd
  2546 + ) item_data ON store_data.F_StoreId = item_data.F_StoreId";
2429 2547  
2430 2548 var storePerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(storePerformanceSql, new { statisticsMonth });
2431 2549  
... ... @@ -2656,7 +2774,7 @@ namespace NCC.Extend.LqStatistics
2656 2774 RewardPerformance = x.RewardPerformance,
2657 2775 StoreTotalPerformance = x.StoreTotalPerformance,
2658 2776 TeamPerformance = x.TeamPerformance,
2659   - PerformanceRatio = x.Percentage,
  2777 + Percentage = x.Percentage,
2660 2778 NewCustomerPerformance = x.NewCustomerPerformance,
2661 2779 NewCustomerConversionRate = x.NewCustomerConversionRate,
2662 2780 NewCustomerPoint = x.NewCustomerPoint,
... ... @@ -2664,8 +2782,8 @@ namespace NCC.Extend.LqStatistics
2664 2782 UpgradePoint = x.UpgradePoint,
2665 2783 Consumption = x.Consumption,
2666 2784 ProjectCount = x.ProjectCount,
2667   - AttendanceDays = x.CustomerCount,
2668   - StoreDays = x.WorkingDays,
  2785 + CustomerCount = x.CustomerCount,
  2786 + WorkingDays = x.WorkingDays,
2669 2787 LeaveDays = x.LeaveDays,
2670 2788 CommissionPoint = x.CommissionPoint,
2671 2789 BasePerformanceCommission = x.BasePerformanceCommission,
... ... @@ -2736,7 +2854,10 @@ namespace NCC.Extend.LqStatistics
2736 2854 u.F_RealName as EmployeeName,
2737 2855 u.F_MDID as StoreId,
2738 2856 COALESCE(md.dm, '') as StoreName,
2739   - COALESCE(u.F_GW, '') as Position,
  2857 + CASE
  2858 + WHEN jsj.is_leader = 1 THEN '顾问'
  2859 + ELSE COALESCE(u.F_GW, '')
  2860 + END as Position,
2740 2861 COALESCE(jsj.jsj, '') as GoldTriangleTeam,
2741 2862 COALESCE(jsj.jsj_id, '') as GoldTriangleId
2742 2863 FROM BASE_USER u
... ... @@ -2745,13 +2866,13 @@ namespace NCC.Extend.LqStatistics
2745 2866 SELECT
2746 2867 jsu.user_id,
2747 2868 jsj.jsj,
2748   - jsj.F_Id as jsj_id
  2869 + jsj.F_Id as jsj_id,
  2870 + jsu.is_leader
2749 2871 FROM lq_jinsanjiao_user jsu
2750 2872 INNER JOIN lq_ycsd_jsj jsj ON jsu.jsj_id COLLATE utf8mb4_general_ci = jsj.F_Id COLLATE utf8mb4_general_ci
2751   - WHERE jsu.F_Month = @statisticsMonth
  2873 + WHERE jsu.F_Month COLLATE utf8mb4_general_ci = @statisticsMonth
2752 2874 AND jsu.status = 'ACTIVE'
2753 2875 AND (jsu.F_DeleteMark IS NULL OR jsu.F_DeleteMark = 0)
2754   - AND jsj.yf = @statisticsMonth
2755 2876 ) jsj ON u.F_Id COLLATE utf8mb4_general_ci = jsj.user_id COLLATE utf8mb4_general_ci
2756 2877 WHERE u.F_EnabledMark = 1 AND u.F_GW = '健康师'
2757 2878 AND (u.F_DeleteMark IS NULL OR u.F_DeleteMark = 0)";
... ... @@ -2809,7 +2930,6 @@ namespace NCC.Extend.LqStatistics
2809 2930 s.F_TotalPerformance = p.F_TotalPerformance,
2810 2931 s.F_BasePerformance = p.F_BasePerformance,
2811 2932 s.F_CooperationPerformance = p.F_CooperationPerformance,
2812   - s.F_ProjectCount = p.F_OrderCount,
2813 2933 s.F_NewCustomerPerformance = p.F_FirstOrderPerformance,
2814 2934 s.F_UpgradePerformance = p.F_UpgradeOrderPerformance
2815 2935 WHERE s.F_StatisticsMonth = @statisticsMonth";
... ... @@ -2827,7 +2947,7 @@ namespace NCC.Extend.LqStatistics
2827 2947 ON s.F_StoreId COLLATE utf8mb4_unicode_ci = st.F_StoreId COLLATE utf8mb4_unicode_ci
2828 2948 AND s.F_StatisticsMonth = st.F_StatisticsMonth
2829 2949 SET
2830   - s.F_StoreTotalPerformance = st.F_TotalPerformance
  2950 + s.F_StoreTotalPerformance = st.F_TotalOrderPerformance
2831 2951 WHERE s.F_StatisticsMonth = @statisticsMonth";
2832 2952  
2833 2953 await _db.Ado.ExecuteCommandAsync(storePerformanceSql, new
... ... @@ -2854,9 +2974,76 @@ namespace NCC.Extend.LqStatistics
2854 2974 userId = _userManager.UserId
2855 2975 });
2856 2976  
  2977 + // 计算占比:队伍业绩 / 个人总业绩
  2978 + var percentageSql = @"
  2979 + UPDATE lq_salary_statistics
  2980 + SET F_Percentage = CASE
  2981 + WHEN F_TotalPerformance > 0 THEN ROUND(( F_TotalPerformance / F_TeamPerformance) * 100, 2)
  2982 + ELSE 0
  2983 + END
  2984 + WHERE F_StatisticsMonth = @statisticsMonth
  2985 + AND F_TeamPerformance > 0";
  2986 +
  2987 + await _db.Ado.ExecuteCommandAsync(percentageSql, new
  2988 + {
  2989 + statisticsMonth,
  2990 + userId = _userManager.UserId
  2991 + });
  2992 +
  2993 + // 从个人消耗业绩统计表更新消耗数据
  2994 + var consumePerformanceSql = @"
  2995 + UPDATE lq_salary_statistics s
  2996 + INNER JOIN lq_statistics_department_consume_performance cp
  2997 + ON s.F_EmployeeId COLLATE utf8mb4_general_ci = cp.F_UserId COLLATE utf8mb4_general_ci
  2998 + AND s.F_StatisticsMonth COLLATE utf8mb4_general_ci = cp.F_StatisticsMonth COLLATE utf8mb4_general_ci
  2999 + SET
  3000 + s.F_Consumption = cp.F_ConsumePerformance,
  3001 + s.F_ProjectCount = cp.F_ConsumeQuantity,
  3002 + s.F_HandworkFee = cp.F_ManualFee,
  3003 + s.F_CustomerCount = cp.F_HeadCount
  3004 + WHERE s.F_StatisticsMonth = @statisticsMonth";
  3005 +
  3006 + await _db.Ado.ExecuteCommandAsync(consumePerformanceSql, new
  3007 + {
  3008 + statisticsMonth,
  3009 + userId = _userManager.UserId
  3010 + });
  3011 +
  3012 + // 计算并更新底薪
  3013 + await CalculateAndUpdateBaseSalary(statisticsMonth);
  3014 +
2857 3015 _logger.LogInformation($"从其他统计表更新工资数据完成 - 月份: {statisticsMonth}");
2858 3016 }
2859 3017  
  3018 + /// <summary>
  3019 + /// 计算并更新健康师底薪
  3020 + /// </summary>
  3021 + private async Task CalculateAndUpdateBaseSalary(string statisticsMonth)
  3022 + {
  3023 + // 健康师底薪计算
  3024 + var healthCoachSalarySql = @"
  3025 + UPDATE lq_salary_statistics s
  3026 + SET s.F_HealthCoachBaseSalary = CASE
  3027 + -- 三星:月消耗达到40000元 且 项目数达到156个 → 底薪2400元
  3028 + WHEN s.F_Consumption >= 40000 AND s.F_ProjectCount >= 156 THEN 2400
  3029 + -- 二星:月消耗达到20000元 且 项目数达到126个 → 底薪2200元
  3030 + WHEN s.F_Consumption >= 20000 AND s.F_ProjectCount >= 126 THEN 2200
  3031 + -- 一星:月消耗达到10000元 或 项目数达到96个 → 底薪2000元
  3032 + WHEN s.F_Consumption >= 10000 OR s.F_ProjectCount >= 96 THEN 2000
  3033 + -- 0星:月消耗未达到10000元 且 项目数未达到96个 → 底薪1800元
  3034 + ELSE 1800
  3035 + END
  3036 + WHERE s.F_StatisticsMonth = @statisticsMonth
  3037 + AND s.F_Position = '健康师' OR s.F_Position = '顾问'";
  3038 +
  3039 + await _db.Ado.ExecuteCommandAsync(healthCoachSalarySql, new
  3040 + {
  3041 + statisticsMonth
  3042 + });
  3043 +
  3044 + _logger.LogInformation($"健康师底薪计算完成 - 月份: {statisticsMonth}");
  3045 + }
  3046 +
2860 3047 #endregion
2861 3048  
2862 3049 #region 其他统计模块列表查询接口
... ... @@ -3055,7 +3242,9 @@ namespace NCC.Extend.LqStatistics
3055 3242 Position = it.DepartmentType,
3056 3243 TotalPerformance = it.ConsumePerformance, // 使用ConsumePerformance作为总业绩
3057 3244 ConsumePerformance = it.ConsumePerformance,
3058   - OrderCount = (int)it.ConsumeQuantity, // 使用ConsumeQuantity作为订单数量
  3245 + OrderCount = it.ConsumeQuantity, // 使用ConsumeQuantity作为订单数量
  3246 + HeadCount = it.HeadCount, // 人头数
  3247 + PersonCount = it.PersonCount, // 人次
3059 3248 CreateTime = it.CreateTime.HasValue ? it.CreateTime.Value : DateTime.Now
3060 3249 }).ToList();
3061 3250  
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -346,18 +346,7 @@ namespace NCC.Extend.LqXhHyhk
346 346 // 批量插入品项明细
347 347 if (allPxmxEntities.Any())
348 348 {
349   - // 分别处理插入和更新
350   - var existingEntities = allPxmxEntities.Where(e => !string.IsNullOrEmpty(e.Id)).ToList();
351   - var newEntities = allPxmxEntities.Where(e => string.IsNullOrEmpty(e.Id)).ToList();
352   -
353   - if (existingEntities.Any())
354   - {
355   - await _db.Updateable(existingEntities).ExecuteCommandAsync();
356   - }
357   - if (newEntities.Any())
358   - {
359   - await _db.Insertable(newEntities).ExecuteCommandAsync();
360   - }
  349 + await _db.Insertable(allPxmxEntities).ExecuteCommandAsync();
361 350 }
362 351 // 批量插入健康师业绩
363 352 if (allJksyjEntities.Any())
... ...