Toggle navigation
Toggle navigation
This project
Loading...
Sign in
zhouhui.jiang
/
test_cursor
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
zhouhui.jiang
2025-09-29 17:50:25 +0800
Browse Files
Options
Browse Files
Download
Plain Diff
Commit
4315aa69d3fc6519dbef6cf753ada29a32d5a1ea
4315aa69
2 parents
9d5c9aea
6ce0d515
Merge branch 'master' of
http://gitlab.erry.com/zhouhui.jiang/test_cursor
Show whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
2679 additions
and
302 deletions
API接口文档.md
backend/src/main/java/com/apple/erp/config/SecurityConfig.java
backend/src/main/java/com/apple/erp/controller/AuthController.java
backend/src/main/java/com/apple/erp/controller/DashboardController.java
backend/src/main/java/com/apple/erp/controller/ExceptionWorkorderController.java
backend/src/main/java/com/apple/erp/dto/response/DashboardStatsRes.java
backend/src/main/java/com/apple/erp/mapper/ExceptionWorkorderMapper.java
backend/src/main/java/com/apple/erp/service/DashboardService.java
backend/src/main/java/com/apple/erp/service/DeliveryMainService.java
backend/src/main/java/com/apple/erp/service/ExceptionWorkorderService.java
backend/src/main/java/com/apple/erp/service/InvoiceMainService.java
backend/src/main/java/com/apple/erp/service/OrderMainService.java
backend/src/main/java/com/apple/erp/service/RebateService.java
backend/src/main/java/com/apple/erp/service/SessionService.java
backend/src/main/java/com/apple/erp/service/impl/DashboardServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/DeliveryMainServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/ExceptionWorkorderServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/InvoiceMainServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/OrderMainServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/RebateServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/SessionServiceImpl.java
backend/src/main/java/com/apple/erp/service/impl/SysMenuServiceImpl.java
backend/src/main/java/com/apple/erp/util/DictValueConverter.java
backend/src/main/resources/mapper/ExceptionWorkorderMapper.xml
frontend/src/api/dashboard.ts
frontend/src/api/exceptionWorkorder.ts
frontend/src/api/rebate.ts
frontend/src/views/dashboard/index.vue
frontend/src/views/exceptionWorkorder/index.vue
frontend/src/views/rebate/index.vue
API接口文档.md
View file @
4315aa6
...
...
@@ -27,7 +27,55 @@
-
`message`
: 响应消息
-
`data`
: 响应数据,具体内容根据接口而定
## 1. 认证管理 (AuthController)
## 1. 首页仪表板 (DashboardController)
### 1.1 获取首页统计数据
**接口路径:**
`GET /api/dashboard/stats`
**功能描述:**
获取首页仪表板的统计数据,包括今日订单、待出库、待开票、销售额等信息
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
```
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"获取统计数据成功"
,
"data"
:
{
"todayOrderCount"
:
23
,
"pendingDeliveryCount"
:
8
,
"pendingInvoiceCount"
:
5
,
"todaySalesAmount"
:
125680.50
,
"onlineUserCount"
:
156
,
"systemStatus"
:
"正常运行"
,
"systemVersion"
:
"v1.0.0"
}
}
```
**错误响应示例:**
```
json
{
"code"
:
500
,
"message"
:
"获取统计数据失败: 数据库连接异常"
,
"data"
:
null
}
```
**响应字段说明:**
-
`todayOrderCount`
: 今日订单数量
-
`pendingDeliveryCount`
: 待出库数量
-
`pendingInvoiceCount`
: 待开票数量
-
`todaySalesAmount`
: 今日销售额
-
`onlineUserCount`
: 在线用户数
-
`systemStatus`
: 系统状态
-
`systemVersion`
: 系统版本
## 2. 认证管理 (AuthController)
### 1.1 用户登录
...
...
@@ -2491,8 +2539,418 @@
**响应:**
返回Excel文件流,文件名格式:
`发票数据_yyyyMMdd_HHmmss.xlsx`
### 4. 异常工单数据导出
**接口路径:**
`POST /api/exception-workorder/export`
**请求方法:**
POST
**权限要求:**
`exception:workorder:export`
**请求参数:**
同异常工单查询接口参数
**响应:**
返回Excel文件流,文件名格式:
`异常工单数据_yyyyMMdd_HHmmss.xlsx`
## 12. 异常工单管理 (ExceptionWorkorderController)
### 12.1 分页查询异常工单列表
**接口路径:**
`GET /api/exception-workorder/list`
**功能描述:**
根据条件分页查询异常工单列表
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
```
**请求参数:**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workorderNo | String | 否 | 工单编号(模糊查询) |
| orderNo | String | 否 | 关联订单号(模糊查询) |
| dealerCode | String | 否 | 经销商编码 |
| dealerName | String | 否 | 经销商名称(模糊查询) |
| workorderStatus | Integer | 否 | 工单状态(1-待处理/2-处理中/3-已解决/4-已关闭) |
| exceptionType | Integer | 否 | 异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常) |
| severityLevel | Integer | 否 | 严重程度(1-高/2-中/3-低) |
| handlerUser | String | 否 | 处理人(模糊查询) |
| startTime | String | 否 | 创建开始时间(yyyy-MM-dd HH:mm:ss) |
| endTime | String | 否 | 创建结束时间(yyyy-MM-dd HH:mm:ss) |
| pageNum | Integer | 是 | 页码,默认1 |
| pageSize | Integer | 是 | 每页大小,默认10 |
**权限要求:**
`exception:workorder:list`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"操作成功"
,
"data"
:
{
"records"
:
[
{
"workorderId"
:
1
,
"workorderNo"
:
"EW202501290001"
,
"orderNo"
:
"ORD202501290001"
,
"dealerCode"
:
"D001"
,
"dealerName"
:
"北京经销商"
,
"exceptionType"
:
1
,
"exceptionTypeName"
:
"逻辑验证异常"
,
"severityLevel"
:
1
,
"severityLevelName"
:
"高"
,
"workorderStatus"
:
1
,
"workorderStatusName"
:
"待处理"
,
"createTime"
:
"2025-01-29 10:00:00"
,
"expectCompleteTime"
:
"2025-01-30 18:00:00"
,
"handlerUser"
:
"张三"
,
"exceptionDesc"
:
"订单金额与产品单价不匹配"
,
"handleSuggest"
:
"请核实订单明细"
,
"dataSource"
:
"系统自动生成"
,
"createBy"
:
"system"
,
"updateBy"
:
null
,
"updateTime"
:
null
,
"workorderLogs"
:
[]
}
],
"total"
:
1
,
"size"
:
10
,
"current"
:
1
,
"pages"
:
1
}
}
```
### 12.2 获取异常工单详情
**接口路径:**
`GET /api/exception-workorder/{workorderId}`
**功能描述:**
根据工单ID获取异常工单详细信息,包含处理日志
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
```
**路径参数:**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workorderId | Long | 是 | 工单ID |
**权限要求:**
`exception:workorder:detail`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"操作成功"
,
"data"
:
{
"workorderId"
:
1
,
"workorderNo"
:
"EW202501290001"
,
"orderNo"
:
"ORD202501290001"
,
"dealerCode"
:
"D001"
,
"dealerName"
:
"北京经销商"
,
"exceptionType"
:
1
,
"exceptionTypeName"
:
"逻辑验证异常"
,
"severityLevel"
:
1
,
"severityLevelName"
:
"高"
,
"workorderStatus"
:
2
,
"workorderStatusName"
:
"处理中"
,
"createTime"
:
"2025-01-29 10:00:00"
,
"expectCompleteTime"
:
"2025-01-30 18:00:00"
,
"handlerUser"
:
"张三"
,
"exceptionDesc"
:
"订单金额与产品单价不匹配"
,
"handleSuggest"
:
"请核实订单明细"
,
"dataSource"
:
"系统自动生成"
,
"createBy"
:
"system"
,
"updateBy"
:
"张三"
,
"updateTime"
:
"2025-01-29 11:00:00"
,
"workorderLogs"
:
[
{
"logId"
:
1
,
"workorderId"
:
1
,
"workorderNo"
:
"EW202501290001"
,
"handleUser"
:
"张三"
,
"handleTime"
:
"2025-01-29 11:00:00"
,
"beforeStatus"
:
1
,
"beforeStatusName"
:
"待处理"
,
"afterStatus"
:
2
,
"afterStatusName"
:
"处理中"
,
"handleOpinion"
:
"开始处理此工单"
,
"attachUrl"
:
null
,
"createBy"
:
"张三"
,
"createTime"
:
"2025-01-29 11:00:00"
}
]
}
}
```
### 12.3 新增异常工单
**接口路径:**
`POST /api/exception-workorder`
**功能描述:**
创建新的异常工单
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
```
**请求体:**
```
json
{
"workorderNo"
:
"EW202501290002"
,
"orderNo"
:
"ORD202501290002"
,
"dealerCode"
:
"D002"
,
"dealerName"
:
"上海经销商"
,
"exceptionType"
:
2
,
"severityLevel"
:
2
,
"workorderStatus"
:
1
,
"expectCompleteTime"
:
"2025-01-30 18:00:00"
,
"handlerUser"
:
"李四"
,
"exceptionDesc"
:
"经销商信息不完整"
,
"handleSuggest"
:
"请补充经销商详细信息"
,
"dataSource"
:
"手动创建"
}
```
**权限要求:**
`exception:workorder:add`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"新增异常工单成功"
,
"data"
:
null
}
```
### 12.4 更新异常工单
**接口路径:**
`PUT /api/exception-workorder`
**功能描述:**
更新异常工单信息
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
```
**请求体:**
```
json
{
"workorderId"
:
1
,
"workorderNo"
:
"EW202501290001"
,
"orderNo"
:
"ORD202501290001"
,
"dealerCode"
:
"D001"
,
"dealerName"
:
"北京经销商"
,
"exceptionType"
:
1
,
"severityLevel"
:
1
,
"workorderStatus"
:
2
,
"expectCompleteTime"
:
"2025-01-30 18:00:00"
,
"handlerUser"
:
"张三"
,
"exceptionDesc"
:
"订单金额与产品单价不匹配"
,
"handleSuggest"
:
"请核实订单明细并联系客户确认"
,
"dataSource"
:
"系统自动生成"
}
```
**权限要求:**
`exception:workorder:edit`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"更新异常工单成功"
,
"data"
:
null
}
```
### 12.5 更新工单状态
**接口路径:**
`POST /api/exception-workorder/status`
**功能描述:**
更新异常工单状态并记录处理日志
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
```
**请求体:**
```
json
{
"workorderId"
:
1
,
"workorderStatus"
:
3
,
"handlerUser"
:
"张三"
,
"handleOpinion"
:
"问题已解决,客户确认无误"
,
"attachUrl"
:
"http://example.com/attachment.pdf"
}
```
**权限要求:**
`exception:workorder:edit`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"更新工单状态成功"
,
"data"
:
null
}
```
### 12.6 删除异常工单
**接口路径:**
`DELETE /api/exception-workorder/{workorderId}`
**功能描述:**
根据工单ID逻辑删除异常工单
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
```
**路径参数:**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workorderId | Long | 是 | 工单ID |
**权限要求:**
`exception:workorder:delete`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"删除异常工单成功"
,
"data"
:
null
}
```
### 12.7 批量删除异常工单
**接口路径:**
`DELETE /api/exception-workorder/batch`
**功能描述:**
根据工单ID列表批量逻辑删除异常工单
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
```
**请求体:**
```
json
[
1
,
2
,
3
]
```
**权限要求:**
`exception:workorder:delete`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"批量删除异常工单成功"
,
"data"
:
null
}
```
### 12.8 批量更新工单状态
**接口路径:**
`POST /api/exception-workorder/batch-status`
**功能描述:**
批量更新异常工单状态
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
```
**请求参数:**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workorderIds | String | 是 | 工单ID列表,逗号分隔 |
| workorderStatus | Integer | 是 | 工单状态 |
| handlerUser | String | 是 | 处理人 |
**权限要求:**
`exception:workorder:edit`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"批量更新工单状态成功"
,
"data"
:
null
}
```
### 12.9 获取异常工单统计信息
**接口路径:**
`GET /api/exception-workorder/stats`
**功能描述:**
获取异常工单的统计信息
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
```
**权限要求:**
`exception:workorder:list`
**响应示例:**
```
json
{
"code"
:
200
,
"message"
:
"操作成功"
,
"data"
:
[
{
"workorderNo"
:
"total"
,
"workorderId"
:
10
,
"exceptionType"
:
3
,
"severityLevel"
:
2
,
"workorderStatus"
:
4
,
"handlerUser"
:
1
,
"exceptionDesc"
:
5
,
"handleSuggest"
:
3
,
"dataSource"
:
2
}
]
}
```
### 12.10 导出异常工单数据
**接口路径:**
`POST /api/exception-workorder/export`
**功能描述:**
根据查询条件导出异常工单数据到Excel
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
```
**请求体:**
同异常工单查询接口参数(可选,为空时导出所有数据)
**权限要求:**
`exception:workorder:export`
**响应:**
返回Excel文件流,文件名格式:
`异常工单数据_yyyyMMdd_HHmmss.xlsx`
**导出字段:**
-
工单ID
-
工单编号
-
工单类型(异常类型名称)
-
严重程度(严重程度名称)
-
工单状态(工单状态名称)
-
问题描述
-
处理人
-
创建人
-
创建时间
-
更新时间
---
**文档版本:**
1.0.0
**最后更新:**
2025-01-2
7
**最后更新:**
2025-01-2
9
**维护人员:**
Apple ERP Team
...
...
backend/src/main/java/com/apple/erp/config/SecurityConfig.java
View file @
4315aa6
package
com
.
apple
.
erp
.
config
;
import
com.apple.erp.utils.JwtUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
...
...
@@ -34,9 +32,6 @@ import java.util.Arrays;
public
class
SecurityConfig
{
@Autowired
private
JwtUtils
jwtUtils
;
@Autowired
private
JwtAuthenticationFilter
jwtAuthenticationFilter
;
@Autowired
...
...
@@ -72,6 +67,7 @@ public class SecurityConfig {
.
antMatchers
(
"/api/auth/**"
).
permitAll
()
.
antMatchers
(
"/api/public/**"
).
permitAll
()
.
antMatchers
(
"/api/test/public"
).
permitAll
()
// 允许测试接口
.
antMatchers
(
"/api/dashboard/**"
).
authenticated
()
// 首页统计需要认证
.
antMatchers
(
"/actuator/**"
).
permitAll
()
.
antMatchers
(
"/druid/**"
).
permitAll
()
.
antMatchers
(
"/swagger-ui/**"
).
permitAll
()
...
...
backend/src/main/java/com/apple/erp/controller/AuthController.java
View file @
4315aa6
...
...
@@ -9,6 +9,7 @@ import com.apple.erp.dto.response.UserInfoRes;
import
com.apple.erp.entity.SysUser
;
import
com.apple.erp.service.SysUserService
;
import
com.apple.erp.service.SysLogService
;
import
com.apple.erp.service.SessionService
;
import
com.apple.erp.utils.JwtUtils
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.Parameter
;
...
...
@@ -24,7 +25,9 @@ import org.springframework.web.bind.annotation.*;
import
javax.servlet.http.HttpServletRequest
;
import
javax.validation.Valid
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
/**
* 认证控制器
...
...
@@ -52,6 +55,9 @@ public class AuthController {
@Autowired
private
SysLogService
sysLogService
;
@Autowired
private
SessionService
sessionService
;
/**
* 用户登录
*/
...
...
@@ -96,6 +102,21 @@ public class AuthController {
String
token
=
jwtUtils
.
generateToken
(
username
);
String
refreshToken
=
jwtUtils
.
generateRefreshToken
(
username
);
// 将用户会话信息存储到Redis
try
{
Map
<
String
,
Object
>
sessionData
=
new
HashMap
<>();
sessionData
.
put
(
"username"
,
username
);
sessionData
.
put
(
"loginTime"
,
System
.
currentTimeMillis
());
sessionData
.
put
(
"clientIp"
,
getClientIp
(
request
));
sessionData
.
put
(
"token"
,
token
);
sessionService
.
storeUserSession
(
username
,
sessionData
);
log
.
info
(
"用户会话已存储: {}"
,
username
);
}
catch
(
Exception
e
)
{
log
.
error
(
"存储用户会话失败"
,
e
);
// Redis失败不影响登录流程
}
LoginRes
loginResponse
=
new
LoginRes
(
token
,
refreshToken
,
username
);
ApiRes
<
LoginRes
>
response
=
ApiRes
.
success
(
"登录成功"
,
loginResponse
);
return
ResponseEntity
.
ok
(
response
);
...
...
@@ -192,6 +213,16 @@ public class AuthController {
}
}
// 清除Redis中的用户会话
if
(
username
!=
null
)
{
try
{
sessionService
.
removeUserSession
(
username
);
log
.
info
(
"用户会话已清除: {}"
,
username
);
}
catch
(
Exception
e
)
{
log
.
error
(
"清除用户会话失败"
,
e
);
}
}
// 清除Spring Security上下文
SecurityContextHolder
.
clearContext
();
...
...
backend/src/main/java/com/apple/erp/controller/DashboardController.java
0 → 100644
View file @
4315aa6
package
com
.
apple
.
erp
.
controller
;
import
com.apple.erp.dto.response.ApiRes
;
import
com.apple.erp.dto.response.DashboardStatsRes
;
import
com.apple.erp.service.DashboardService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
java.util.List
;
import
java.util.Map
;
/**
* 首页仪表板控制器
* 提供首页统计数据接口
*/
@Slf4j
@RestController
@RequestMapping
(
"/api/dashboard"
)
public
class
DashboardController
{
@Autowired
private
DashboardService
dashboardService
;
/**
* 获取首页统计数据
* @return 统计数据
*/
@GetMapping
(
"/stats"
)
public
ApiRes
<
DashboardStatsRes
>
getDashboardStats
()
{
log
.
info
(
"获取首页统计数据请求"
);
try
{
DashboardStatsRes
result
=
dashboardService
.
getDashboardStats
();
log
.
info
(
"首页统计数据获取成功: {}"
,
result
);
return
ApiRes
.
success
(
"获取统计数据成功"
,
result
);
}
catch
(
Exception
e
)
{
log
.
error
(
"获取首页统计数据失败"
,
e
);
return
ApiRes
.
error
(
500
,
"获取统计数据失败: "
+
e
.
getMessage
());
}
}
/**
* 测试接口
* @return 测试数据
*/
@GetMapping
(
"/test"
)
public
ApiRes
<
DashboardStatsRes
>
getTestStats
()
{
log
.
info
(
"测试接口调用"
);
DashboardStatsRes
stats
=
new
DashboardStatsRes
();
stats
.
setTodayOrderCount
(
10
);
stats
.
setPendingDeliveryCount
(
5
);
stats
.
setPendingInvoiceCount
(
3
);
stats
.
setTodaySalesAmount
(
10000.0
);
stats
.
setOnlineUserCount
(
100
);
stats
.
setSystemStatus
(
"测试正常"
);
stats
.
setSystemVersion
(
"v1.0.0"
);
return
ApiRes
.
success
(
"测试成功"
,
stats
);
}
/**
* 获取最近活动
* @return 最近活动列表
*/
@GetMapping
(
"/activities"
)
public
ApiRes
<
List
<
Map
<
String
,
Object
>>>
getRecentActivities
()
{
log
.
info
(
"获取最近活动请求"
);
try
{
List
<
Map
<
String
,
Object
>>
activities
=
dashboardService
.
getRecentActivities
();
return
ApiRes
.
success
(
"获取最近活动成功"
,
activities
);
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近活动失败"
,
e
);
return
ApiRes
.
error
(
500
,
"获取最近活动失败: "
+
e
.
getMessage
());
}
}
}
backend/src/main/java/com/apple/erp/controller/ExceptionWorkorderController.java
View file @
4315aa6
...
...
@@ -6,12 +6,14 @@ import com.apple.erp.dto.ExceptionWorkorderRes;
import
com.apple.erp.dto.ExceptionWorkorderStatusUpdateReq
;
import
com.apple.erp.dto.ExceptionWorkorderUpdateReq
;
import
com.apple.erp.service.ExceptionWorkorderService
;
import
com.apple.erp.service.ExcelExportService
;
import
com.apple.erp.dto.response.ApiRes
;
import
com.baomidou.mybatisplus.extension.plugins.pagination.Page
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.security.access.prepost.PreAuthorize
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.*
;
...
...
@@ -34,6 +36,9 @@ public class ExceptionWorkorderController {
@Autowired
private
ExceptionWorkorderService
exceptionWorkorderService
;
@Autowired
private
ExcelExportService
excelExportService
;
@Operation
(
summary
=
"分页查询异常工单列表"
,
description
=
"根据条件分页查询异常工单列表"
)
@GetMapping
(
"/list"
)
@PreAuthorize
(
"hasAuthority('exception:workorder:list')"
)
...
...
@@ -168,4 +173,22 @@ public class ExceptionWorkorderController {
List
<
ExceptionWorkorderRes
>
stats
=
exceptionWorkorderService
.
getExceptionWorkorderStats
();
return
ApiRes
.
success
(
stats
);
}
@Operation
(
summary
=
"导出异常工单数据"
,
description
=
"根据查询条件导出异常工单数据到Excel"
)
@PostMapping
(
"/export"
)
@PreAuthorize
(
"hasAuthority('exception:workorder:export')"
)
public
ResponseEntity
<
byte
[]>
exportExceptionWorkorders
(
@RequestBody
(
required
=
false
)
ExceptionWorkorderQueryReq
queryReq
)
{
try
{
// 如果请求体为空,创建默认查询条件
if
(
queryReq
==
null
)
{
queryReq
=
new
ExceptionWorkorderQueryReq
();
}
// 获取异常工单数据
List
<
ExceptionWorkorderRes
>
workorders
=
exceptionWorkorderService
.
getExceptionWorkorderListForExport
(
queryReq
);
// 导出Excel
return
excelExportService
.
exportExceptionWorkorders
(
workorders
);
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"导出异常工单数据失败: "
+
e
.
getMessage
());
}
}
}
...
...
backend/src/main/java/com/apple/erp/dto/response/DashboardStatsRes.java
0 → 100644
View file @
4315aa6
package
com
.
apple
.
erp
.
dto
.
response
;
import
lombok.Data
;
/**
* 首页统计数据响应DTO
*/
@Data
public
class
DashboardStatsRes
{
/**
* 今日订单数量
*/
private
Integer
todayOrderCount
;
/**
* 待出库数量
*/
private
Integer
pendingDeliveryCount
;
/**
* 待开票数量
*/
private
Integer
pendingInvoiceCount
;
/**
* 今日销售额
*/
private
Double
todaySalesAmount
;
/**
* 在线用户数
*/
private
Integer
onlineUserCount
;
/**
* 系统状态
*/
private
String
systemStatus
;
/**
* 系统版本
*/
private
String
systemVersion
;
/**
* 今日订单增长率
*/
private
String
orderGrowthRate
;
/**
* 今日销售额增长率
*/
private
String
salesGrowthRate
;
}
backend/src/main/java/com/apple/erp/mapper/ExceptionWorkorderMapper.java
View file @
4315aa6
...
...
@@ -54,4 +54,12 @@ public interface ExceptionWorkorderMapper extends BaseMapper<ExceptionWorkorder>
int
batchUpdateWorkorderStatus
(
@Param
(
"workorderIds"
)
List
<
Long
>
workorderIds
,
@Param
(
"workorderStatus"
)
Integer
workorderStatus
,
@Param
(
"handlerUser"
)
String
handlerUser
);
/**
* 获取异常工单列表用于导出
*
* @param queryReq 查询条件
* @return 异常工单列表(不分页)
*/
List
<
ExceptionWorkorderRes
>
selectExceptionWorkorderListForExport
(
@Param
(
"query"
)
ExceptionWorkorderQueryReq
queryReq
);
}
...
...
backend/src/main/java/com/apple/erp/service/DashboardService.java
0 → 100644
View file @
4315aa6
package
com
.
apple
.
erp
.
service
;
import
com.apple.erp.dto.response.DashboardStatsRes
;
import
java.util.List
;
import
java.util.Map
;
/**
* 首页仪表板服务接口
*/
public
interface
DashboardService
{
/**
* 获取首页统计数据
* @return 统计数据
*/
DashboardStatsRes
getDashboardStats
();
/**
* 获取最近活动
* @return 最近活动列表
*/
List
<
Map
<
String
,
Object
>>
getRecentActivities
();
}
backend/src/main/java/com/apple/erp/service/DeliveryMainService.java
View file @
4315aa6
...
...
@@ -67,6 +67,20 @@ public interface DeliveryMainService extends IService<DeliveryMain> {
* @return 是否成功
*/
boolean
updateDeliveryStatus
(
Long
deliveryId
,
Integer
deliveryStatus
);
/**
* 获取待出库数量
* @return 待出库数量
*/
Integer
getPendingDeliveryCount
();
/**
* 获取最近的出库记录
*
* @param limit 限制数量
* @return 最近出库列表
*/
List
<
DeliveryRes
>
getRecentDeliveries
(
Integer
limit
);
}
...
...
backend/src/main/java/com/apple/erp/service/ExceptionWorkorderService.java
View file @
4315aa6
...
...
@@ -91,4 +91,12 @@ public interface ExceptionWorkorderService extends IService<ExceptionWorkorder>
* @return 统计信息
*/
List
<
ExceptionWorkorderRes
>
getExceptionWorkorderStats
();
/**
* 获取异常工单列表用于导出
*
* @param queryReq 查询条件
* @return 异常工单列表(不分页)
*/
List
<
ExceptionWorkorderRes
>
getExceptionWorkorderListForExport
(
ExceptionWorkorderQueryReq
queryReq
);
}
...
...
backend/src/main/java/com/apple/erp/service/InvoiceMainService.java
View file @
4315aa6
...
...
@@ -67,6 +67,20 @@ public interface InvoiceMainService extends IService<InvoiceMain> {
* @return 是否成功
*/
boolean
updateInvoiceStatus
(
Long
invoiceId
,
Integer
invoiceStatus
);
/**
* 获取待开票数量
* @return 待开票数量
*/
Integer
getPendingInvoiceCount
();
/**
* 获取最近的发票记录
*
* @param limit 限制数量
* @return 最近发票列表
*/
List
<
InvoiceRes
>
getRecentInvoices
(
Integer
limit
);
}
...
...
backend/src/main/java/com/apple/erp/service/OrderMainService.java
View file @
4315aa6
...
...
@@ -92,5 +92,29 @@ public interface OrderMainService extends IService<OrderMain> {
* @return 是否成功
*/
boolean
updateRebateCalcFlag
(
Long
orderId
,
Integer
rebateCalcFlag
);
/**
* 获取今日订单数量
*
* @param date 日期
* @return 订单数量
*/
Integer
getTodayOrderCount
(
String
date
);
/**
* 获取今日销售额
*
* @param date 日期
* @return 销售额
*/
Double
getTodaySalesAmount
(
String
date
);
/**
* 获取最近的订单记录
*
* @param limit 限制数量
* @return 最近订单列表
*/
List
<
OrderRes
>
getRecentOrders
(
Integer
limit
);
}
...
...
backend/src/main/java/com/apple/erp/service/RebateService.java
View file @
4315aa6
...
...
@@ -139,4 +139,12 @@ public interface RebateService extends IService<Rebate> {
* @return 趋势统计数据
*/
List
<
Map
<
String
,
Object
>>
getRebateTrendStats
(
String
startDate
,
String
endDate
);
/**
* 获取最近的返利记录
*
* @param limit 限制数量
* @return 最近返利列表
*/
List
<
RebateRes
>
getRecentRebates
(
Integer
limit
);
}
...
...
backend/src/main/java/com/apple/erp/service/SessionService.java
0 → 100644
View file @
4315aa6
package
com
.
apple
.
erp
.
service
;
import
java.util.Map
;
/**
* 会话管理服务接口
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
public
interface
SessionService
{
/**
* 存储用户会话信息
* @param username 用户名
* @param sessionData 会话数据
*/
void
storeUserSession
(
String
username
,
Map
<
String
,
Object
>
sessionData
);
/**
* 获取用户会话信息
* @param username 用户名
* @return 会话数据
*/
Map
<
String
,
Object
>
getUserSession
(
String
username
);
/**
* 删除用户会话
* @param username 用户名
*/
void
removeUserSession
(
String
username
);
/**
* 获取在线用户数
* @return 在线用户数
*/
Integer
getOnlineUserCount
();
/**
* 清理过期会话
*/
void
cleanupExpiredSessions
();
}
backend/src/main/java/com/apple/erp/service/impl/DashboardServiceImpl.java
0 → 100644
View file @
4315aa6
package
com
.
apple
.
erp
.
service
.
impl
;
import
com.apple.erp.dto.response.DashboardStatsRes
;
import
com.apple.erp.dto.OrderRes
;
import
com.apple.erp.dto.DeliveryRes
;
import
com.apple.erp.dto.InvoiceRes
;
import
com.apple.erp.dto.response.RebateRes
;
import
com.apple.erp.service.DashboardService
;
import
com.apple.erp.service.OrderMainService
;
import
com.apple.erp.service.DeliveryMainService
;
import
com.apple.erp.service.InvoiceMainService
;
import
com.apple.erp.service.RebateService
;
import
com.apple.erp.service.SessionService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
java.time.LocalDate
;
import
java.time.format.DateTimeFormatter
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
/**
* 首页仪表板服务实现
*/
@Slf4j
@Service
public
class
DashboardServiceImpl
implements
DashboardService
{
@Autowired
private
OrderMainService
orderMainService
;
@Autowired
private
DeliveryMainService
deliveryMainService
;
@Autowired
private
InvoiceMainService
invoiceMainService
;
@Autowired
private
RebateService
rebateService
;
@Autowired
private
SessionService
sessionService
;
@Override
public
DashboardStatsRes
getDashboardStats
()
{
log
.
info
(
"开始获取首页统计数据"
);
DashboardStatsRes
stats
=
new
DashboardStatsRes
();
try
{
// 获取今日订单数量
String
today
=
LocalDate
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
));
log
.
info
(
"查询今日订单数量,日期: {}"
,
today
);
stats
.
setTodayOrderCount
(
orderMainService
.
getTodayOrderCount
(
today
));
// 获取待出库数量(状态为已确认但未出库的订单)
log
.
info
(
"查询待出库数量"
);
stats
.
setPendingDeliveryCount
(
deliveryMainService
.
getPendingDeliveryCount
());
// 获取待开票数量(已出库但未开票的订单)
log
.
info
(
"查询待开票数量"
);
stats
.
setPendingInvoiceCount
(
invoiceMainService
.
getPendingInvoiceCount
());
// 获取今日销售额
log
.
info
(
"查询今日销售额,日期: {}"
,
today
);
stats
.
setTodaySalesAmount
(
orderMainService
.
getTodaySalesAmount
(
today
));
// 获取在线用户数(从Redis或Session中获取)
stats
.
setOnlineUserCount
(
getOnlineUserCount
());
// 系统状态
stats
.
setSystemStatus
(
getSystemStatus
());
// 系统版本
stats
.
setSystemVersion
(
"v1.0.0"
);
// 计算增长率数据
stats
.
setOrderGrowthRate
(
calculateOrderGrowthRate
(
today
));
stats
.
setSalesGrowthRate
(
calculateSalesGrowthRate
(
today
));
log
.
info
(
"首页统计数据获取完成: {}"
,
stats
);
}
catch
(
Exception
e
)
{
log
.
error
(
"获取首页统计数据失败"
,
e
);
// 如果获取数据失败,返回默认值
stats
.
setTodayOrderCount
(
0
);
stats
.
setPendingDeliveryCount
(
0
);
stats
.
setPendingInvoiceCount
(
0
);
stats
.
setTodaySalesAmount
(
0.0
);
stats
.
setOnlineUserCount
(
0
);
stats
.
setSystemStatus
(
"系统异常"
);
stats
.
setSystemVersion
(
"v1.0.0"
);
stats
.
setOrderGrowthRate
(
"0%"
);
stats
.
setSalesGrowthRate
(
"0%"
);
}
return
stats
;
}
@Override
public
List
<
Map
<
String
,
Object
>>
getRecentActivities
()
{
log
.
info
(
"开始获取最近活动数据"
);
List
<
Map
<
String
,
Object
>>
activities
=
new
ArrayList
<>();
try
{
// 从数据库获取最近的活动数据
activities
.
addAll
(
getRecentOrders
());
activities
.
addAll
(
getRecentDeliveries
());
activities
.
addAll
(
getRecentInvoices
());
activities
.
addAll
(
getRecentRebates
());
// 按时间排序,取最新的5条
activities
.
sort
((
a
,
b
)
->
{
String
timeA
=
(
String
)
a
.
get
(
"time"
);
String
timeB
=
(
String
)
b
.
get
(
"time"
);
return
timeB
.
compareTo
(
timeA
);
});
if
(
activities
.
size
()
>
5
)
{
activities
=
activities
.
subList
(
0
,
5
);
}
log
.
info
(
"最近活动数据获取完成,共{}条记录"
,
activities
.
size
());
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近活动数据失败"
,
e
);
}
return
activities
;
}
/**
* 获取最近的订单活动
*/
private
List
<
Map
<
String
,
Object
>>
getRecentOrders
()
{
List
<
Map
<
String
,
Object
>>
activities
=
new
ArrayList
<>();
try
{
List
<
OrderRes
>
recentOrders
=
orderMainService
.
getRecentOrders
(
2
);
for
(
OrderRes
order
:
recentOrders
)
{
Map
<
String
,
Object
>
activity
=
new
HashMap
<>();
activity
.
put
(
"id"
,
order
.
getOrderId
());
activity
.
put
(
"type"
,
"order"
);
activity
.
put
(
"title"
,
"新订单 "
+
order
.
getOrderNo
()
+
" 已创建"
);
activity
.
put
(
"time"
,
formatTimeAgo
(
order
.
getCreateTime
()));
activities
.
add
(
activity
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近订单活动失败"
,
e
);
}
return
activities
;
}
/**
* 获取最近的出库活动
*/
private
List
<
Map
<
String
,
Object
>>
getRecentDeliveries
()
{
List
<
Map
<
String
,
Object
>>
activities
=
new
ArrayList
<>();
try
{
List
<
DeliveryRes
>
recentDeliveries
=
deliveryMainService
.
getRecentDeliveries
(
2
);
for
(
DeliveryRes
delivery
:
recentDeliveries
)
{
Map
<
String
,
Object
>
activity
=
new
HashMap
<>();
activity
.
put
(
"id"
,
delivery
.
getDeliveryId
());
activity
.
put
(
"type"
,
"delivery"
);
activity
.
put
(
"title"
,
"出库单 "
+
delivery
.
getDeliveryNo
()
+
" 已创建"
);
activity
.
put
(
"time"
,
formatTimeAgo
(
delivery
.
getCreateTime
()));
activities
.
add
(
activity
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近出库活动失败"
,
e
);
}
return
activities
;
}
/**
* 获取最近的发票活动
*/
private
List
<
Map
<
String
,
Object
>>
getRecentInvoices
()
{
List
<
Map
<
String
,
Object
>>
activities
=
new
ArrayList
<>();
try
{
List
<
InvoiceRes
>
recentInvoices
=
invoiceMainService
.
getRecentInvoices
(
2
);
for
(
InvoiceRes
invoice
:
recentInvoices
)
{
Map
<
String
,
Object
>
activity
=
new
HashMap
<>();
activity
.
put
(
"id"
,
invoice
.
getInvoiceId
());
activity
.
put
(
"type"
,
"invoice"
);
activity
.
put
(
"title"
,
"发票 "
+
invoice
.
getInvoiceNo
()
+
" 已开具"
);
activity
.
put
(
"time"
,
formatTimeAgo
(
invoice
.
getCreateTime
()));
activities
.
add
(
activity
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近发票活动失败"
,
e
);
}
return
activities
;
}
/**
* 获取最近的返利活动
*/
private
List
<
Map
<
String
,
Object
>>
getRecentRebates
()
{
List
<
Map
<
String
,
Object
>>
activities
=
new
ArrayList
<>();
try
{
List
<
RebateRes
>
recentRebates
=
rebateService
.
getRecentRebates
(
2
);
for
(
RebateRes
rebate
:
recentRebates
)
{
Map
<
String
,
Object
>
activity
=
new
HashMap
<>();
activity
.
put
(
"id"
,
rebate
.
getRebateId
());
activity
.
put
(
"type"
,
"rebate"
);
activity
.
put
(
"title"
,
"返利 "
+
rebate
.
getRebateNo
()
+
" 已计算"
);
activity
.
put
(
"time"
,
formatTimeAgo
(
rebate
.
getCreateTime
()));
activities
.
add
(
activity
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近返利活动失败"
,
e
);
}
return
activities
;
}
/**
* 获取在线用户数
*/
private
Integer
getOnlineUserCount
()
{
try
{
// 使用SessionService获取在线用户数
Integer
onlineCount
=
sessionService
.
getOnlineUserCount
();
log
.
info
(
"获取在线用户数: {}"
,
onlineCount
);
return
onlineCount
;
}
catch
(
Exception
e
)
{
log
.
error
(
"获取在线用户数失败"
,
e
);
// 如果Redis不可用,尝试从数据库获取活跃用户数
return
getActiveUserCountFromDatabase
();
}
}
/**
* 从数据库获取活跃用户数(Redis不可用时的备用方案)
*/
private
Integer
getActiveUserCountFromDatabase
()
{
try
{
// 查询最近30分钟内有登录记录的用户数
// 这里需要根据实际的用户表结构调整
log
.
info
(
"从数据库获取活跃用户数"
);
// TODO: 实现数据库查询逻辑
return
0
;
}
catch
(
Exception
e
)
{
log
.
error
(
"从数据库获取活跃用户数失败"
,
e
);
return
0
;
}
}
/**
* 获取系统状态
*/
private
String
getSystemStatus
()
{
try
{
// 检查数据库连接状态
// 检查关键服务状态
// 这里暂时返回正常运行,后续可以添加健康检查
return
"正常运行"
;
}
catch
(
Exception
e
)
{
log
.
error
(
"获取系统状态失败"
,
e
);
return
"系统异常"
;
}
}
/**
* 计算订单增长率
*/
private
String
calculateOrderGrowthRate
(
String
today
)
{
try
{
// 获取昨日订单数量
String
yesterday
=
LocalDate
.
now
().
minusDays
(
1
).
format
(
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
));
Integer
yesterdayCount
=
orderMainService
.
getTodayOrderCount
(
yesterday
);
Integer
todayCount
=
orderMainService
.
getTodayOrderCount
(
today
);
if
(
yesterdayCount
==
null
||
yesterdayCount
==
0
)
{
return
todayCount
>
0
?
"+100%"
:
"0%"
;
}
double
growthRate
=
((
double
)
(
todayCount
-
yesterdayCount
)
/
yesterdayCount
)
*
100
;
return
String
.
format
(
"%+.1f%%"
,
growthRate
);
}
catch
(
Exception
e
)
{
log
.
error
(
"计算订单增长率失败"
,
e
);
return
"0%"
;
}
}
/**
* 计算销售额增长率
*/
private
String
calculateSalesGrowthRate
(
String
today
)
{
try
{
// 获取昨日销售额
String
yesterday
=
LocalDate
.
now
().
minusDays
(
1
).
format
(
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
));
Double
yesterdayAmount
=
orderMainService
.
getTodaySalesAmount
(
yesterday
);
Double
todayAmount
=
orderMainService
.
getTodaySalesAmount
(
today
);
if
(
yesterdayAmount
==
null
||
yesterdayAmount
==
0
)
{
return
todayAmount
>
0
?
"+100%"
:
"0%"
;
}
double
growthRate
=
((
todayAmount
-
yesterdayAmount
)
/
yesterdayAmount
)
*
100
;
return
String
.
format
(
"%+.1f%%"
,
growthRate
);
}
catch
(
Exception
e
)
{
log
.
error
(
"计算销售额增长率失败"
,
e
);
return
"0%"
;
}
}
/**
* 格式化时间为相对时间
*/
private
String
formatTimeAgo
(
java
.
time
.
LocalDateTime
dateTime
)
{
if
(
dateTime
==
null
)
{
return
"未知时间"
;
}
java
.
time
.
LocalDateTime
now
=
java
.
time
.
LocalDateTime
.
now
();
long
minutes
=
java
.
time
.
Duration
.
between
(
dateTime
,
now
).
toMinutes
();
if
(
minutes
<
1
)
{
return
"刚刚"
;
}
else
if
(
minutes
<
60
)
{
return
minutes
+
"分钟前"
;
}
else
if
(
minutes
<
1440
)
{
// 24小时
long
hours
=
minutes
/
60
;
return
hours
+
"小时前"
;
}
else
{
long
days
=
minutes
/
1440
;
return
days
+
"天前"
;
}
}
}
backend/src/main/java/com/apple/erp/service/impl/DeliveryMainServiceImpl.java
View file @
4315aa6
...
...
@@ -22,6 +22,7 @@ import org.springframework.util.StringUtils;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.stream.Collectors
;
...
...
@@ -254,6 +255,34 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
return
null
;
}
}
@Override
public
Integer
getPendingDeliveryCount
()
{
LambdaQueryWrapper
<
DeliveryMain
>
wrapper
=
new
LambdaQueryWrapper
<>();
wrapper
.
eq
(
DeliveryMain:
:
getDelFlag
,
"0"
)
.
eq
(
DeliveryMain:
:
getDeliveryStatus
,
0
);
// 0表示待出库
return
Math
.
toIntExact
(
count
(
wrapper
));
}
@Override
public
List
<
DeliveryRes
>
getRecentDeliveries
(
Integer
limit
)
{
try
{
LambdaQueryWrapper
<
DeliveryMain
>
wrapper
=
new
LambdaQueryWrapper
<>();
wrapper
.
eq
(
DeliveryMain:
:
getDelFlag
,
"0"
)
.
orderByDesc
(
DeliveryMain:
:
getCreateTime
)
.
last
(
"LIMIT "
+
(
limit
!=
null
?
limit
:
5
));
List
<
DeliveryMain
>
deliveries
=
list
(
wrapper
);
return
deliveries
.
stream
().
map
(
delivery
->
{
DeliveryRes
deliveryRes
=
new
DeliveryRes
();
BeanUtils
.
copyProperties
(
delivery
,
deliveryRes
);
return
deliveryRes
;
}).
collect
(
Collectors
.
toList
());
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近出库记录失败"
,
e
);
return
new
ArrayList
<>();
}
}
}
...
...
backend/src/main/java/com/apple/erp/service/impl/ExceptionWorkorderServiceImpl.java
View file @
4315aa6
...
...
@@ -18,11 +18,9 @@ import org.springframework.beans.BeanUtils;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.util.StringUtils
;
import
java.time.LocalDateTime
;
import
java.util.List
;
import
java.util.stream.Collectors
;
/**
* 异常工单Service实现类
...
...
@@ -200,4 +198,10 @@ public class ExceptionWorkorderServiceImpl extends ServiceImpl<ExceptionWorkorde
public
List
<
ExceptionWorkorderRes
>
getExceptionWorkorderStats
()
{
return
exceptionWorkorderMapper
.
selectExceptionWorkorderStats
();
}
@Override
public
List
<
ExceptionWorkorderRes
>
getExceptionWorkorderListForExport
(
ExceptionWorkorderQueryReq
queryReq
)
{
// 不分页查询所有数据用于导出
return
exceptionWorkorderMapper
.
selectExceptionWorkorderListForExport
(
queryReq
);
}
}
...
...
backend/src/main/java/com/apple/erp/service/impl/InvoiceMainServiceImpl.java
View file @
4315aa6
...
...
@@ -21,6 +21,7 @@ import org.springframework.util.StringUtils;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.stream.Collectors
;
...
...
@@ -229,4 +230,31 @@ public class InvoiceMainServiceImpl extends ServiceImpl<InvoiceMainMapper, Invoi
}
}
@Override
public
Integer
getPendingInvoiceCount
()
{
LambdaQueryWrapper
<
InvoiceMain
>
wrapper
=
new
LambdaQueryWrapper
<>();
wrapper
.
eq
(
InvoiceMain:
:
getDelFlag
,
"0"
)
.
eq
(
InvoiceMain:
:
getInvoiceStatus
,
0
);
// 0表示待开票
return
Math
.
toIntExact
(
count
(
wrapper
));
}
@Override
public
List
<
InvoiceRes
>
getRecentInvoices
(
Integer
limit
)
{
try
{
LambdaQueryWrapper
<
InvoiceMain
>
wrapper
=
new
LambdaQueryWrapper
<>();
wrapper
.
eq
(
InvoiceMain:
:
getDelFlag
,
"0"
)
.
orderByDesc
(
InvoiceMain:
:
getCreateTime
)
.
last
(
"LIMIT "
+
(
limit
!=
null
?
limit
:
5
));
List
<
InvoiceMain
>
invoices
=
list
(
wrapper
);
return
invoices
.
stream
().
map
(
invoice
->
{
InvoiceRes
invoiceRes
=
new
InvoiceRes
();
BeanUtils
.
copyProperties
(
invoice
,
invoiceRes
);
return
invoiceRes
;
}).
collect
(
Collectors
.
toList
());
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近发票记录失败"
,
e
);
return
new
ArrayList
<>();
}
}
}
...
...
backend/src/main/java/com/apple/erp/service/impl/OrderMainServiceImpl.java
View file @
4315aa6
...
...
@@ -16,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
import
org.springframework.util.StringUtils
;
import
java.time.LocalDateTime
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.stream.Collectors
;
...
...
@@ -288,5 +289,59 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
order
.
setUpdateTime
(
LocalDateTime
.
now
());
return
orderMainMapper
.
updateById
(
order
)
>
0
;
}
@Override
public
Integer
getTodayOrderCount
(
String
date
)
{
try
{
LambdaQueryWrapper
<
OrderMain
>
wrapper
=
new
LambdaQueryWrapper
<>();
wrapper
.
eq
(
OrderMain:
:
getDelFlag
,
"0"
)
.
like
(
OrderMain:
:
getCreateTime
,
date
);
long
count
=
count
(
wrapper
);
System
.
out
.
println
(
"今日订单数量查询结果: "
+
count
+
", 日期: "
+
date
);
return
Math
.
toIntExact
(
count
);
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"查询今日订单数量失败: "
+
e
.
getMessage
());
return
0
;
}
}
@Override
public
Double
getTodaySalesAmount
(
String
date
)
{
try
{
LambdaQueryWrapper
<
OrderMain
>
wrapper
=
new
LambdaQueryWrapper
<>();
wrapper
.
eq
(
OrderMain:
:
getDelFlag
,
"0"
)
.
like
(
OrderMain:
:
getCreateTime
,
date
);
List
<
OrderMain
>
orders
=
list
(
wrapper
);
double
totalAmount
=
orders
.
stream
()
.
mapToDouble
(
order
->
order
.
getTotalAmount
()
!=
null
?
order
.
getTotalAmount
().
doubleValue
()
:
0.0
)
.
sum
();
System
.
out
.
println
(
"今日销售额查询结果: "
+
totalAmount
+
", 日期: "
+
date
);
return
totalAmount
;
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"查询今日销售额失败: "
+
e
.
getMessage
());
return
0.0
;
}
}
@Override
public
List
<
OrderRes
>
getRecentOrders
(
Integer
limit
)
{
try
{
LambdaQueryWrapper
<
OrderMain
>
wrapper
=
new
LambdaQueryWrapper
<>();
wrapper
.
eq
(
OrderMain:
:
getDelFlag
,
"0"
)
.
orderByDesc
(
OrderMain:
:
getCreateTime
)
.
last
(
"LIMIT "
+
(
limit
!=
null
?
limit
:
5
));
List
<
OrderMain
>
orders
=
list
(
wrapper
);
return
orders
.
stream
().
map
(
order
->
{
OrderRes
orderRes
=
new
OrderRes
();
BeanUtils
.
copyProperties
(
order
,
orderRes
);
return
orderRes
;
}).
collect
(
Collectors
.
toList
());
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近订单失败"
,
e
);
return
new
ArrayList
<>();
}
}
}
...
...
backend/src/main/java/com/apple/erp/service/impl/RebateServiceImpl.java
View file @
4315aa6
...
...
@@ -275,4 +275,24 @@ public class RebateServiceImpl extends ServiceImpl<RebateMapper, Rebate> impleme
log
.
info
(
"获取返利趋势统计数据,开始日期:{},结束日期:{}"
,
startDate
,
endDate
);
return
rebateMapper
.
getRebateTrendStats
(
startDate
,
endDate
);
}
@Override
public
List
<
RebateRes
>
getRecentRebates
(
Integer
limit
)
{
try
{
LambdaQueryWrapper
<
Rebate
>
wrapper
=
new
LambdaQueryWrapper
<>();
wrapper
.
eq
(
Rebate:
:
getDelFlag
,
"0"
)
.
orderByDesc
(
Rebate:
:
getCreateTime
)
.
last
(
"LIMIT "
+
(
limit
!=
null
?
limit
:
5
));
List
<
Rebate
>
rebates
=
list
(
wrapper
);
return
rebates
.
stream
().
map
(
rebate
->
{
RebateRes
rebateRes
=
new
RebateRes
();
BeanUtils
.
copyProperties
(
rebate
,
rebateRes
);
return
rebateRes
;
}).
collect
(
Collectors
.
toList
());
}
catch
(
Exception
e
)
{
log
.
error
(
"获取最近返利记录失败"
,
e
);
return
new
ArrayList
<>();
}
}
}
...
...
backend/src/main/java/com/apple/erp/service/impl/SessionServiceImpl.java
0 → 100644
View file @
4315aa6
package
com
.
apple
.
erp
.
service
.
impl
;
import
com.apple.erp.service.SessionService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.scheduling.annotation.Scheduled
;
import
org.springframework.stereotype.Service
;
import
java.util.HashMap
;
import
java.util.Map
;
import
java.util.Set
;
/**
* 会话管理服务实现
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Slf4j
@Service
public
class
SessionServiceImpl
implements
SessionService
{
private
static
final
String
SESSION_KEY_PREFIX
=
"user:session:"
;
private
static
final
long
SESSION_TIMEOUT
=
1800
;
// 30分钟
@Autowired
private
RedisTemplate
<
String
,
Object
>
redisTemplate
;
@Override
public
void
storeUserSession
(
String
username
,
Map
<
String
,
Object
>
sessionData
)
{
try
{
String
sessionKey
=
SESSION_KEY_PREFIX
+
username
;
redisTemplate
.
opsForValue
().
set
(
sessionKey
,
sessionData
,
SESSION_TIMEOUT
);
log
.
debug
(
"用户会话已存储: {}"
,
username
);
}
catch
(
Exception
e
)
{
log
.
error
(
"存储用户会话失败: {}"
,
username
,
e
);
}
}
@Override
public
Map
<
String
,
Object
>
getUserSession
(
String
username
)
{
try
{
String
sessionKey
=
SESSION_KEY_PREFIX
+
username
;
Object
sessionData
=
redisTemplate
.
opsForValue
().
get
(
sessionKey
);
if
(
sessionData
instanceof
Map
)
{
return
(
Map
<
String
,
Object
>)
sessionData
;
}
}
catch
(
Exception
e
)
{
log
.
error
(
"获取用户会话失败: {}"
,
username
,
e
);
}
return
new
HashMap
<>();
}
@Override
public
void
removeUserSession
(
String
username
)
{
try
{
String
sessionKey
=
SESSION_KEY_PREFIX
+
username
;
redisTemplate
.
delete
(
sessionKey
);
log
.
debug
(
"用户会话已删除: {}"
,
username
);
}
catch
(
Exception
e
)
{
log
.
error
(
"删除用户会话失败: {}"
,
username
,
e
);
}
}
@Override
public
Integer
getOnlineUserCount
()
{
try
{
String
pattern
=
SESSION_KEY_PREFIX
+
"*"
;
Set
<
String
>
sessionKeys
=
redisTemplate
.
keys
(
pattern
);
if
(
sessionKeys
!=
null
)
{
int
onlineCount
=
sessionKeys
.
size
();
log
.
debug
(
"当前在线用户数: {}"
,
onlineCount
);
return
onlineCount
;
}
else
{
log
.
warn
(
"Redis中未找到用户会话数据"
);
return
0
;
}
}
catch
(
Exception
e
)
{
log
.
error
(
"获取在线用户数失败"
,
e
);
return
0
;
}
}
@Override
@Scheduled
(
fixedRate
=
300000
)
// 每5分钟执行一次
public
void
cleanupExpiredSessions
()
{
try
{
String
pattern
=
SESSION_KEY_PREFIX
+
"*"
;
Set
<
String
>
sessionKeys
=
redisTemplate
.
keys
(
pattern
);
if
(
sessionKeys
!=
null
)
{
int
cleanedCount
=
0
;
for
(
String
sessionKey
:
sessionKeys
)
{
// Redis会自动清理过期的key,这里可以添加额外的清理逻辑
// 比如检查会话的最后活动时间等
cleanedCount
++;
}
log
.
debug
(
"会话清理完成,检查了 {} 个会话"
,
cleanedCount
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"清理过期会话失败"
,
e
);
}
}
}
backend/src/main/java/com/apple/erp/service/impl/SysMenuServiceImpl.java
View file @
4315aa6
...
...
@@ -11,7 +11,6 @@ import org.springframework.stereotype.Service;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.stream.Collectors
;
/**
* 系统菜单Service实现类
...
...
@@ -77,7 +76,7 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
wrapper
.
eq
(
SysMenu:
:
getDelFlag
,
"0"
);
// 排序:按父菜单ID和排序号升序
wrapper
.
orderByAsc
(
SysMenu:
:
getParentId
,
SysMenu:
:
getSort
);
wrapper
.
orderByAsc
(
SysMenu:
:
getParentId
).
orderByAsc
(
SysMenu:
:
getSort
);
return
wrapper
;
}
...
...
backend/src/main/java/com/apple/erp/util/DictValueConverter.java
View file @
4315aa6
...
...
@@ -198,33 +198,6 @@ public class DictValueConverter {
}
}
/**
* 加载指定字典类型到Redis
*/
private
static
void
loadDictToRedis
(
String
dictType
)
{
if
(
staticDictItemService
==
null
)
{
return
;
}
try
{
// 根据字典类型获取字典项
List
<
SysDictItem
>
dictItems
=
staticDictItemService
.
getDictItemsByType
(
dictType
);
Map
<
String
,
String
>
mapping
=
new
HashMap
<>();
for
(
SysDictItem
item
:
dictItems
)
{
if
(
item
.
getDelFlag
()
==
null
||
"0"
.
equals
(
item
.
getDelFlag
()))
{
mapping
.
put
(
item
.
getDictValue
(),
item
.
getDictLabel
());
}
}
// 存储到Redis
String
cacheKey
=
DICT_CACHE_PREFIX
+
dictType
;
staticRedisTemplate
.
opsForValue
().
set
(
cacheKey
,
mapping
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"加载字典到Redis失败: "
+
e
.
getMessage
());
}
}
/**
* 根据字典类型ID获取字典类型编码
...
...
backend/src/main/resources/mapper/ExceptionWorkorderMapper.xml
View file @
4315aa6
...
...
@@ -150,4 +150,76 @@
AND del_flag = '0'
</update>
<!-- 获取异常工单列表用于导出 -->
<select
id=
"selectExceptionWorkorderListForExport"
resultType=
"com.apple.erp.dto.ExceptionWorkorderRes"
>
SELECT
ew.workorder_id,
ew.workorder_no,
ew.order_no,
ew.dealer_code,
ew.dealer_name,
ew.exception_type,
CASE ew.exception_type
WHEN 1 THEN '逻辑验证异常'
WHEN 2 THEN '源头验证异常'
WHEN 3 THEN '交叉验证异常'
ELSE '未知类型'
END AS exception_type_name,
ew.severity_level,
CASE ew.severity_level
WHEN 1 THEN '高'
WHEN 2 THEN '中'
WHEN 3 THEN '低'
ELSE '未知'
END AS severity_level_name,
ew.workorder_status,
CASE ew.workorder_status
WHEN 1 THEN '待处理'
WHEN 2 THEN '处理中'
WHEN 3 THEN '已解决'
WHEN 4 THEN '已关闭'
ELSE '未知状态'
END AS workorder_status_name,
ew.exception_desc,
ew.handler_user,
ew.create_by,
ew.create_time,
ew.update_time
FROM t_exception_workorder ew
<where>
ew.del_flag = '0'
<if
test=
"query.workorderNo != null and query.workorderNo != ''"
>
AND ew.workorder_no LIKE CONCAT('%', #{query.workorderNo}, '%')
</if>
<if
test=
"query.orderNo != null and query.orderNo != ''"
>
AND ew.order_no LIKE CONCAT('%', #{query.orderNo}, '%')
</if>
<if
test=
"query.dealerCode != null and query.dealerCode != ''"
>
AND ew.dealer_code = #{query.dealerCode}
</if>
<if
test=
"query.dealerName != null and query.dealerName != ''"
>
AND ew.dealer_name LIKE CONCAT('%', #{query.dealerName}, '%')
</if>
<if
test=
"query.workorderStatus != null"
>
AND ew.workorder_status = #{query.workorderStatus}
</if>
<if
test=
"query.exceptionType != null"
>
AND ew.exception_type = #{query.exceptionType}
</if>
<if
test=
"query.severityLevel != null"
>
AND ew.severity_level = #{query.severityLevel}
</if>
<if
test=
"query.handlerUser != null and query.handlerUser != ''"
>
AND ew.handler_user LIKE CONCAT('%', #{query.handlerUser}, '%')
</if>
<if
test=
"query.startTime != null"
>
AND ew.create_time >= #{query.startTime}
</if>
<if
test=
"query.endTime != null"
>
AND ew.create_time
<
= #{query.endTime}
</if>
</where>
ORDER BY ew.create_time DESC
</select>
</mapper>
...
...
frontend/src/api/dashboard.ts
0 → 100644
View file @
4315aa6
import
{
request
}
from
'@/utils/request'
/**
* 首页统计数据响应接口
*/
export
interface
DashboardStats
{
todayOrderCount
:
number
pendingDeliveryCount
:
number
pendingInvoiceCount
:
number
todaySalesAmount
:
number
onlineUserCount
:
number
systemStatus
:
string
systemVersion
:
string
orderGrowthRate
:
string
salesGrowthRate
:
string
}
export
interface
RecentActivity
{
id
:
number
type
:
string
title
:
string
time
:
string
}
/**
* 首页API
*/
export
const
dashboardApi
=
{
/**
* 获取首页统计数据
*/
getDashboardStats
:
():
Promise
<
DashboardStats
>
=>
{
return
request
.
get
(
'/api/dashboard/stats'
)
},
/**
* 获取最近活动
*/
getRecentActivities
:
():
Promise
<
RecentActivity
[]
>
=>
{
return
request
.
get
(
'/api/dashboard/activities'
)
},
/**
* 获取测试数据
*/
getTestStats
:
():
Promise
<
DashboardStats
>
=>
{
return
request
.
get
(
'/api/dashboard/test'
)
}
}
export
default
dashboardApi
frontend/src/api/exceptionWorkorder.ts
View file @
4315aa6
...
...
@@ -146,6 +146,13 @@ export const exceptionWorkorderApi = {
// 获取异常工单统计信息
getExceptionWorkorderStats
:
()
=>
{
return
request
.
get
(
'/api/exception-workorder/stats'
)
},
// 导出异常工单数据
exportExceptionWorkorders
:
(
params
:
ExceptionWorkorderQueryReq
)
=>
{
return
request
.
post
(
'/api/exception-workorder/export'
,
params
,
{
responseType
:
'blob'
})
}
}
...
...
frontend/src/api/rebate.ts
View file @
4315aa6
...
...
@@ -32,6 +32,8 @@ export interface RebateSearchParams {
productCode
?:
string
operateType
?:
number
calcFlag
?:
number
auditStatus
?:
number
dataSource
?:
string
rebateStartDate
?:
string
rebateEndDate
?:
string
}
...
...
frontend/src/views/dashboard/index.vue
View file @
4315aa6
<template>
<div class="dashboard-container">
<div class="dashboard-header">
<h2>系统概览</h2>
<p v-if="loading">正在加载用户信息...</p>
<p v-else>欢迎回来,{{ userInfo?.username || '未知' }}!</p>
<!-- 欢迎区域 -->
<div class="welcome-section">
<div class="welcome-content">
<h1 class="welcome-title">欢迎使用 Apple经销商ERP系统</h1>
<p class="welcome-subtitle" v-if="loading">正在加载用户信息...</p>
<p class="welcome-subtitle" v-else>您好,{{ userInfo?.username || userInfo?.realName || '用户' }}!今天是 {{ currentDate }}</p>
</div>
<div class="user-info">
<div class="user-avatar">
<span>{{ getUserInitials() }}</span>
</div>
<div class="user-details">
<div class="user-name">{{ userInfo?.username || userInfo?.realName || '未知用户' }}</div>
<div class="user-role">{{ getRoleName(userInfo) || '普通用户' }}</div>
</div>
</div>
</div>
<div class="dashboard-stats">
<div class="stat-card">
<div class="stat-icon">👤</div>
<!-- 核心业务统计 -->
<div class="stats-section">
<h2 class="section-title">核心业务概览</h2>
<div class="stats-grid">
<div class="stat-card primary">
<div class="stat-icon">📦</div>
<div class="stat-content">
<h3>用户总数</h3>
<p class="stat-number">1,234</p>
<div class="stat-label">今日订单</div>
<div class="stat-value">{{ statsLoading ? '...' : (todayStats.todayOrderCount || 0) }}</div>
<div class="stat-change positive">{{ todayStats.orderGrowthRate || '+12.5%' }}</div>
</div>
</div>
<div class="stat-card
">
<div class="stat-icon">📈
</div>
<div class="stat-card success
">
<div class="stat-icon">🚚
</div>
<div class="stat-content">
<h3>今日访问</h3>
<p class="stat-number">567</p>
<div class="stat-label">待出库</div>
<div class="stat-value">{{ statsLoading ? '...' : (todayStats.pendingDeliveryCount || 0) }}</div>
<div class="stat-change">需要处理</div>
</div>
</div>
<div class="stat-card
">
<div class="stat-icon">✅
</div>
<div class="stat-card warning
">
<div class="stat-icon">🧾
</div>
<div class="stat-content">
<h3>系统状态</h3>
<p class="stat-number">正常</p>
<div class="stat-label">待开票</div>
<div class="stat-value">{{ statsLoading ? '...' : (todayStats.pendingInvoiceCount || 0) }}</div>
<div class="stat-change">待处理</div>
</div>
</div>
<div class="stat-card
">
<div class="stat-icon">⏱️
</div>
<div class="stat-card info
">
<div class="stat-icon">💰
</div>
<div class="stat-content">
<h3>运行时间</h3>
<p class="stat-number">{{ currentTime }}</p>
<div class="stat-label">今日销售额</div>
<div class="stat-value">¥{{ statsLoading ? '...' : formatAmount(todayStats.todaySalesAmount || 0) }}</div>
<div class="stat-change positive">{{ todayStats.salesGrowthRate || '+8.2%' }}</div>
</div>
</div>
</div>
</div>
<div class="dashboard-content">
<div class="dashboard-card">
<h3>系统信息</h3>
<div class="info-grid">
<!-- 主要内容区域 -->
<div class="main-content">
<!-- 快速操作 -->
<div class="quick-actions-card">
<h3 class="card-title">快速操作</h3>
<div class="actions-grid">
<button class="action-btn primary" @click="navigateTo('/main/order')">
<div class="action-icon">📋</div>
<div class="action-text">
<div class="action-title">订单管理</div>
<div class="action-desc">查看和管理订单</div>
</div>
</button>
<button class="action-btn success" @click="navigateTo('/main/delivery')">
<div class="action-icon">🚚</div>
<div class="action-text">
<div class="action-title">出库管理</div>
<div class="action-desc">处理出库业务</div>
</div>
</button>
<button class="action-btn warning" @click="navigateTo('/main/invoice')">
<div class="action-icon">🧾</div>
<div class="action-text">
<div class="action-title">发票管理</div>
<div class="action-desc">开具和管理发票</div>
</div>
</button>
<button class="action-btn info" @click="navigateTo('/main/rebate')">
<div class="action-icon">💰</div>
<div class="action-text">
<div class="action-title">返利管理</div>
<div class="action-desc">返利计算和发放</div>
</div>
</button>
<button class="action-btn secondary" @click="navigateTo('/main/product')">
<div class="action-icon">📱</div>
<div class="action-text">
<div class="action-title">产品管理</div>
<div class="action-desc">管理产品信息</div>
</div>
</button>
<button class="action-btn secondary" @click="navigateTo('/main/dealer')">
<div class="action-icon">🏢</div>
<div class="action-text">
<div class="action-title">经销商管理</div>
<div class="action-desc">管理经销商信息</div>
</div>
</button>
</div>
</div>
<!-- 系统信息 -->
<div class="system-info-card">
<h3 class="card-title">系统信息</h3>
<div class="info-list">
<div class="info-item">
<label>用户名:</label>
<span v-if="loading">加载中...</span>
<span v-else>{{ userInfo?.username || '未知' }}</span>
<div class="info-label">系统版本</div>
<div class="info-value">{{ todayStats.systemVersion }}</div>
</div>
<div class="info-item">
<label>角色:</label>
<span v-if="loading">加载中...</span>
<span v-else>{{ getRoleName(userInfo) || '普通用户' }}</span>
<div class="info-label">当前时间</div>
<div class="info-value">{{ currentTime }}</div>
</div>
<div class="info-item">
<label>登录时间:</label>
<span>{{ currentTime }}</span>
<div class="info-label">系统状态</div>
<div class="info-value" :class="{ 'status-normal': todayStats.systemStatus === '正常运行', 'status-error': todayStats.systemStatus !== '正常运行' }">
{{ todayStats.systemStatus }}
</div>
</div>
<div class="info-item">
<label>系统版本:</label>
<span>v1.0.0</span>
<div class="info-label">在线用户</div>
<div class="info-value">{{ todayStats.onlineUserCount }}</div>
</div>
</div>
</div>
</div>
<div class="dashboard-card">
<h3>快速操作</h3>
<div class="quick-actions">
<button class="action-btn" @click="navigateTo('/main/users')">👤 用户管理</button>
<button class="action-btn" @click="navigateTo('/main/sys/role')">🛡️ 角色管理</button>
<button class="action-btn" @click="navigateTo('/main/settings')">⚙️ 系统设置</button>
<button class="action-btn" @click="navigateTo('/main/sys/log')">📊 查看日志</button>
<!-- 最近活动 -->
<div class="recent-activities">
<h3 class="card-title">最近活动</h3>
<div class="activity-list" v-if="!activitiesLoading">
<div class="activity-item" v-for="activity in recentActivities" :key="activity.id">
<div class="activity-icon" :class="activity.type">
{{ getActivityIcon(activity.type) }}
</div>
<div class="activity-content">
<div class="activity-title">{{ activity.title }}</div>
<div class="activity-time">{{ activity.time }}</div>
</div>
</div>
<div v-if="recentActivities.length === 0" class="no-activities">
暂无最近活动
</div>
</div>
<div v-else class="loading-activities">
正在加载活动数据...
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted
, computed
} from 'vue'
import { useRouter } from 'vue-router'
import { request } from '@/utils/request'
import dashboardApi, { type DashboardStats, type RecentActivity } from '@/api/dashboard'
const router = useRouter()
const currentTime = ref('')
const currentDate = ref('')
const userInfo = ref<any>(null)
const loading = ref(false)
const statsLoading = ref(false)
// 今日统计数据
const todayStats = ref<DashboardStats>({
todayOrderCount: 0,
pendingDeliveryCount: 0,
pendingInvoiceCount: 0,
todaySalesAmount: 0,
onlineUserCount: 0,
systemStatus: '正常运行',
systemVersion: 'v1.0.0',
orderGrowthRate: '+12.5%',
salesGrowthRate: '+8.2%'
})
// 最近活动数据
const recentActivities = ref<RecentActivity[]>([])
const activitiesLoading = ref(false)
// 获取用户姓名首字母
const getUserInitials = () => {
const name = userInfo.value?.username || userInfo.value?.realName || 'U'
return name.charAt(0).toUpperCase()
}
// 获取角色名称
const getRoleName = (userInfo: any) => {
...
...
@@ -119,6 +233,71 @@ const getRoleName = (userInfo: any) => {
return '普通用户'
}
// 格式化金额
const formatAmount = (amount: number) => {
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2 })
}
// 获取活动图标
const getActivityIcon = (type: string) => {
const iconMap: { [key: string]: string } = {
'order': '📋',
'delivery': '🚚',
'invoice': '🧾',
'rebate': '💰',
'system': '⚙️'
}
return iconMap[type] || '📄'
}
// 获取今日统计数据
const fetchTodayStats = async () => {
try {
statsLoading.value = true
console.log('开始获取首页统计数据...')
const response = await dashboardApi.getDashboardStats()
todayStats.value = response
console.log('首页统计数据获取成功:', response)
} catch (error: any) {
console.error('获取统计数据失败:', error)
console.error('错误详情:', error?.message)
// 如果API调用失败,使用默认值
todayStats.value = {
todayOrderCount: 0,
pendingDeliveryCount: 0,
pendingInvoiceCount: 0,
todaySalesAmount: 0,
onlineUserCount: 0,
systemStatus: '系统异常',
systemVersion: 'v1.0.0',
orderGrowthRate: '0%',
salesGrowthRate: '0%'
}
} finally {
statsLoading.value = false
}
}
// 获取最近活动
const fetchRecentActivities = async () => {
try {
activitiesLoading.value = true
console.log('开始获取最近活动数据...')
const response = await dashboardApi.getRecentActivities()
recentActivities.value = response
console.log('最近活动数据获取成功:', response)
} catch (error: any) {
console.error('获取最近活动失败:', error)
console.error('错误详情:', error?.message)
// 如果API调用失败,使用默认值
recentActivities.value = []
} finally {
activitiesLoading.value = false
}
}
// 获取用户信息
const fetchUserInfo = async () => {
try {
...
...
@@ -138,9 +317,29 @@ const fetchUserInfo = async () => {
}
}
// 更新时间
const updateTime = () => {
const now = new Date()
currentTime.value = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
currentDate.value = now.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
})
}
onMounted(() => {
// 获取当前时间
currentTime.value = new Date().toLocaleString()
// 更新当前时间
updateTime()
setInterval(updateTime, 1000)
// 检查是否已登录
const token = localStorage.getItem('token')
...
...
@@ -149,8 +348,10 @@ onMounted(() => {
return
}
// 获取用户信息
// 获取用户信息
、统计数据和最近活动
fetchUserInfo()
fetchTodayStats()
fetchRecentActivities()
})
// 页面跳转函数
...
...
@@ -170,151 +371,449 @@ const logout = () => {
}
</script>
<style scoped>
<style scoped
lang="scss"
>
.dashboard-container {
padding: 2
0
px;
padding: 2
4
px;
min-height: 100vh;
background: #f5f7fa;
}
.dashboard-header {
margin-bottom: 30px;
}
.dashboard-header h2 {
font-size: 20px;
color: #333;
// 欢迎区域
.welcome-section {
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 32px;
border-radius: 12px;
margin-bottom: 32px;
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
.welcome-content {
.welcome-title {
font-size: 28px;
font-weight: 700;
margin: 0 0 8px 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.dashboard-header p
{
color: #666
;
.welcome-subtitle
{
font-size: 16px
;
margin: 0;
opacity: 0.9;
}
}
.user-info {
display: flex;
align-items: center;
gap: 16px;
.user-avatar {
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
backdrop-filter: blur(10px);
}
.user-details {
.user-name {
font-size: 18px;
font-weight: 600;
margin-bottom: 4px;
}
.user-role {
font-size: 14px;
opacity: 0.8;
}
}
}
}
.dashboard-stats {
// 统计区域
.stats-section {
margin-bottom: 32px;
.section-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin: 0 0 20px 0;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20
0px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(25
0px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
}
.stat-card {
background: white;
padding: 20
px;
border-radius: 8
px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1
);
padding: 24
px;
border-radius: 12
px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08
);
display: flex;
align-items: center;
gap: 15
px;
transition: transform
0.3s ease;
}
gap: 20
px;
transition: all
0.3s ease;
border-left: 4px solid;
.stat-card:hover {
transform: translateY(-2px);
}
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
}
.stat-icon {
font-size: 20px;
width: 32px;
height: 32px;
&.primary {
border-left-color: #3498db;
}
&.success {
border-left-color: #2ecc71;
}
&.warning {
border-left-color: #f39c12;
}
&.info {
border-left-color: #9b59b6;
}
.stat-icon {
font-size: 32px;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
background: #f8f9fa
;
border-radius: 6px
;
}
border-radius: 12px
;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)
;
}
.stat-content h3 {
margin: 0 0 5px 0;
font-size: 12px;
.stat-content {
flex: 1;
.stat-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
font-weight: 500;
}
}
.stat-number {
margin: 0;
font-size: 18px;
font-weight: bold;
.stat-value {
font-size: 28px;
font-weight: 700;
color: #333;
margin-bottom: 4px;
}
.stat-change {
font-size: 12px;
font-weight: 500;
&.positive {
color: #2ecc71;
}
&:not(.positive) {
color: #666;
}
}
}
}
}
.dashboard-content {
// 主要内容区域
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
grid-template-columns: 2fr 1fr;
gap: 24px;
margin-bottom: 32px;
}
.dashboard-card {
// 快速操作卡片
.quick-actions-card {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
.dashboard-card h3 {
margin: 0 0 16px 0;
.card-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0 0 20px 0;
}
.actions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.action-btn {
background: white;
border: 2px solid #e9ecef;
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 16px;
text-align: left;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
&.primary {
border-color: #3498db;
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
color: white;
&:hover {
background: linear-gradient(135deg, #2980b9 0%, #1f618d 100%);
}
}
&.success {
border-color: #2ecc71;
background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%);
color: white;
&:hover {
background: linear-gradient(135deg, #27ae60 0%, #229954 100%);
}
}
&.warning {
border-color: #f39c12;
background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%);
color: white;
&:hover {
background: linear-gradient(135deg, #e67e22 0%, #d35400 100%);
}
}
&.info {
border-color: #9b59b6;
background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%);
color: white;
&:hover {
background: linear-gradient(135deg, #8e44ad 0%, #7d3c98 100%);
}
}
&.secondary {
border-color: #95a5a6;
background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
color: white;
&:hover {
background: linear-gradient(135deg, #7f8c8d 0%, #6c7b7d 100%);
}
}
.action-icon {
font-size: 24px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 8px;
}
.action-text {
.action-title {
font-size: 16px;
font-weight: 600;
}
margin-bottom: 4px;
}
.info-grid {
display: grid;
gap: 12px;
.action-desc {
font-size: 12px;
opacity: 0.8;
}
}
}
}
// 系统信息卡片
.system-info-card {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
.card-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0 0 20px 0;
}
.info-list {
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8
px 0;
padding: 12
px 0;
border-bottom: 1px solid #f0f0f0;
}
.info-item
:last-child {
&
:last-child {
border-bottom: none;
}
.info-item
label {
.info-
label {
font-weight: 500;
color: #666;
}
font-size: 14px;
}
.info-item span
{
.info-value
{
color: #333;
}
font-weight: 500;
.quick-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
&.status-normal {
color: #2ecc71;
}
&.status-error {
color: #e74c3c;
}
}
}
}
}
.action-btn {
background: #3498db;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
// 最近活动
.recent-activities {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
.card-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0 0 20px 0;
}
.activity-list {
.activity-item {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.activity-icon {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
&.order {
background: #e3f2fd;
color: #1976d2;
}
&.delivery {
background: #e8f5e8;
color: #2e7d32;
}
&.invoice {
background: #fff3e0;
color: #f57c00;
}
&.rebate {
background: #f3e5f5;
color: #7b1fa2;
}
&.system {
background: #f1f8e9;
color: #558b2f;
}
}
.activity-content {
flex: 1;
.activity-title {
font-size: 14px;
color: #333;
margin-bottom: 4px;
font-weight: 500;
}
.activity-time {
font-size: 12px;
transition: background-color 0.3s;
color: #666;
}
}
}
}
.no-activities, .loading-activities {
text-align: center;
color: #666;
padding: 20px;
font-size: 14px;
}
}
.action-btn:hover {
background: #2980b9;
// 响应式设计
@media (max-width: 1200px) {
.main-content {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.dashboard-stats {
grid-template-columns: 1fr;
.dashboard-container {
padding: 16px;
}
.welcome-section {
flex-direction: column;
text-align: center;
gap: 20px;
.user-info {
justify-content: center;
}
}
.
dashboard-content
{
.
stats-grid
{
grid-template-columns: 1fr;
}
.quick-actions
{
.actions-grid
{
grid-template-columns: 1fr;
}
}
...
...
frontend/src/views/exceptionWorkorder/index.vue
View file @
4315aa6
...
...
@@ -734,8 +734,38 @@ const handleBatchProcess = () => {
}
// 导出
const handleExport = () => {
ElMessage.info('导出功能开发中...')
const handleExport = async () => {
try {
await ElMessageBox.confirm('确定要导出异常工单数据吗?', '确认导出', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
ElMessage.info('正在导出数据,请稍候...')
const response = await exceptionWorkorderApi.exportExceptionWorkorders(searchParams.value)
// 创建下载链接
const blob = new Blob([response as unknown as BlobPart], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `异常工单数据_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
} catch (error: any) {
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败: ' + (error?.message || '未知错误'))
}
}
}
// 提交新增
...
...
frontend/src/views/rebate/index.vue
View file @
4315aa6
...
...
@@ -2,51 +2,64 @@
<div class="rebate-page">
<!-- 搜索区域 -->
<div class="search-section">
<div class="search-form">
<div class="search-row">
<div class="search-item">
<span class="search-label">经销商名称</span
>
<
el-input
<label>经销商名称:</label
>
<
input
v-model="searchForm.dealerName"
class="search-input"
placeholder="请输入经销商名称"
clearable
size="small"
style="width: 160px"
/>
</div>
<div class="search-item">
<span class="search-label">返利编号</span
>
<
el-input
<label>返利编号:</label
>
<
input
v-model="searchForm.rebateNo"
class="search-input"
placeholder="请输入返利编号"
clearable
size="small"
style="width: 160px"
/>
</div>
<div class="search-item">
<span class="search-label">日期</span>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
size="small"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@change="handleDateRangeChange"
style="width: 240px"
/>
<label>操作类型:</label>
<select v-model="searchForm.operateType" class="search-select">
<option value="">所有</option>
<option value="1">新增</option>
<option value="2">修改</option>
<option value="3">删除</option>
</select>
</div>
<div class="search-actions">
<el-button type="primary" size="small" @click="handleSearch" :loading="loading">
查询
</el-button>
<el-button size="small" @click="handleReset">
重置
</el-button>
<div class="search-item">
<label>计算状态:</label>
<select v-model="searchForm.calcFlag" class="search-select">
<option value="">所有</option>
<option value="0">未计算</option>
<option value="1">已计算</option>
</select>
</div>
</div>
<div class="search-row">
<div class="search-item">
<label>审核状态:</label>
<select v-model="searchForm.auditStatus" class="search-select">
<option value="">所有</option>
<option value="0">待审核</option>
<option value="1">审核通过</option>
<option value="2">审核中</option>
<option value="3">审核拒绝</option>
</select>
</div>
<div class="search-item">
<label>数据来源:</label>
<select v-model="searchForm.dataSource" class="search-select">
<option value="">所有</option>
<option value="ERP系统">ERP系统</option>
<option value="手工录入">手工录入</option>
<option value="API导入">API导入</option>
</select>
</div>
<div class="search-actions">
<button @click="handleSearch" class="search-btn" :disabled="loading">🔍 搜索</button>
<button @click="handleReset" class="reset-btn">🔄 重置</button>
</div>
</div>
</div>
...
...
@@ -96,91 +109,99 @@
</div>
</div>
<!-- 操作按钮区域 -->
<div class="action-section">
<div class="action-buttons">
<button @click="handleExport" class="action-btn secondary" :disabled="exportLoading">
📋 导出
</button>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="table-title">
返利记录
<div class="table-controls">
<button @click="handleTableSearch" class="control-btn">🔍</button>
<button @click="handleTableRefresh" class="control-btn">🔄</button>
<button @click="handleTableExport" class="control-btn">📋</button>
<button @click="handleTableViewToggle" class="control-btn">⊞</button>
</div>
<div class="table-actions">
<el-button type="primary" size="small" @click="handleExport" :loading="exportLoading">
<el-icon><Download /></el-icon>
导出
</el-button>
<div class="table-info">
数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }}
</div>
<div class="table-container">
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner">加载中...</div>
</div>
<table class="data-table">
<thead>
<tr>
<th>返利编号</th>
<th>订单编号</th>
<th>经销商代码</th>
<th>经销商名称</th>
<th>产品编码</th>
<th>返利金额</th>
<th>返利日期</th>
<th>操作类型</th>
<th>计算状态</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-if="rebateList.length === 0">
<td colspan="11" class="empty-data">暂无数据</td>
</tr>
<tr v-for="row in rebateList" :key="row.rebateId">
<td>{{ row.rebateNo }}</td>
<td>{{ row.orderNo }}</td>
<td>{{ row.dealerCode }}</td>
<td>{{ row.dealerName }}</td>
<td>{{ row.productCode }}</td>
<td class="amount-text">{{ formatAmount(row.rebateAmount) }}</td>
<td>{{ row.rebateDate }}</td>
<td>
<span :class="getOperateTypeClass(row.operateType || 0)">
{{ row.operateTypeText || getOperateTypeText(row.operateType || 0) }}
</span>
</td>
<td>
<span :class="getCalcFlagClass(row.calcFlag || 0)">
{{ row.calcFlagText || getCalcFlagText(row.calcFlag || 0) }}
</span>
</td>
<td>{{ formatDate(row.updateTime || '') }}</td>
<td>
<button @click="handleViewDetail(row)" class="action-link">详情</button>
</td>
</tr>
</tbody>
</table>
</div>
<el-table
v-loading="loading"
:data="rebateList"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 'normal' }"
:empty-text="rebateList.length === 0 ? '暂无数据' : ''"
size="small"
>
<el-table-column prop="rebateNo" label="返利编号" width="140" align="center" />
<el-table-column prop="orderNo" label="订单编号" width="140" align="center" />
<el-table-column prop="dealerCode" label="经销商代码" width="120" align="center" />
<el-table-column prop="dealerName" label="经销商名称" min-width="150" />
<el-table-column prop="productCode" label="产品编码" width="120" align="center" />
<el-table-column prop="rebateAmount" label="返利金额" width="120" align="right">
<template #default="{ row }">
<span class="amount-text">{{ formatAmount(row.rebateAmount) }}</span>
</template>
</el-table-column>
<el-table-column prop="rebateDate" label="返利日期" width="120" align="center" />
<el-table-column prop="operateTypeText" label="操作类型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getOperateTypeTagType(row.operateType)" size="small">
{{ row.operateTypeText || getOperateTypeText(row.operateType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="calcFlagText" label="计算状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getCalcFlagTagType(row.calcFlag)" size="small">
{{ row.calcFlagText || getCalcFlagText(row.calcFlag) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="150" align="center">
<template #default="{ row }">
{{ formatDate(row.updateTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="{ row }">
<el-button type="primary" size="small" link @click="handleViewDetail(row)">
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<div class="pagination-info">
共 {{ pagination.total }} 条,{{ pagination.pageSize }}/页
</div>
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
small
/>
<div class="pagination-controls">
<button @click="handlePrevPage" :disabled="pagination.pageNum <= 1" class="page-btn">‹</button>
<button
v-for="page in getPageNumbers()"
:key="page"
@click="handlePageChange(page)"
:class="['page-btn', { active: page === pagination.pageNum }]"
>
{{ page }}
</button>
<button @click="handleNextPage" :disabled="pagination.pageNum >= getTotalPages()" class="page-btn">›</button>
</div>
<div class="pagination-jump">
前往
<
el-
input
<input
v-model="jumpPage"
size="small"
style="width: 50px; margin: 0 8px;"
class="jump-input"
@keyup.enter="handleJumpPage"
/>
页
...
...
@@ -435,6 +456,8 @@ const searchForm = reactive<RebateSearchParams>({
productCode: '',
operateType: undefined,
calcFlag: undefined,
auditStatus: undefined,
dataSource: '',
rebateStartDate: '',
rebateEndDate: ''
})
...
...
@@ -730,15 +753,17 @@ const handleReset = () => {
Object.assign(searchForm, {
pageNum: 1,
pageSize: 10,
rebateNo: undefined,
dealerName: undefined,
productName: undefined,
rebateType: undefined,
status: undefined,
applyStartTime: undefined,
applyEndTime: undefined
rebateNo: '',
dealerCode: '',
dealerName: '',
productCode: '',
operateType: undefined,
calcFlag: undefined,
auditStatus: undefined,
dataSource: '',
rebateStartDate: '',
rebateEndDate: ''
})
applyDateRange.value = null
pagination.pageNum = 1
getRebateList()
}
...
...
@@ -748,6 +773,104 @@ const handleRefresh = () => {
getRebateList()
}
// 表格控制按钮
const handleTableSearch = () => {
handleSearch()
}
const handleTableRefresh = () => {
handleRefresh()
}
const handleTableExport = () => {
handleExport()
}
const handleTableViewToggle = () => {
// 切换视图模式
console.log('切换视图模式')
}
// 分页控制
const handlePrevPage = () => {
if (pagination.pageNum > 1) {
pagination.pageNum--
getRebateList()
}
}
const handleNextPage = () => {
if (pagination.pageNum < getTotalPages()) {
pagination.pageNum++
getRebateList()
}
}
const handlePageChange = (page: number | string) => {
if (typeof page === 'number') {
pagination.pageNum = page
getRebateList()
}
}
const getPageNumbers = () => {
const totalPages = getTotalPages()
const current = pagination.pageNum
const pages = []
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i)
}
} else {
if (current <= 4) {
for (let i = 1; i <= 5; i++) {
pages.push(i)
}
pages.push('...')
pages.push(totalPages)
} else if (current >= totalPages - 3) {
pages.push(1)
pages.push('...')
for (let i = totalPages - 4; i <= totalPages; i++) {
pages.push(i)
}
} else {
pages.push(1)
pages.push('...')
for (let i = current - 1; i <= current + 1; i++) {
pages.push(i)
}
pages.push('...')
pages.push(totalPages)
}
}
return pages
}
const getTotalPages = () => {
return Math.ceil(pagination.total / pagination.pageSize)
}
// 状态样式类
const getOperateTypeClass = (type: number) => {
const classes = {
1: 'status-tag success',
2: 'status-tag warning',
3: 'status-tag danger'
}
return classes[type as keyof typeof classes] || 'status-tag'
}
const getCalcFlagClass = (flag: number) => {
const classes = {
0: 'status-tag warning',
1: 'status-tag success'
}
return classes[flag as keyof typeof classes] || 'status-tag'
}
// 分页变化
const handleSizeChange = (size: number) => {
pagination.pageSize = size
...
...
@@ -784,11 +907,11 @@ const handleViewDetail = async (row: Rebate) => {
const response = await rebateApi.getRebateById(row.rebateId)
console.log('详情接口响应:', response)
// 根据request.ts拦截器的处理,response
.data已经是实际数据
if (response
&& response.data
) {
rebateDetail.value = response
.data
// 根据request.ts拦截器的处理,response
直接就是数据对象
if (response) {
rebateDetail.value = response
as unknown as Rebate
detailDialogVisible.value = true
console.log('详情数据:', response
.data
)
console.log('详情数据:', response)
} else {
ElMessage.error('获取返利详情失败')
}
...
...
@@ -1478,23 +1601,57 @@ onMounted(async () => {
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
.search-form {
.search-row {
display: flex;
align-items: center;
gap: 24px;
flex-wrap: wrap;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.search-item {
display: flex;
align-items: center;
gap: 8px;
.search-
label {
label {
font-size: 14px;
color: #333;
white-space: nowrap;
min-width: 70px;
min-width: 80px;
}
.search-input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
width: 160px;
transition: border-color 0.3s;
&:focus {
outline: none;
border-color: #409eff;
}
}
.search-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
width: 120px;
background: white;
cursor: pointer;
transition: border-color 0.3s;
&:focus {
outline: none;
border-color: #409eff;
}
}
}
...
...
@@ -1502,6 +1659,96 @@ onMounted(async () => {
margin-left: auto;
display: flex;
gap: 8px;
.search-btn, .reset-btn {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
background: white;
&:hover {
background: #f5f7fa;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.search-btn {
background: #409eff;
color: white;
border-color: #409eff;
&:hover {
background: #66b1ff;
}
}
}
}
}
.action-section {
background: white;
padding: 12px 20px;
margin-bottom: 16px;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
.action-buttons {
display: flex;
gap: 12px;
.action-btn {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
background: white;
&:hover {
background: #f5f7fa;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
&.primary {
background: #409eff;
color: white;
border-color: #409eff;
&:hover {
background: #66b1ff;
}
}
&.secondary {
background: #67c23a;
color: white;
border-color: #67c23a;
&:hover {
background: #85ce61;
}
}
&.danger {
background: #f56c6c;
color: white;
border-color: #f56c6c;
&:hover {
background: #f78989;
}
}
}
}
...
...
@@ -1580,55 +1827,107 @@ onMounted(async () => {
.table-section {
background: white;
border-radius:
8
px;
box-shadow: 0
2px 8
px rgba(0, 0, 0, 0.1);
border-radius:
4
px;
box-shadow: 0
1px 4
px rgba(0, 0, 0, 0.1);
overflow: hidden;
.table-header {
display: flex;
justify-content:
space-between
;
justify-content:
flex-end
;
align-items: center;
padding: 20px 20px 0 20px;
margin-bottom: 16px;
padding: 12px 20px;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
.table-title {
font-size: 16px;
font-weight: 500;
color: #333;
.table-controls {
display: flex;
gap: 8px;
.control-btn {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
&:hover {
background: #f5f7fa;
border-color: #409eff;
}
}
}
}
.table-container {
position: relative;
.table-actions {
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
z-index: 10;
.table-info {
font-size: 12px;
color: #999;
}
.loading-spinner {
font-size: 14px;
color: #666;
}
}
.el-table {
border: none;
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
thead {
background: #fafafa;
:deep(.el-table__header) {
th {
background-color: #fafafa
;
border: none
;
padding: 12px 16px
;
text-align: left
;
font-weight: 500;
color: #333;
border-bottom: 1px solid #e0e0e0;
white-space: nowrap;
}
}
:deep(.el-table__body) {
tr:hover > td {
background-color: #f5f7fa;
tbody {
tr {
transition: background-color 0.3s;
&:hover {
background: #f5f7fa;
}
&:nth-child(even) {
background: #fafafa;
}
&:nth-child(even):hover {
background: #f0f2f5;
}
td {
border: none
;
padding: 12px 16px
;
border-bottom: 1px solid #f0f0f0;
vertical-align: middle;
&.empty-data {
text-align: center;
color: #999;
font-style: italic;
padding: 40px;
}
}
}
}
}
}
...
...
@@ -1646,15 +1945,100 @@ onMounted(async () => {
color: #666;
}
.pagination-controls {
display: flex;
gap: 4px;
align-items: center;
.page-btn {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
min-width: 32px;
&:hover:not(:disabled) {
background: #f5f7fa;
border-color: #409eff;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&.active {
background: #409eff;
color: white;
border-color: #409eff;
}
}
}
.pagination-jump {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #666;
.jump-input {
width: 50px;
padding: 4px 8px;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
font-size: 14px;
&:focus {
outline: none;
border-color: #409eff;
}
}
}
}
}
.status-tag {
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
&.success {
background: #f0f9ff;
color: #67c23a;
}
&.warning {
background: #fdf6ec;
color: #e6a23c;
}
&.danger {
background: #fef0f0;
color: #f56c6c;
}
}
.action-link {
color: #409eff;
text-decoration: none;
cursor: pointer;
font-size: 14px;
background: none;
border: none;
padding: 0;
&:hover {
color: #66b1ff;
text-decoration: underline;
}
}
.amount-text {
color: #f56c6c;
font-weight: 500;
...
...
Please
register
or
login
to post a comment