Commit caa83dede2abb7b720f603e929f237ca59e47d3e

Authored by “wangming”
1 parent 070faa12

111

antis-ncc-admin/.env.development
1 1 # 开发
2 2  
3 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 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 3 // 保存金三角开卡业绩统计数据
4 4 export function saveGoldTriangleStatistics(statisticsMonth) {
5 5 return request({
6   - url: '/api/Extend/LqStatistics/save-gold-triangle-statistics',
  6 + url: '/api/Extend/LqStatistics/save-gold-triangle-stats',
7 7 method: 'POST',
8 8 data: { statisticsMonth }
9 9 })
... ... @@ -12,7 +12,7 @@ export function saveGoldTriangleStatistics(statisticsMonth) {
12 12 // 保存健康师个人开单业绩统计数据
13 13 export function savePersonalPerformanceStatistics(statisticsMonth) {
14 14 return request({
15   - url: '/api/Extend/LqStatistics/save-personal-performance-statistics',
  15 + url: '/api/Extend/LqStatistics/save-personal-performance-stats',
16 16 method: 'POST',
17 17 data: { statisticsMonth }
18 18 })
... ... @@ -21,7 +21,7 @@ export function savePersonalPerformanceStatistics(statisticsMonth) {
21 21 // 保存科技部开单业绩统计数据
22 22 export function saveTechPerformanceStatistics(statisticsMonth) {
23 23 return request({
24   - url: '/api/Extend/LqStatistics/save-tech-performance-statistics',
  24 + url: '/api/Extend/LqStatistics/save-tech-performance-stats',
25 25 method: 'POST',
26 26 data: { statisticsMonth }
27 27 })
... ...
antis-ncc-admin/src/utils/define.js
1 1 // 开发环境接口配置
2 2 // JAVA Boot版本对应后端接口地址
3 3 // JAVA Cloud对应网关地址
4   -const APIURl = 'http://localhost:8061'
  4 +const APIURl = 'http://localhost:2011'
5 5  
6 6 module.exports = {
7 7 APIURl: APIURl,
... ...
antis-ncc-admin/src/views/salaryCalculation/index.vue
1 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 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 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 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 76 :loading="loadingStates.goldTriangle"
55 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 82 </el-button>
61 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 92 :loading="loadingStates.personalPerformance"
76 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 98 </el-button>
82 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 108 :loading="loadingStates.techPerformance"
97 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 129 </el-button>
103 130 </div>
104 131 </div>
105   - </div>
  132 + </el-card>
106 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 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 160 </div>
  161 + <pre class="result-content">{{ formatJson(result.data) }}</pre>
147 162 </div>
148 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 165 </div>
156 166 </div>
157 167 </div>
158 168 </template>
159 169  
160 170 <script>
161   -import {
162   - saveGoldTriangleStatistics,
163   - savePersonalPerformanceStatistics,
164   - saveTechPerformanceStatistics
  171 +import {
  172 + saveGoldTriangleStatistics,
  173 + savePersonalPerformanceStatistics,
  174 + saveTechPerformanceStatistics
165 175 } from '@/api/extend/salaryCalculation'
166 176  
167 177 export default {
168 178 name: 'SalaryCalculation',
169 179 data() {
170 180 return {
171   - // 统计月份,默认为当前月份
172 181 statisticsMonth: this.getCurrentMonth(),
173   - // 加载状态
174   - loading: false,
175 182 loadingStates: {
176 183 goldTriangle: false,
177 184 personalPerformance: false,
178 185 techPerformance: false
179 186 },
180   - // 结果数据
181   - resultData: []
  187 + results: [], // 存储统计结果,最多保留10条
  188 + isCalculating: false, // 一键计算状态
  189 + calculationProgress: [] // 计算进度
182 190 }
183 191 },
184 192 methods: {
185   - // 获取当前月份
  193 + resetMonth() {
  194 + this.statisticsMonth = this.getCurrentMonth()
  195 + this.$message.info('已重置为当前月份')
  196 + },
186 197 getCurrentMonth() {
187 198 const now = new Date()
188 199 const year = now.getFullYear()
189 200 const month = String(now.getMonth() + 1).padStart(2, '0')
190 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 368 time: new Date().toLocaleString()
219 369 })
220 370 this.$message.error('金三角开卡业绩统计失败')
  371 + throw error // 重新抛出错误,用于一键计算时的错误处理
221 372 } finally {
222 373 this.loadingStates.goldTriangle = false
223 374 }
... ... @@ -245,6 +396,7 @@ export default {
245 396 time: new Date().toLocaleString()
246 397 })
247 398 this.$message.error('健康师个人开单业绩统计失败')
  399 + throw error
248 400 } finally {
249 401 this.loadingStates.personalPerformance = false
250 402 }
... ... @@ -272,334 +424,363 @@ export default {
272 424 time: new Date().toLocaleString()
273 425 })
274 426 this.$message.error('科技部开单业绩统计失败')
  427 + throw error
275 428 } finally {
276 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 434 </script>
315 435  
316 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 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 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 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 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 744 white-space: pre-wrap;
533 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 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 786 -</style>
  787 +</style>
606 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 1211 /// <response code="200">成功保存统计数据</response>
1212 1212 /// <response code="400">参数错误</response>
1213 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 1219 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式");
1220 1220 }
1221 1221  
  1222 + var statisticsMonth = input.StatisticsMonth;
  1223 +
1222 1224 try
1223 1225 {
1224 1226 // 使用数据库聚合方式,直接在数据库中完成所有统计计算
... ... @@ -1389,14 +1391,16 @@ namespace NCC.Extend.LqStatistics
1389 1391 /// <response code="200">成功保存统计数据</response>
1390 1392 /// <response code="400">参数错误</response>
1391 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 1399 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式");
1398 1400 }
1399 1401  
  1402 + var statisticsMonth = input.StatisticsMonth;
  1403 +
1400 1404 try
1401 1405 {
1402 1406 // 使用数据库聚合方式,直接在数据库中完成所有统计计算
... ... @@ -1662,14 +1666,16 @@ namespace NCC.Extend.LqStatistics
1662 1666 /// <response code="200">成功保存统计数据</response>
1663 1667 /// <response code="400">参数错误</response>
1664 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 1674 throw NCCException.Oh("统计月份格式错误,请使用YYYYMM格式");
1671 1675 }
1672 1676  
  1677 + var statisticsMonth = input.StatisticsMonth;
  1678 +
1673 1679 try
1674 1680 {
1675 1681 // 使用数据库聚合方式,只统计开单业绩(开卡流水)
... ...