Commit caa83dede2abb7b720f603e929f237ca59e47d3e

Authored by “wangming”
1 parent 070faa12

111

antis-ncc-admin/.env.development
1 # 开发 1 # 开发
2 2
3 VUE_CLI_BABEL_TRANSPILE_MODULES = true 3 VUE_CLI_BABEL_TRANSPILE_MODULES = true
4 -VUE_APP_BASE_API = 'http://lvqian.antissoft.com' 4 +# VUE_APP_BASE_API = 'http://lvqian.antissoft.com'
  5 +VUE_APP_BASE_API = 'http://localhost:2011'
5 VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket' 6 VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket'
antis-ncc-admin/src/api/extend/salaryCalculation.js
@@ -3,7 +3,7 @@ import request from '@/utils/request' @@ -3,7 +3,7 @@ import request from '@/utils/request'
3 // 保存金三角开卡业绩统计数据 3 // 保存金三角开卡业绩统计数据
4 export function saveGoldTriangleStatistics(statisticsMonth) { 4 export function saveGoldTriangleStatistics(statisticsMonth) {
5 return request({ 5 return request({
6 - url: '/api/Extend/LqStatistics/save-gold-triangle-statistics', 6 + url: '/api/Extend/LqStatistics/save-gold-triangle-stats',
7 method: 'POST', 7 method: 'POST',
8 data: { statisticsMonth } 8 data: { statisticsMonth }
9 }) 9 })
@@ -12,7 +12,7 @@ export function saveGoldTriangleStatistics(statisticsMonth) { @@ -12,7 +12,7 @@ export function saveGoldTriangleStatistics(statisticsMonth) {
12 // 保存健康师个人开单业绩统计数据 12 // 保存健康师个人开单业绩统计数据
13 export function savePersonalPerformanceStatistics(statisticsMonth) { 13 export function savePersonalPerformanceStatistics(statisticsMonth) {
14 return request({ 14 return request({
15 - url: '/api/Extend/LqStatistics/save-personal-performance-statistics', 15 + url: '/api/Extend/LqStatistics/save-personal-performance-stats',
16 method: 'POST', 16 method: 'POST',
17 data: { statisticsMonth } 17 data: { statisticsMonth }
18 }) 18 })
@@ -21,7 +21,7 @@ export function savePersonalPerformanceStatistics(statisticsMonth) { @@ -21,7 +21,7 @@ export function savePersonalPerformanceStatistics(statisticsMonth) {
21 // 保存科技部开单业绩统计数据 21 // 保存科技部开单业绩统计数据
22 export function saveTechPerformanceStatistics(statisticsMonth) { 22 export function saveTechPerformanceStatistics(statisticsMonth) {
23 return request({ 23 return request({
24 - url: '/api/Extend/LqStatistics/save-tech-performance-statistics', 24 + url: '/api/Extend/LqStatistics/save-tech-performance-stats',
25 method: 'POST', 25 method: 'POST',
26 data: { statisticsMonth } 26 data: { statisticsMonth }
27 }) 27 })
antis-ncc-admin/src/utils/define.js
1 // 开发环境接口配置 1 // 开发环境接口配置
2 // JAVA Boot版本对应后端接口地址 2 // JAVA Boot版本对应后端接口地址
3 // JAVA Cloud对应网关地址 3 // JAVA Cloud对应网关地址
4 -const APIURl = 'http://localhost:8061' 4 +const APIURl = 'http://localhost:2011'
5 5
6 module.exports = { 6 module.exports = {
7 APIURl: APIURl, 7 APIURl: APIURl,
antis-ncc-admin/src/views/salaryCalculation/index.vue
1 <template> 1 <template>
2 - <div class="NCC-common-layout">  
3 - <div class="NCC-common-layout-center">  
4 - <!-- 页面标题 -->  
5 - <div class="page-header">  
6 - <h2 class="page-title">  
7 - <i class="el-icon-calculator"></i>  
8 - 工资计算系统  
9 - </h2>  
10 - <p class="page-description">统计各类业绩数据,为工资计算提供基础数据支撑</p> 2 + <div class="salary-calculation-container">
  3 + <!-- 页面头部 -->
  4 + <div class="header-card">
  5 + <h1 class="title">工资计算与统计系统</h1>
  6 + <p class="subtitle">智能生成各类业绩统计数据,一键完成工资计算</p>
  7 + </div>
  8 +
  9 + <!-- 月份选择区域 -->
  10 + <el-card class="box-card month-selector-card">
  11 + <div slot="header" class="clearfix">
  12 + <span><i class="el-icon-date"></i> 选择统计月份</span>
  13 + <el-button style="float: right; padding: 3px 0" type="text" @click="resetMonth">重置</el-button>
11 </div> 14 </div>
  15 + <div class="month-picker-wrapper">
  16 + <el-date-picker
  17 + v-model="statisticsMonth"
  18 + type="month"
  19 + placeholder="选择月份"
  20 + format="yyyy年MM月"
  21 + value-format="yyyyMM"
  22 + :clearable="false"
  23 + class="month-picker"
  24 + >
  25 + </el-date-picker>
  26 + </div>
  27 + </el-card>
12 28
13 - <!-- 统计月份选择 -->  
14 - <div class="NCC-common-search-box">  
15 - <el-form :inline="true" class="statistics-form">  
16 - <el-form-item label="统计月份">  
17 - <el-date-picker  
18 - v-model="statisticsMonth"  
19 - type="month"  
20 - placeholder="选择月份"  
21 - format="yyyyMM"  
22 - value-format="yyyyMM"  
23 - :clearable="false"  
24 - class="month-picker"  
25 - />  
26 - </el-form-item>  
27 - <el-form-item>  
28 - <el-button  
29 - type="primary"  
30 - icon="el-icon-refresh"  
31 - @click="refreshMonth"  
32 - :loading="loading" 29 + <!-- 主要内容区域 - 左右布局 -->
  30 + <div class="main-content">
  31 + <!-- 左侧:统计操作区域 -->
  32 + <div class="left-panel">
  33 + <!-- 一键计算工资 -->
  34 + <el-card class="box-card calculate-card">
  35 + <div slot="header" class="clearfix">
  36 + <span><i class="el-icon-cpu"></i> 一键计算工资</span>
  37 + </div>
  38 + <div class="calculate-content">
  39 + <p class="calculate-desc">按顺序执行所有统计方法,生成完整的工资数据</p>
  40 + <el-button
  41 + type="primary"
  42 + size="large"
  43 + :loading="isCalculating"
  44 + @click="handleCalculateSalary"
  45 + class="calculate-button"
  46 + :disabled="!statisticsMonth"
33 > 47 >
34 - 刷新月份 48 + <i class="el-icon-magic-stick"></i>
  49 + {{ isCalculating ? '计算中...' : '一键计算工资' }}
35 </el-button> 50 </el-button>
36 - </el-form-item>  
37 - </el-form>  
38 - </div>  
39 -  
40 - <!-- 统计按钮区域 -->  
41 - <div class="statistics-buttons">  
42 - <div class="button-grid">  
43 - <!-- 金三角开卡业绩统计 -->  
44 - <div class="statistics-card gold-triangle">  
45 - <div class="card-header">  
46 - <i class="el-icon-trophy"></i>  
47 - <h3>金三角开卡业绩统计</h3> 51 + <div v-if="calculationProgress.length > 0" class="progress-info">
  52 + <div class="progress-title">计算进度:</div>
  53 + <div v-for="(step, index) in calculationProgress" :key="index" class="progress-step">
  54 + <i :class="getProgressIcon(step.status)" :style="{ color: getProgressColor(step.status) }"></i>
  55 + <span :class="{ 'completed': step.status === 'completed', 'failed': step.status === 'failed' }">
  56 + {{ step.name }}
  57 + </span>
  58 + </div>
48 </div> 59 </div>
49 - <div class="card-content">  
50 - <p class="card-description">统计金三角团队的开卡业绩数据</p>  
51 - <el-button  
52 - type="primary"  
53 - size="large" 60 + </div>
  61 + </el-card>
  62 +
  63 + <!-- 单独统计操作 -->
  64 + <el-card class="box-card statistics-card">
  65 + <div slot="header" class="clearfix">
  66 + <span><i class="el-icon-s-operation"></i> 单独统计操作</span>
  67 + </div>
  68 + <div class="statistics-grid">
  69 + <div class="stat-item blue-item">
  70 + <div class="stat-header">
  71 + <i class="el-icon-s-data"></i>
  72 + <span>金三角开卡业绩</span>
  73 + </div>
  74 + <el-button
  75 + type="primary"
54 :loading="loadingStates.goldTriangle" 76 :loading="loadingStates.goldTriangle"
55 @click="handleGoldTriangleStatistics" 77 @click="handleGoldTriangleStatistics"
56 - class="statistics-btn" 78 + class="stat-button"
  79 + :disabled="!statisticsMonth"
57 > 80 >
58 - <i class="el-icon-data-analysis"></i>  
59 - 开始统计 81 + 立即统计
60 </el-button> 82 </el-button>
61 </div> 83 </div>
62 - </div>  
63 84
64 - <!-- 健康师个人开单业绩统计 -->  
65 - <div class="statistics-card personal-performance">  
66 - <div class="card-header">  
67 - <i class="el-icon-user"></i>  
68 - <h3>健康师个人开单业绩统计</h3>  
69 - </div>  
70 - <div class="card-content">  
71 - <p class="card-description">统计健康师个人开单业绩,包含基础业绩和合作业绩</p>  
72 - <el-button  
73 - type="success"  
74 - size="large" 85 + <div class="stat-item green-item">
  86 + <div class="stat-header">
  87 + <i class="el-icon-user"></i>
  88 + <span>健康师个人开单业绩</span>
  89 + </div>
  90 + <el-button
  91 + type="success"
75 :loading="loadingStates.personalPerformance" 92 :loading="loadingStates.personalPerformance"
76 @click="handlePersonalPerformanceStatistics" 93 @click="handlePersonalPerformanceStatistics"
77 - class="statistics-btn" 94 + class="stat-button"
  95 + :disabled="!statisticsMonth"
78 > 96 >
79 - <i class="el-icon-data-analysis"></i>  
80 - 开始统计 97 + 立即统计
81 </el-button> 98 </el-button>
82 </div> 99 </div>
83 - </div>  
84 100
85 - <!-- 科技部开单业绩统计 -->  
86 - <div class="statistics-card tech-performance">  
87 - <div class="card-header">  
88 - <i class="el-icon-cpu"></i>  
89 - <h3>科技部开单业绩统计</h3>  
90 - </div>  
91 - <div class="card-content">  
92 - <p class="card-description">统计科技部老师的开单业绩数据</p>  
93 - <el-button  
94 - type="warning"  
95 - size="large" 101 + <div class="stat-item orange-item">
  102 + <div class="stat-header">
  103 + <i class="el-icon-s-promotion"></i>
  104 + <span>科技部开单业绩</span>
  105 + </div>
  106 + <el-button
  107 + type="warning"
96 :loading="loadingStates.techPerformance" 108 :loading="loadingStates.techPerformance"
97 @click="handleTechPerformanceStatistics" 109 @click="handleTechPerformanceStatistics"
98 - class="statistics-btn" 110 + class="stat-button"
  111 + :disabled="!statisticsMonth"
99 > 112 >
100 - <i class="el-icon-data-analysis"></i>  
101 - 开始统计 113 + 立即统计
  114 + </el-button>
  115 + </div>
  116 +
  117 + <!-- 预留位置,用于未来添加更多统计方法 -->
  118 + <div class="stat-item placeholder-item">
  119 + <div class="stat-header">
  120 + <i class="el-icon-plus"></i>
  121 + <span>更多统计方法</span>
  122 + </div>
  123 + <el-button
  124 + type="info"
  125 + disabled
  126 + class="stat-button"
  127 + >
  128 + 敬请期待
102 </el-button> 129 </el-button>
103 </div> 130 </div>
104 </div> 131 </div>
105 - </div> 132 + </el-card>
106 </div> 133 </div>
107 134
108 - <!-- 结果显示区域 -->  
109 - <div class="result-section">  
110 - <div class="result-header">  
111 - <h3>  
112 - <i class="el-icon-document"></i>  
113 - 统计结果  
114 - </h3>  
115 - <el-button  
116 - type="text"  
117 - icon="el-icon-delete"  
118 - @click="clearResults"  
119 - v-if="resultData.length > 0"  
120 - >  
121 - 清空结果  
122 - </el-button>  
123 - </div>  
124 -  
125 - <div class="result-content" v-if="resultData.length > 0">  
126 - <div  
127 - v-for="(result, index) in resultData"  
128 - :key="index"  
129 - class="result-item"  
130 - :class="result.type"  
131 - >  
132 - <div class="result-header-item">  
133 - <div class="result-title">  
134 - <i :class="getResultIcon(result.type)"></i>  
135 - {{ result.title }}  
136 - </div>  
137 - <div class="result-time">{{ result.time }}</div> 135 + <!-- 右侧:结果显示区域 -->
  136 + <div class="right-panel">
  137 + <el-card class="box-card result-card">
  138 + <div slot="header" class="clearfix">
  139 + <span><i class="el-icon-document"></i> 统计结果</span>
  140 + <div class="header-actions">
  141 + <el-button type="text" @click="exportResults" :disabled="results.length === 0">
  142 + <i class="el-icon-download"></i> 导出
  143 + </el-button>
  144 + <el-button type="text" @click="clearResults">
  145 + <i class="el-icon-delete"></i> 清空
  146 + </el-button>
138 </div> 147 </div>
139 - <div class="result-body">  
140 - <div class="result-status" :class="result.success ? 'success' : 'error'">  
141 - <i :class="result.success ? 'el-icon-success' : 'el-icon-error'"></i>  
142 - {{ result.success ? '统计成功' : '统计失败' }}  
143 - </div>  
144 - <div class="result-details">  
145 - <pre class="result-json">{{ formatResult(result.data) }}</pre> 148 + </div>
  149 + <div class="result-list" ref="resultList">
  150 + <div v-if="results.length === 0" class="no-results">
  151 + <i class="el-icon-document-copy"></i>
  152 + <p>暂无统计结果</p>
  153 + <p class="no-results-desc">请选择月份并点击上方按钮进行统计</p>
  154 + </div>
  155 + <div v-for="(result, index) in results" :key="index" :class="['result-item', result.success ? 'success-item' : 'error-item']">
  156 + <div class="result-header">
  157 + <i :class="[result.success ? 'el-icon-success' : 'el-icon-error', 'result-icon']"></i>
  158 + <span class="result-title">{{ result.title }}</span>
  159 + <span class="result-time">{{ result.time }}</span>
146 </div> 160 </div>
  161 + <pre class="result-content">{{ formatJson(result.data) }}</pre>
147 </div> 162 </div>
148 </div> 163 </div>
149 - </div>  
150 -  
151 - <div v-else class="empty-result">  
152 - <i class="el-icon-document-copy"></i>  
153 - <p>暂无统计结果,请点击上方按钮开始统计</p>  
154 - </div> 164 + </el-card>
155 </div> 165 </div>
156 </div> 166 </div>
157 </div> 167 </div>
158 </template> 168 </template>
159 169
160 <script> 170 <script>
161 -import {  
162 - saveGoldTriangleStatistics,  
163 - savePersonalPerformanceStatistics,  
164 - saveTechPerformanceStatistics 171 +import {
  172 + saveGoldTriangleStatistics,
  173 + savePersonalPerformanceStatistics,
  174 + saveTechPerformanceStatistics
165 } from '@/api/extend/salaryCalculation' 175 } from '@/api/extend/salaryCalculation'
166 176
167 export default { 177 export default {
168 name: 'SalaryCalculation', 178 name: 'SalaryCalculation',
169 data() { 179 data() {
170 return { 180 return {
171 - // 统计月份,默认为当前月份  
172 statisticsMonth: this.getCurrentMonth(), 181 statisticsMonth: this.getCurrentMonth(),
173 - // 加载状态  
174 - loading: false,  
175 loadingStates: { 182 loadingStates: {
176 goldTriangle: false, 183 goldTriangle: false,
177 personalPerformance: false, 184 personalPerformance: false,
178 techPerformance: false 185 techPerformance: false
179 }, 186 },
180 - // 结果数据  
181 - resultData: [] 187 + results: [], // 存储统计结果,最多保留10条
  188 + isCalculating: false, // 一键计算状态
  189 + calculationProgress: [] // 计算进度
182 } 190 }
183 }, 191 },
184 methods: { 192 methods: {
185 - // 获取当前月份 193 + resetMonth() {
  194 + this.statisticsMonth = this.getCurrentMonth()
  195 + this.$message.info('已重置为当前月份')
  196 + },
186 getCurrentMonth() { 197 getCurrentMonth() {
187 const now = new Date() 198 const now = new Date()
188 const year = now.getFullYear() 199 const year = now.getFullYear()
189 const month = String(now.getMonth() + 1).padStart(2, '0') 200 const month = String(now.getMonth() + 1).padStart(2, '0')
190 return `${year}${month}` 201 return `${year}${month}`
191 }, 202 },
  203 + clearResults() {
  204 + this.results = []
  205 + this.calculationProgress = []
  206 + this.$message.info('已清空所有统计结果')
  207 + },
  208 + addResult(newResult) {
  209 + this.results.unshift(newResult) // 添加到数组开头
  210 + if (this.results.length > 10) {
  211 + this.results.pop() // 保持最多10条记录
  212 + }
  213 + this.$nextTick(() => {
  214 + this.$refs.resultList.scrollTop = 0 // 滚动到顶部显示最新结果
  215 + })
  216 + },
  217 + formatJson(json) {
  218 + if (typeof json !== 'string') {
  219 + json = JSON.stringify(json, null, 2)
  220 + }
  221 + return json
  222 + },
  223 + exportResults() {
  224 + if (this.results.length === 0) {
  225 + this.$message.warning('没有可导出的结果')
  226 + return
  227 + }
  228 + // 这里可以实现导出功能
  229 + this.$message.success('导出功能开发中...')
  230 + },
192 231
193 - // 刷新月份  
194 - refreshMonth() {  
195 - this.statisticsMonth = this.getCurrentMonth()  
196 - this.$message.success('月份已刷新为当前月份') 232 + // 一键计算工资
  233 + async handleCalculateSalary() {
  234 + if (!this.statisticsMonth) {
  235 + this.$message.error('请先选择统计月份')
  236 + return
  237 + }
  238 +
  239 + this.isCalculating = true
  240 + this.calculationProgress = []
  241 + this.results = [] // 清空之前的结果
  242 +
  243 + // 定义统计步骤
  244 + const steps = [
  245 + {
  246 + name: '金三角开卡业绩统计',
  247 + method: 'handleGoldTriangleStatistics',
  248 + type: 'gold-triangle'
  249 + },
  250 + {
  251 + name: '健康师个人开单业绩统计',
  252 + method: 'handlePersonalPerformanceStatistics',
  253 + type: 'personal-performance'
  254 + },
  255 + {
  256 + name: '科技部开单业绩统计',
  257 + method: 'handleTechPerformanceStatistics',
  258 + type: 'tech-performance'
  259 + }
  260 + ]
  261 +
  262 + try {
  263 + // 按顺序执行每个统计步骤
  264 + for (let i = 0; i < steps.length; i++) {
  265 + const step = steps[i]
  266 +
  267 + // 更新进度状态
  268 + this.calculationProgress.push({
  269 + name: step.name,
  270 + status: 'running'
  271 + })
  272 +
  273 + try {
  274 + // 调用对应的统计方法
  275 + await this[step.method]()
  276 +
  277 + // 标记为完成
  278 + this.calculationProgress[i].status = 'completed'
  279 +
  280 + // 等待一秒再执行下一步
  281 + if (i < steps.length - 1) {
  282 + await new Promise(resolve => setTimeout(resolve, 1000))
  283 + }
  284 + } catch (error) {
  285 + // 标记为失败
  286 + this.calculationProgress[i].status = 'failed'
  287 + this.$message.error(`${step.name}失败: ${error.message}`)
  288 +
  289 + // 如果某个步骤失败,询问是否继续
  290 + const continueCalc = await this.$confirm(
  291 + `${step.name}执行失败,是否继续执行后续统计?`,
  292 + '统计失败',
  293 + {
  294 + confirmButtonText: '继续',
  295 + cancelButtonText: '停止',
  296 + type: 'warning'
  297 + }
  298 + ).catch(() => false)
  299 +
  300 + if (!continueCalc) {
  301 + break
  302 + }
  303 + }
  304 + }
  305 +
  306 + // 检查是否所有步骤都完成
  307 + const completedCount = this.calculationProgress.filter(p => p.status === 'completed').length
  308 + if (completedCount === steps.length) {
  309 + this.$message.success('工资计算完成!所有统计步骤都已成功执行')
  310 + } else {
  311 + this.$message.warning(`工资计算部分完成,成功执行了 ${completedCount}/${steps.length} 个统计步骤`)
  312 + }
  313 +
  314 + } catch (error) {
  315 + this.$message.error('工资计算过程中发生错误')
  316 + } finally {
  317 + this.isCalculating = false
  318 + }
  319 + },
  320 +
  321 + // 获取进度图标
  322 + getProgressIcon(status) {
  323 + switch (status) {
  324 + case 'running':
  325 + return 'el-icon-loading'
  326 + case 'completed':
  327 + return 'el-icon-success'
  328 + case 'failed':
  329 + return 'el-icon-error'
  330 + default:
  331 + return 'el-icon-time'
  332 + }
  333 + },
  334 +
  335 + // 获取进度颜色
  336 + getProgressColor(status) {
  337 + switch (status) {
  338 + case 'running':
  339 + return '#409EFF'
  340 + case 'completed':
  341 + return '#67C23A'
  342 + case 'failed':
  343 + return '#F56C6C'
  344 + default:
  345 + return '#909399'
  346 + }
197 }, 347 },
198 348
199 // 金三角开卡业绩统计 349 // 金三角开卡业绩统计
@@ -218,6 +368,7 @@ export default { @@ -218,6 +368,7 @@ export default {
218 time: new Date().toLocaleString() 368 time: new Date().toLocaleString()
219 }) 369 })
220 this.$message.error('金三角开卡业绩统计失败') 370 this.$message.error('金三角开卡业绩统计失败')
  371 + throw error // 重新抛出错误,用于一键计算时的错误处理
221 } finally { 372 } finally {
222 this.loadingStates.goldTriangle = false 373 this.loadingStates.goldTriangle = false
223 } 374 }
@@ -245,6 +396,7 @@ export default { @@ -245,6 +396,7 @@ export default {
245 time: new Date().toLocaleString() 396 time: new Date().toLocaleString()
246 }) 397 })
247 this.$message.error('健康师个人开单业绩统计失败') 398 this.$message.error('健康师个人开单业绩统计失败')
  399 + throw error
248 } finally { 400 } finally {
249 this.loadingStates.personalPerformance = false 401 this.loadingStates.personalPerformance = false
250 } 402 }
@@ -272,334 +424,363 @@ export default { @@ -272,334 +424,363 @@ export default {
272 time: new Date().toLocaleString() 424 time: new Date().toLocaleString()
273 }) 425 })
274 this.$message.error('科技部开单业绩统计失败') 426 this.$message.error('科技部开单业绩统计失败')
  427 + throw error
275 } finally { 428 } finally {
276 this.loadingStates.techPerformance = false 429 this.loadingStates.techPerformance = false
277 } 430 }
278 - },  
279 -  
280 - // 添加结果  
281 - addResult(result) {  
282 - this.resultData.unshift(result)  
283 - // 限制结果数量,最多保留10条  
284 - if (this.resultData.length > 10) {  
285 - this.resultData = this.resultData.slice(0, 10)  
286 - }  
287 - },  
288 -  
289 - // 清空结果  
290 - clearResults() {  
291 - this.resultData = []  
292 - this.$message.success('结果已清空')  
293 - },  
294 -  
295 - // 获取结果图标  
296 - getResultIcon(type) {  
297 - const iconMap = {  
298 - 'gold-triangle': 'el-icon-trophy',  
299 - 'personal-performance': 'el-icon-user',  
300 - 'tech-performance': 'el-icon-cpu'  
301 - }  
302 - return iconMap[type] || 'el-icon-document'  
303 - },  
304 -  
305 - // 格式化结果显示  
306 - formatResult(data) {  
307 - if (typeof data === 'string') {  
308 - return data  
309 - }  
310 - return JSON.stringify(data, null, 2)  
311 } 431 }
312 } 432 }
313 } 433 }
314 </script> 434 </script>
315 435
316 <style lang="scss" scoped> 436 <style lang="scss" scoped>
317 -.page-header {  
318 - text-align: center;  
319 - margin-bottom: 30px;  
320 - padding: 20px 0;  
321 - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);  
322 - border-radius: 12px;  
323 - color: white;  
324 -  
325 - .page-title {  
326 - margin: 0 0 10px 0;  
327 - font-size: 28px;  
328 - font-weight: 600;  
329 -  
330 - i {  
331 - margin-right: 10px;  
332 - font-size: 32px; 437 +.salary-calculation-container {
  438 + padding: 20px;
  439 + background-color: #f0f2f5;
  440 + min-height: calc(100vh - 50px);
  441 + font-family: 'Arial', sans-serif;
  442 +
  443 + .header-card {
  444 + background: linear-gradient(135deg, #409EFF, #79BBFF);
  445 + color: #fff;
  446 + padding: 30px;
  447 + border-radius: 15px;
  448 + margin-bottom: 20px;
  449 + text-align: center;
  450 + box-shadow: 0 8px 20px rgba(0, 123, 255, 0.2);
  451 +
  452 + .title {
  453 + font-size: 2.5em;
  454 + margin-bottom: 10px;
  455 + font-weight: bold;
333 } 456 }
334 - }  
335 -  
336 - .page-description {  
337 - margin: 0;  
338 - font-size: 16px;  
339 - opacity: 0.9;  
340 - }  
341 -}  
342 457
343 -.statistics-form {  
344 - background: white;  
345 - padding: 20px;  
346 - border-radius: 12px;  
347 - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);  
348 - margin-bottom: 20px;  
349 -  
350 - .month-picker {  
351 - width: 200px; 458 + .subtitle {
  459 + font-size: 1.1em;
  460 + opacity: 0.9;
  461 + }
352 } 462 }
353 -}  
354 463
355 -.statistics-buttons {  
356 - margin-bottom: 30px;  
357 -  
358 - .button-grid {  
359 - display: grid;  
360 - grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));  
361 - gap: 20px;  
362 - }  
363 -} 464 + .box-card {
  465 + border-radius: 10px;
  466 + margin-bottom: 20px;
  467 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
364 468
365 -.statistics-card {  
366 - background: white;  
367 - border-radius: 12px;  
368 - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);  
369 - transition: all 0.3s ease;  
370 - overflow: hidden;  
371 -  
372 - &:hover {  
373 - transform: translateY(-5px);  
374 - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);  
375 - }  
376 -  
377 - .card-header {  
378 - padding: 20px 20px 15px;  
379 - border-bottom: 1px solid #f0f0f0;  
380 -  
381 - i {  
382 - font-size: 24px;  
383 - margin-right: 10px; 469 + ::v-deep .el-card__header {
  470 + background-color: #fff;
  471 + border-bottom: 1px solid #ebeef5;
  472 + padding: 18px 20px;
  473 + font-size: 1.1em;
  474 + font-weight: bold;
  475 + color: #303133;
384 } 476 }
385 -  
386 - h3 {  
387 - margin: 0;  
388 - font-size: 18px;  
389 - font-weight: 600;  
390 - display: inline-block; 477 +
  478 + ::v-deep .el-card__body {
  479 + padding: 20px;
391 } 480 }
392 } 481 }
393 -  
394 - .card-content {  
395 - padding: 20px;  
396 -  
397 - .card-description {  
398 - color: #666;  
399 - margin: 0 0 20px 0;  
400 - line-height: 1.6; 482 +
  483 + .month-selector-card {
  484 + .month-picker-wrapper {
  485 + display: flex;
  486 + justify-content: center;
  487 + align-items: center;
401 } 488 }
402 -  
403 - .statistics-btn { 489 + .month-picker {
404 width: 100%; 490 width: 100%;
405 - height: 50px;  
406 - font-size: 16px;  
407 - font-weight: 600;  
408 - border-radius: 8px;  
409 -  
410 - i {  
411 - margin-right: 8px;  
412 - font-size: 18px;  
413 - } 491 + max-width: 300px;
414 } 492 }
415 } 493 }
416 -  
417 - // 不同卡片的主题色  
418 - &.gold-triangle {  
419 - .card-header i {  
420 - color: #409EFF; 494 +
  495 + // 主要内容区域 - 左右布局
  496 + .main-content {
  497 + display: flex;
  498 + gap: 20px;
  499 + min-height: 600px;
  500 +
  501 + .left-panel {
  502 + flex: 1;
  503 + min-width: 0; // 防止flex子项溢出
421 } 504 }
422 - }  
423 -  
424 - &.personal-performance {  
425 - .card-header i {  
426 - color: #67C23A; 505 +
  506 + .right-panel {
  507 + flex: 1;
  508 + min-width: 0;
427 } 509 }
428 } 510 }
429 -  
430 - &.tech-performance {  
431 - .card-header i {  
432 - color: #E6A23C; 511 +
  512 + // 一键计算工资卡片
  513 + .calculate-card {
  514 + margin-bottom: 20px;
  515 + background: linear-gradient(135deg, #67C23A, #85CE61);
  516 + color: white;
  517 +
  518 + ::v-deep .el-card__header {
  519 + background: rgba(255, 255, 255, 0.1);
  520 + color: white;
  521 + border-bottom: 1px solid rgba(255, 255, 255, 0.2);
433 } 522 }
434 - }  
435 -}  
436 523
437 -.result-section {  
438 - background: white;  
439 - border-radius: 12px;  
440 - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);  
441 - overflow: hidden;  
442 -  
443 - .result-header {  
444 - display: flex;  
445 - justify-content: space-between;  
446 - align-items: center;  
447 - padding: 20px;  
448 - background: #f8f9fa;  
449 - border-bottom: 1px solid #e9ecef;  
450 -  
451 - h3 {  
452 - margin: 0;  
453 - font-size: 18px;  
454 - font-weight: 600;  
455 - color: #333;  
456 -  
457 - i {  
458 - margin-right: 8px;  
459 - color: #409EFF; 524 + .calculate-content {
  525 + text-align: center;
  526 +
  527 + .calculate-desc {
  528 + color: rgba(255, 255, 255, 0.9);
  529 + margin-bottom: 20px;
  530 + font-size: 1.1em;
  531 + }
  532 +
  533 + .calculate-button {
  534 + width: 100%;
  535 + height: 60px;
  536 + font-size: 1.3em;
  537 + font-weight: bold;
  538 + border-radius: 10px;
  539 + background: rgba(255, 255, 255, 0.2);
  540 + border: 2px solid rgba(255, 255, 255, 0.3);
  541 + color: white;
  542 +
  543 + &:hover:not(:disabled) {
  544 + background: rgba(255, 255, 255, 0.3);
  545 + border-color: rgba(255, 255, 255, 0.5);
  546 + }
  547 +
  548 + &:disabled {
  549 + opacity: 0.6;
  550 + }
  551 + }
  552 +
  553 + .progress-info {
  554 + margin-top: 20px;
  555 + text-align: left;
  556 +
  557 + .progress-title {
  558 + font-weight: bold;
  559 + margin-bottom: 10px;
  560 + color: rgba(255, 255, 255, 0.9);
  561 + }
  562 +
  563 + .progress-step {
  564 + display: flex;
  565 + align-items: center;
  566 + margin-bottom: 8px;
  567 + font-size: 0.95em;
  568 +
  569 + i {
  570 + margin-right: 8px;
  571 + font-size: 1.1em;
  572 + }
  573 +
  574 + span {
  575 + color: rgba(255, 255, 255, 0.9);
  576 +
  577 + &.completed {
  578 + color: #fff;
  579 + font-weight: bold;
  580 + }
  581 +
  582 + &.failed {
  583 + color: #ffeb3b;
  584 + font-weight: bold;
  585 + }
  586 + }
  587 + }
460 } 588 }
461 } 589 }
462 } 590 }
463 -  
464 - .result-content {  
465 - max-height: 600px;  
466 - overflow-y: auto;  
467 - }  
468 -  
469 - .result-item {  
470 - border-bottom: 1px solid #f0f0f0;  
471 -  
472 - &:last-child {  
473 - border-bottom: none; 591 +
  592 + // 统计操作卡片
  593 + .statistics-card {
  594 + .statistics-grid {
  595 + display: grid;
  596 + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  597 + gap: 15px;
  598 +
  599 + .stat-item {
  600 + background: #fff;
  601 + border-radius: 8px;
  602 + padding: 15px;
  603 + text-align: center;
  604 + transition: all 0.3s ease;
  605 + border: 2px solid transparent;
  606 +
  607 + &:hover {
  608 + transform: translateY(-2px);
  609 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  610 + }
  611 +
  612 + .stat-header {
  613 + margin-bottom: 15px;
  614 +
  615 + i {
  616 + font-size: 1.5em;
  617 + margin-bottom: 8px;
  618 + display: block;
  619 + }
  620 +
  621 + span {
  622 + font-weight: bold;
  623 + font-size: 0.9em;
  624 + color: #333;
  625 + }
  626 + }
  627 +
  628 + .stat-button {
  629 + width: 100%;
  630 + height: 40px;
  631 + font-size: 0.9em;
  632 + border-radius: 6px;
  633 + }
  634 +
  635 + // 不同主题色
  636 + &.blue-item {
  637 + border-color: #409EFF;
  638 + .stat-header i { color: #409EFF; }
  639 + }
  640 +
  641 + &.green-item {
  642 + border-color: #67C23A;
  643 + .stat-header i { color: #67C23A; }
  644 + }
  645 +
  646 + &.orange-item {
  647 + border-color: #E6A23C;
  648 + .stat-header i { color: #E6A23C; }
  649 + }
  650 +
  651 + &.placeholder-item {
  652 + border-color: #C0C4CC;
  653 + background: #f5f7fa;
  654 + .stat-header i { color: #C0C4CC; }
  655 + }
  656 + }
474 } 657 }
475 -  
476 - .result-header-item { 658 + }
  659 +
  660 + // 结果显示卡片
  661 + .result-card {
  662 + height: 100%;
  663 +
  664 + .header-actions {
477 display: flex; 665 display: flex;
478 - justify-content: space-between;  
479 - align-items: center;  
480 - padding: 15px 20px;  
481 - background: #fafafa;  
482 -  
483 - .result-title {  
484 - font-weight: 600;  
485 - color: #333;  
486 - 666 + gap: 10px;
  667 + }
  668 +
  669 + .result-list {
  670 + max-height: 500px;
  671 + overflow-y: auto;
  672 + padding-right: 10px;
  673 +
  674 + .no-results {
  675 + text-align: center;
  676 + color: #909399;
  677 + padding: 40px 20px;
  678 +
487 i { 679 i {
488 - margin-right: 8px; 680 + font-size: 3em;
  681 + margin-bottom: 15px;
  682 + color: #ddd;
  683 + }
  684 +
  685 + p {
  686 + margin: 5px 0;
  687 + font-size: 1.1em;
  688 + }
  689 +
  690 + .no-results-desc {
  691 + font-size: 0.9em;
  692 + color: #bbb;
489 } 693 }
490 } 694 }
491 -  
492 - .result-time {  
493 - color: #999;  
494 - font-size: 14px;  
495 - }  
496 - }  
497 -  
498 - .result-body {  
499 - padding: 20px;  
500 -  
501 - .result-status {  
502 - display: flex;  
503 - align-items: center; 695 +
  696 + .result-item {
  697 + border-left: 5px solid;
  698 + padding: 15px;
504 margin-bottom: 15px; 699 margin-bottom: 15px;
505 - font-weight: 600;  
506 -  
507 - i {  
508 - margin-right: 8px;  
509 - font-size: 16px; 700 + border-radius: 8px;
  701 + background-color: #f9f9f9;
  702 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  703 +
  704 + &.success-item {
  705 + border-color: #67C23A;
510 } 706 }
511 -  
512 - &.success {  
513 - color: #67C23A; 707 + &.error-item {
  708 + border-color: #F56C6C;
514 } 709 }
515 -  
516 - &.error {  
517 - color: #F56C6C; 710 +
  711 + .result-header {
  712 + display: flex;
  713 + align-items: center;
  714 + margin-bottom: 10px;
  715 + font-weight: bold;
  716 + color: #303133;
  717 +
  718 + .result-icon {
  719 + font-size: 1.2em;
  720 + margin-right: 8px;
  721 + &.el-icon-success {
  722 + color: #67C23A;
  723 + }
  724 + &.el-icon-error {
  725 + color: #F56C6C;
  726 + }
  727 + }
  728 +
  729 + .result-title {
  730 + font-size: 1.1em;
  731 + flex-grow: 1;
  732 + }
  733 +
  734 + .result-time {
  735 + font-size: 0.9em;
  736 + color: #909399;
  737 + }
518 } 738 }
519 - }  
520 -  
521 - .result-details {  
522 - .result-json {  
523 - background: #f8f9fa;  
524 - border: 1px solid #e9ecef;  
525 - border-radius: 6px;  
526 - padding: 15px;  
527 - margin: 0;  
528 - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;  
529 - font-size: 13px;  
530 - line-height: 1.5;  
531 - color: #333; 739 +
  740 + .result-content {
  741 + background-color: #eee;
  742 + padding: 10px;
  743 + border-radius: 5px;
532 white-space: pre-wrap; 744 white-space: pre-wrap;
533 word-break: break-all; 745 word-break: break-all;
534 - max-height: 300px;  
535 - overflow-y: auto; 746 + font-family: 'Consolas', 'Monaco', monospace;
  747 + font-size: 0.9em;
  748 + color: #333;
536 } 749 }
537 } 750 }
538 } 751 }
539 -  
540 - // 不同类型的结果项样式  
541 - &.gold-triangle {  
542 - .result-header-item {  
543 - border-left: 4px solid #409EFF;  
544 - }  
545 - }  
546 -  
547 - &.personal-performance {  
548 - .result-header-item {  
549 - border-left: 4px solid #67C23A;  
550 - }  
551 - }  
552 -  
553 - &.tech-performance {  
554 - .result-header-item {  
555 - border-left: 4px solid #E6A23C;  
556 - }  
557 - }  
558 } 752 }
559 -  
560 - .empty-result {  
561 - text-align: center;  
562 - padding: 60px 20px;  
563 - color: #999;  
564 -  
565 - i {  
566 - font-size: 48px;  
567 - margin-bottom: 20px;  
568 - color: #ddd;  
569 - }  
570 -  
571 - p {  
572 - margin: 0;  
573 - font-size: 16px;  
574 - } 753 +}
  754 +
  755 +// 响应式调整
  756 +@media (max-width: 1200px) {
  757 + .main-content {
  758 + flex-direction: column;
575 } 759 }
576 } 760 }
577 761
578 -// 响应式设计  
579 @media (max-width: 768px) { 762 @media (max-width: 768px) {
580 - .button-grid {  
581 - grid-template-columns: 1fr; 763 + .salary-calculation-container {
  764 + padding: 10px;
582 } 765 }
583 766
584 - .page-header {  
585 - .page-title {  
586 - font-size: 24px; 767 + .header-card {
  768 + padding: 20px;
  769 + .title {
  770 + font-size: 1.8em;
587 } 771 }
588 -  
589 - .page-description {  
590 - font-size: 14px; 772 + .subtitle {
  773 + font-size: 0.9em;
591 } 774 }
592 } 775 }
593 776
594 - .statistics-card {  
595 - .card-header h3 {  
596 - font-size: 16px;  
597 - }  
598 -  
599 - .card-content .statistics-btn {  
600 - height: 45px;  
601 - font-size: 14px;  
602 - } 777 + .statistics-grid {
  778 + grid-template-columns: 1fr !important;
  779 + }
  780 +
  781 + .calculate-button {
  782 + height: 50px !important;
  783 + font-size: 1.1em !important;
603 } 784 }
604 } 785 }
605 -</style> 786 -</style>
  787 +</style>
606 \ No newline at end of file 788 \ No newline at end of file
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/SalaryStatisticsInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqStatistics
  4 +{
  5 + /// <summary>
  6 + /// 工资统计输入参数
  7 + /// </summary>
  8 + public class SalaryStatisticsInput
  9 + {
  10 + /// <summary>
  11 + /// 统计月份(YYYYMM格式)
  12 + /// </summary>
  13 + [Required(ErrorMessage = "统计月份不能为空")]
  14 + [StringLength(6, MinimumLength = 6, ErrorMessage = "统计月份格式错误,请使用YYYYMM格式")]
  15 + public string StatisticsMonth { get; set; }
  16 + }
  17 +}
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
@@ -1211,14 +1211,16 @@ namespace NCC.Extend.LqStatistics @@ -1211,14 +1211,16 @@ namespace NCC.Extend.LqStatistics
1211 /// <response code="200">成功保存统计数据</response> 1211 /// <response code="200">成功保存统计数据</response>
1212 /// <response code="400">参数错误</response> 1212 /// <response code="400">参数错误</response>
1213 /// <response code="500">服务器内部错误</response> 1213 /// <response code="500">服务器内部错误</response>
1214 - [HttpPost("save-gold-triangle-statistics")]  
1215 - public async Task<dynamic> SaveGoldTriangleStatistics(string statisticsMonth) 1214 + [HttpPost("save-gold-triangle-stats")]
  1215 + public async Task<dynamic> SaveGoldTriangleStatistics([FromBody] SalaryStatisticsInput input)
1216 { 1216 {
1217 - if (string.IsNullOrEmpty(statisticsMonth) || statisticsMonth.Length != 6) 1217 + if (input == null || string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
1218 { 1218 {
1219 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式"); 1219 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式");
1220 } 1220 }
1221 1221
  1222 + var statisticsMonth = input.StatisticsMonth;
  1223 +
1222 try 1224 try
1223 { 1225 {
1224 // 使用数据库聚合方式,直接在数据库中完成所有统计计算 1226 // 使用数据库聚合方式,直接在数据库中完成所有统计计算
@@ -1389,14 +1391,16 @@ namespace NCC.Extend.LqStatistics @@ -1389,14 +1391,16 @@ namespace NCC.Extend.LqStatistics
1389 /// <response code="200">成功保存统计数据</response> 1391 /// <response code="200">成功保存统计数据</response>
1390 /// <response code="400">参数错误</response> 1392 /// <response code="400">参数错误</response>
1391 /// <response code="500">服务器内部错误</response> 1393 /// <response code="500">服务器内部错误</response>
1392 - [HttpPost("save-personal-performance-statistics")]  
1393 - public async Task<dynamic> SavePersonalPerformanceStatistics(string statisticsMonth) 1394 + [HttpPost("save-personal-performance-stats")]
  1395 + public async Task<dynamic> SavePersonalPerformanceStatistics([FromBody] SalaryStatisticsInput input)
1394 { 1396 {
1395 - if (string.IsNullOrEmpty(statisticsMonth) || statisticsMonth.Length != 6) 1397 + if (input == null || string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
1396 { 1398 {
1397 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式"); 1399 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式");
1398 } 1400 }
1399 1401
  1402 + var statisticsMonth = input.StatisticsMonth;
  1403 +
1400 try 1404 try
1401 { 1405 {
1402 // 使用数据库聚合方式,直接在数据库中完成所有统计计算 1406 // 使用数据库聚合方式,直接在数据库中完成所有统计计算
@@ -1662,14 +1666,16 @@ namespace NCC.Extend.LqStatistics @@ -1662,14 +1666,16 @@ namespace NCC.Extend.LqStatistics
1662 /// <response code="200">成功保存统计数据</response> 1666 /// <response code="200">成功保存统计数据</response>
1663 /// <response code="400">参数错误</response> 1667 /// <response code="400">参数错误</response>
1664 /// <response code="500">服务器内部错误</response> 1668 /// <response code="500">服务器内部错误</response>
1665 - [HttpPost("save-tech-performance-statistics")]  
1666 - public async Task<dynamic> SaveTechPerformanceStatistics(string statisticsMonth) 1669 + [HttpPost("save-tech-performance-stats")]
  1670 + public async Task<dynamic> SaveTechPerformanceStatistics([FromBody] SalaryStatisticsInput input)
1667 { 1671 {
1668 - if (string.IsNullOrEmpty(statisticsMonth) || statisticsMonth.Length != 6) 1672 + if (input == null || string.IsNullOrEmpty(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
1669 { 1673 {
1670 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式"); 1674 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式");
1671 } 1675 }
1672 1676
  1677 + var statisticsMonth = input.StatisticsMonth;
  1678 +
1673 try 1679 try
1674 { 1680 {
1675 // 使用数据库聚合方式,只统计开单业绩(开卡流水) 1681 // 使用数据库聚合方式,只统计开单业绩(开卡流水)