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
jiaxing.zhou
2025-09-29 15:05:50 +0800
Browse Files
Options
Browse Files
Download
Plain Diff
Commit
db7a8768851e9ba8ad97fb75d0f8621d34e2eeb0
db7a8768
2 parents
3fb42b2a
e5f29769
Merge remote-tracking branch 'origin/dev'
Show whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
2337 additions
and
284 deletions
API接口文档.md
backend/src/main/java/com/apple/erp/config/DictConfig.java
backend/src/main/java/com/apple/erp/config/ExportConfig.java
backend/src/main/java/com/apple/erp/controller/AuthController.java
backend/src/main/java/com/apple/erp/controller/DeliveryMainController.java
backend/src/main/java/com/apple/erp/controller/InvoiceMainController.java
backend/src/main/java/com/apple/erp/controller/OrderMainController.java
backend/src/main/java/com/apple/erp/controller/RebateController.java
backend/src/main/java/com/apple/erp/controller/SysDictItemController.java
backend/src/main/java/com/apple/erp/dto/request/RebateQueryReq.java
backend/src/main/java/com/apple/erp/dto/response/UserInfoRes.java
backend/src/main/java/com/apple/erp/service/ExcelExportService.java
backend/src/main/java/com/apple/erp/util/DictValueConverter.java
backend/src/main/java/com/apple/erp/util/ExcelExportUtil.java
backend/src/main/java/com/apple/erp/util/GenericExcelExportUtil.java
frontend/auto-imports.d.ts
frontend/components.d.ts
frontend/src/api/delivery.ts
frontend/src/api/dict.ts
frontend/src/api/invoice.ts
frontend/src/api/order.ts
frontend/src/api/rebate.ts
frontend/src/layouts/MainLayout.vue
frontend/src/main.ts
frontend/src/utils/request.ts
frontend/src/views/dashboard/index.vue
frontend/src/views/delivery/index.vue
frontend/src/views/invoice/index.vue
frontend/src/views/order/index.vue
frontend/src/views/rebate/index.vue
frontend/src/views/settings/index.vue
frontend/tsconfig.json
API接口文档.md
View file @
db7a876
...
...
@@ -103,7 +103,7 @@
**接口路径:**
`GET /api/auth/userinfo`
**功能描述:**
获取当前登录用户的详细信息
**功能描述:**
获取当前登录用户的详细信息
,包括用户名、真实姓名、角色列表和权限列表
**请求头:**
`Authorization: Bearer {token}`
...
...
@@ -114,11 +114,46 @@
"message"
:
"获取用户信息成功"
,
"data"
:
{
"username"
:
"admin"
,
"authorities"
:
[
"ROLE_ADMIN"
]
"realName"
:
"系统管理员"
,
"roles"
:
[
{
"roleId"
:
1
,
"roleCode"
:
"ADMIN"
,
"roleName"
:
"系统管理员"
,
"status"
:
1
,
"statusText"
:
"正常"
,
"remark"
:
"系统管理员角色"
,
"createBy"
:
"admin"
,
"createTime"
:
"2024-01-01T00:00:00"
,
"updateBy"
:
"admin"
,
"updateTime"
:
"2024-01-01T00:00:00"
}
],
"authorities"
:
[
{
"authority"
:
"ROLE_ADMIN"
}
]
}
}
```
**响应字段说明:**
-
`username`
: 用户名
-
`realName`
: 真实姓名
-
`roles`
: 用户角色列表
-
`roleId`
: 角色ID
-
`roleCode`
: 角色编码
-
`roleName`
: 角色名称
-
`status`
: 角色状态(0-停用/1-启用)
-
`statusText`
: 角色状态文本描述
-
`remark`
: 备注
-
`createBy`
: 创建者
-
`createTime`
: 创建时间
-
`updateBy`
: 更新者
-
`updateTime`
: 更新时间
-
`authorities`
: 用户权限列表(Spring Security权限对象)
## 2. 用户管理 (SysUserController)
### 2.1 获取用户列表
...
...
@@ -2296,6 +2331,50 @@
}
```
### 8. 导出发票数据
**接口路径:**
`POST /api/invoice/export`
**请求方法:**
POST
**权限要求:**
`invoice:export`
**请求参数:**
同发票查询接口参数
**响应:**
返回Excel文件流
---
## 八、数据导出接口
### 1. 订单数据导出
**接口路径:**
`POST /order/export`
**请求方法:**
POST
**权限要求:**
`order:export`
**请求参数:**
同订单查询接口参数
**响应:**
返回Excel文件流,文件名格式:
`订单数据_yyyyMMdd_HHmmss.xlsx`
### 2. 出库数据导出
**接口路径:**
`POST /api/delivery/export`
**请求方法:**
POST
**权限要求:**
`delivery:export`
**请求参数:**
同出库查询接口参数
**响应:**
返回Excel文件流,文件名格式:
`出库数据_yyyyMMdd_HHmmss.xlsx`
### 3. 发票数据导出
**接口路径:**
`POST /api/invoice/export`
**请求方法:**
POST
**权限要求:**
`invoice:export`
**请求参数:**
同发票查询接口参数
**响应:**
返回Excel文件流,文件名格式:
`发票数据_yyyyMMdd_HHmmss.xlsx`
---
**文档版本:**
1.0.0
...
...
backend/src/main/java/com/apple/erp/config/DictConfig.java
0 → 100644
View file @
db7a876
package
com
.
apple
.
erp
.
config
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* 字典值配置类
* 集中管理所有字典值转换配置
*
* @author Apple ERP System
* @since 2025-01-01
*/
public
class
DictConfig
{
/**
* 操作类型字典
*/
public
static
final
Map
<
Integer
,
String
>
OPERATE_TYPE
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
1
,
"新增"
);
put
(
2
,
"修改"
);
put
(
3
,
"删除"
);
}};
/**
* 计算状态字典
*/
public
static
final
Map
<
Integer
,
String
>
CALC_FLAG
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
0
,
"未计算"
);
put
(
1
,
"已计算"
);
}};
/**
* 审核状态字典
*/
public
static
final
Map
<
Integer
,
String
>
AUDIT_STATUS
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
0
,
"待审核"
);
put
(
1
,
"审核通过"
);
put
(
2
,
"审核中"
);
put
(
3
,
"审核拒绝"
);
}};
/**
* 出库状态字典
*/
public
static
final
Map
<
Integer
,
String
>
DELIVERY_STATUS
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
0
,
"未出库"
);
put
(
1
,
"已出库"
);
put
(
2
,
"部分出库"
);
}};
/**
* 发票状态字典
*/
public
static
final
Map
<
Integer
,
String
>
INVOICE_STATUS
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
0
,
"未开票"
);
put
(
1
,
"已开票"
);
put
(
2
,
"部分开票"
);
}};
/**
* 返利计算状态字典
*/
public
static
final
Map
<
Integer
,
String
>
REBATE_CALC_FLAG
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
0
,
"未计算"
);
put
(
1
,
"已计算"
);
}};
/**
* 审核状态字典
*/
public
static
final
Map
<
Integer
,
String
>
VERIFY_STATUS
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
0
,
"待审核"
);
put
(
1
,
"审核通过"
);
put
(
2
,
"审核中"
);
put
(
3
,
"审核拒绝"
);
}};
/**
* 工单状态字典
*/
public
static
final
Map
<
Integer
,
String
>
WORKORDER_STATUS
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
0
,
"待处理"
);
put
(
1
,
"处理中"
);
put
(
2
,
"已完成"
);
put
(
3
,
"已关闭"
);
}};
/**
* 严重程度字典
*/
public
static
final
Map
<
Integer
,
String
>
SEVERITY_LEVEL
=
new
HashMap
<
Integer
,
String
>()
{{
put
(
1
,
"低"
);
put
(
2
,
"中"
);
put
(
3
,
"高"
);
put
(
4
,
"紧急"
);
}};
}
backend/src/main/java/com/apple/erp/config/ExportConfig.java
0 → 100644
View file @
db7a876
package
com
.
apple
.
erp
.
config
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* 导出配置类
* 定义各模块的导出字段映射配置
*
* @author Apple ERP System
* @since 2025-01-01
*/
public
class
ExportConfig
{
/**
* 订单导出配置
*/
public
static
final
Map
<
String
,
String
[]>
ORDER_EXPORT_CONFIG
=
new
HashMap
<
String
,
String
[]>()
{{
put
(
"headers"
,
new
String
[]{
"订单ID"
,
"订单编号"
,
"经销商编码"
,
"经销商名称"
,
"订单日期"
,
"订单金额"
,
"返利金额"
,
"出库状态"
,
"开票状态"
,
"返利计算状态"
,
"数据来源"
,
"审核状态"
,
"上传时间"
,
"创建时间"
});
put
(
"fields"
,
new
String
[]{
"orderId"
,
"orderNo"
,
"dealerCode"
,
"dealerName"
,
"orderDate"
,
"totalAmount"
,
"rebateAmount"
,
"deliveryStatus"
,
"invoiceStatus"
,
"rebateCalcFlag"
,
"dataSource"
,
"verifyStatus"
,
"uploadTime"
,
"createTime"
});
}};
/**
* 出库导出配置
*/
public
static
final
Map
<
String
,
String
[]>
DELIVERY_EXPORT_CONFIG
=
new
HashMap
<
String
,
String
[]>()
{{
put
(
"headers"
,
new
String
[]{
"出库单ID"
,
"出库单编号"
,
"经销商编码"
,
"经销商名称"
,
"出库日期"
,
"关联订单编号"
,
"出库状态"
,
"仓库编码"
,
"数据来源"
,
"创建时间"
});
put
(
"fields"
,
new
String
[]{
"deliveryId"
,
"deliveryNo"
,
"dealerCode"
,
"dealerName"
,
"deliveryDate"
,
"orderNo"
,
"deliveryStatus"
,
"warehouseCode"
,
"dataSource"
,
"createTime"
});
}};
/**
* 发票导出配置
*/
public
static
final
Map
<
String
,
String
[]>
INVOICE_EXPORT_CONFIG
=
new
HashMap
<
String
,
String
[]>()
{{
put
(
"headers"
,
new
String
[]{
"发票ID"
,
"发票编号"
,
"订单编号"
,
"出库单编号"
,
"经销商编码"
,
"经销商名称"
,
"发票金额"
,
"发票日期"
,
"开票状态"
,
"税率"
,
"数据来源"
,
"创建时间"
});
put
(
"fields"
,
new
String
[]{
"invoiceId"
,
"invoiceNo"
,
"orderNo"
,
"deliveryNo"
,
"dealerCode"
,
"dealerName"
,
"totalAmount"
,
"invoiceDate"
,
"invoiceStatus"
,
"taxRate"
,
"dataSource"
,
"createTime"
});
}};
/**
* 返利导出配置
*/
public
static
final
Map
<
String
,
String
[]>
REBATE_EXPORT_CONFIG
=
new
HashMap
<
String
,
String
[]>()
{{
put
(
"headers"
,
new
String
[]{
"返利ID"
,
"返利编号"
,
"订单编号"
,
"经销商编码"
,
"经销商名称"
,
"返利金额"
,
"返利类型"
,
"计算状态"
,
"审核状态"
,
"数据来源"
,
"创建时间"
,
"更新时间"
});
put
(
"fields"
,
new
String
[]{
"rebateId"
,
"rebateNo"
,
"orderNo"
,
"dealerCode"
,
"dealerName"
,
"rebateAmount"
,
"operateType"
,
"calcFlag"
,
"auditStatus"
,
"dataSource"
,
"createTime"
,
"updateTime"
});
}};
/**
* 异常工单导出配置
*/
public
static
final
Map
<
String
,
String
[]>
EXCEPTION_WORKORDER_EXPORT_CONFIG
=
new
HashMap
<
String
,
String
[]>()
{{
put
(
"headers"
,
new
String
[]{
"工单ID"
,
"工单编号"
,
"工单类型"
,
"严重程度"
,
"工单状态"
,
"问题描述"
,
"处理人"
,
"创建人"
,
"创建时间"
,
"更新时间"
});
put
(
"fields"
,
new
String
[]{
"workorderId"
,
"workorderNo"
,
"workorderType"
,
"severityLevel"
,
"workorderStatus"
,
"problemDescription"
,
"assignee"
,
"creator"
,
"createTime"
,
"updateTime"
});
}};
}
backend/src/main/java/com/apple/erp/controller/AuthController.java
View file @
db7a876
...
...
@@ -4,6 +4,7 @@ import com.apple.erp.dto.request.LoginReq;
import
com.apple.erp.dto.request.RefreshTokenReq
;
import
com.apple.erp.dto.response.ApiRes
;
import
com.apple.erp.dto.response.LoginRes
;
import
com.apple.erp.dto.response.RoleRes
;
import
com.apple.erp.dto.response.UserInfoRes
;
import
com.apple.erp.entity.SysUser
;
import
com.apple.erp.service.SysUserService
;
...
...
@@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.*;
import
javax.servlet.http.HttpServletRequest
;
import
javax.validation.Valid
;
import
java.util.List
;
/**
* 认证控制器
...
...
@@ -214,9 +216,34 @@ public class AuthController {
if
(
authentication
!=
null
&&
authentication
.
isAuthenticated
())
{
String
username
=
authentication
.
getName
();
try
{
// 获取用户详细信息
SysUser
user
=
sysUserService
.
findByUsername
(
username
);
if
(
user
!=
null
)
{
// 获取用户角色信息
List
<
RoleRes
>
roles
=
sysUserService
.
getUserRoles
(
user
.
getUserId
());
UserInfoRes
userInfo
=
new
UserInfoRes
(
user
.
getUsername
(),
user
.
getRealName
(),
roles
,
authentication
.
getAuthorities
()
);
ApiRes
<
UserInfoRes
>
response
=
ApiRes
.
success
(
"获取用户信息成功"
,
userInfo
);
return
ResponseEntity
.
ok
(
response
);
}
else
{
// 如果找不到用户信息,返回基本信息
UserInfoRes
userInfo
=
new
UserInfoRes
(
username
,
authentication
.
getAuthorities
());
ApiRes
<
UserInfoRes
>
response
=
ApiRes
.
success
(
"获取用户信息成功"
,
userInfo
);
return
ResponseEntity
.
ok
(
response
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"获取用户详细信息失败: "
+
e
.
getMessage
(),
e
);
// 如果获取详细信息失败,返回基本信息
UserInfoRes
userInfo
=
new
UserInfoRes
(
username
,
authentication
.
getAuthorities
());
ApiRes
<
UserInfoRes
>
response
=
ApiRes
.
success
(
"获取用户信息成功"
,
userInfo
);
return
ResponseEntity
.
ok
(
response
);
}
}
else
{
ApiRes
<
UserInfoRes
>
response
=
ApiRes
.
error
(
"未认证"
);
return
ResponseEntity
.
status
(
401
).
body
(
response
);
...
...
backend/src/main/java/com/apple/erp/controller/DeliveryMainController.java
View file @
db7a876
...
...
@@ -6,16 +6,20 @@ import com.apple.erp.dto.DeliveryRes;
import
com.apple.erp.dto.DeliveryUpdateReq
;
import
com.apple.erp.service.DeliveryMainService
;
import
com.apple.erp.dto.response.ApiRes
;
import
com.apple.erp.service.ExcelExportService
;
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.*
;
import
javax.validation.Valid
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
/**
...
...
@@ -33,6 +37,9 @@ public class DeliveryMainController {
@Autowired
private
DeliveryMainService
deliveryMainService
;
@Autowired
private
ExcelExportService
excelExportService
;
@Operation
(
summary
=
"分页查询出库列表"
,
description
=
"根据查询条件分页获取出库列表"
)
@GetMapping
(
"/list"
)
@PreAuthorize
(
"hasAuthority('delivery:list')"
)
...
...
@@ -139,6 +146,40 @@ public class DeliveryMainController {
return
ApiRes
.
error
(
"修改出库状态失败: "
+
e
.
getMessage
());
}
}
@Operation
(
summary
=
"导出出库数据"
,
description
=
"根据查询条件导出出库数据到Excel"
)
@PostMapping
(
"/export"
)
@PreAuthorize
(
"hasAuthority('delivery:export')"
)
public
ResponseEntity
<
byte
[]>
exportDeliveries
(
@Valid
@RequestBody
DeliveryQueryReq
queryReq
)
{
try
{
// 获取所有符合条件的数据(不分页)
DeliveryQueryReq
exportQuery
=
new
DeliveryQueryReq
();
exportQuery
.
setDeliveryNo
(
queryReq
.
getDeliveryNo
());
exportQuery
.
setDealerCode
(
queryReq
.
getDealerCode
());
exportQuery
.
setDealerName
(
queryReq
.
getDealerName
());
exportQuery
.
setDeliveryStatus
(
queryReq
.
getDeliveryStatus
());
exportQuery
.
setWarehouseCode
(
queryReq
.
getWarehouseCode
());
exportQuery
.
setDataSource
(
queryReq
.
getDataSource
());
exportQuery
.
setDeliveryStartDate
(
queryReq
.
getDeliveryStartDate
());
exportQuery
.
setDeliveryEndDate
(
queryReq
.
getDeliveryEndDate
());
// 设置大分页获取所有数据
exportQuery
.
setPageNum
(
1
);
exportQuery
.
setPageSize
(
10000
);
Page
<
DeliveryRes
>
result
=
deliveryMainService
.
getDeliveryList
(
exportQuery
);
List
<
DeliveryRes
>
deliveries
=
result
.
getRecords
();
// 生成文件名
String
timestamp
=
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyyMMdd_HHmmss"
));
String
fileName
=
"出库数据_"
+
timestamp
;
// 导出到Excel
return
excelExportService
.
exportDeliveries
(
deliveries
);
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"导出出库数据失败: "
+
e
.
getMessage
(),
e
);
}
}
}
...
...
backend/src/main/java/com/apple/erp/controller/InvoiceMainController.java
View file @
db7a876
...
...
@@ -6,16 +6,20 @@ import com.apple.erp.dto.InvoiceRes;
import
com.apple.erp.dto.InvoiceUpdateReq
;
import
com.apple.erp.service.InvoiceMainService
;
import
com.apple.erp.dto.response.ApiRes
;
import
com.apple.erp.service.ExcelExportService
;
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.*
;
import
javax.validation.Valid
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
/**
...
...
@@ -33,6 +37,9 @@ public class InvoiceMainController {
@Autowired
private
InvoiceMainService
invoiceMainService
;
@Autowired
private
ExcelExportService
excelExportService
;
@Operation
(
summary
=
"分页查询发票列表"
,
description
=
"根据查询条件分页获取发票列表"
)
@GetMapping
(
"/list"
)
@PreAuthorize
(
"hasAuthority('invoice:list')"
)
...
...
@@ -139,6 +146,43 @@ public class InvoiceMainController {
return
ApiRes
.
error
(
"修改发票状态失败: "
+
e
.
getMessage
());
}
}
@Operation
(
summary
=
"导出发票数据"
,
description
=
"根据查询条件导出发票数据到Excel"
)
@PostMapping
(
"/export"
)
@PreAuthorize
(
"hasAuthority('invoice:export')"
)
public
ResponseEntity
<
byte
[]>
exportInvoices
(
@Valid
@RequestBody
InvoiceQueryReq
queryReq
)
{
try
{
// 获取所有符合条件的数据(不分页)
InvoiceQueryReq
exportQuery
=
new
InvoiceQueryReq
();
exportQuery
.
setInvoiceNo
(
queryReq
.
getInvoiceNo
());
exportQuery
.
setOrderNo
(
queryReq
.
getOrderNo
());
exportQuery
.
setDeliveryNo
(
queryReq
.
getDeliveryNo
());
exportQuery
.
setDealerCode
(
queryReq
.
getDealerCode
());
exportQuery
.
setDealerName
(
queryReq
.
getDealerName
());
exportQuery
.
setInvoiceStatus
(
queryReq
.
getInvoiceStatus
());
exportQuery
.
setDataSource
(
queryReq
.
getDataSource
());
exportQuery
.
setInvoiceStartDate
(
queryReq
.
getInvoiceStartDate
());
exportQuery
.
setInvoiceEndDate
(
queryReq
.
getInvoiceEndDate
());
exportQuery
.
setMinAmount
(
queryReq
.
getMinAmount
());
exportQuery
.
setMaxAmount
(
queryReq
.
getMaxAmount
());
// 设置大分页获取所有数据
exportQuery
.
setPageNum
(
1
);
exportQuery
.
setPageSize
(
10000
);
Page
<
InvoiceRes
>
result
=
invoiceMainService
.
getInvoiceList
(
exportQuery
);
List
<
InvoiceRes
>
invoices
=
result
.
getRecords
();
// 生成文件名
String
timestamp
=
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyyMMdd_HHmmss"
));
String
fileName
=
"发票数据_"
+
timestamp
;
// 导出到Excel
return
excelExportService
.
exportInvoices
(
invoices
);
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"导出发票数据失败: "
+
e
.
getMessage
(),
e
);
}
}
}
...
...
backend/src/main/java/com/apple/erp/controller/OrderMainController.java
View file @
db7a876
...
...
@@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes;
import
com.apple.erp.dto.OrderUpdateReq
;
import
com.apple.erp.service.OrderMainService
;
import
com.apple.erp.dto.response.ApiRes
;
import
com.apple.erp.service.ExcelExportService
;
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.*
;
import
javax.validation.Valid
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
/**
...
...
@@ -33,6 +37,9 @@ public class OrderMainController {
@Autowired
private
OrderMainService
orderMainService
;
@Autowired
private
ExcelExportService
excelExportService
;
@Operation
(
summary
=
"分页查询订单列表"
,
description
=
"根据条件分页查询订单列表"
)
@GetMapping
(
"/list"
)
@PreAuthorize
(
"hasAuthority('order:list')"
)
...
...
@@ -182,4 +189,42 @@ public class OrderMainController {
return
ApiRes
.
error
(
"修改返利计算状态失败: "
+
e
.
getMessage
());
}
}
@Operation
(
summary
=
"导出订单数据"
,
description
=
"根据查询条件导出订单数据到Excel"
)
@PostMapping
(
"/export"
)
@PreAuthorize
(
"hasAuthority('order:export')"
)
public
ResponseEntity
<
byte
[]>
exportOrders
(
@Valid
@RequestBody
OrderQueryReq
queryReq
)
{
try
{
// 获取所有符合条件的数据(不分页)
OrderQueryReq
exportQuery
=
new
OrderQueryReq
();
exportQuery
.
setOrderNo
(
queryReq
.
getOrderNo
());
exportQuery
.
setDealerCode
(
queryReq
.
getDealerCode
());
exportQuery
.
setDealerName
(
queryReq
.
getDealerName
());
exportQuery
.
setDeliveryStatus
(
queryReq
.
getDeliveryStatus
());
exportQuery
.
setInvoiceStatus
(
queryReq
.
getInvoiceStatus
());
exportQuery
.
setRebateCalcFlag
(
queryReq
.
getRebateCalcFlag
());
exportQuery
.
setDataSource
(
queryReq
.
getDataSource
());
exportQuery
.
setVerifyStatus
(
queryReq
.
getVerifyStatus
());
exportQuery
.
setOrderStartDate
(
queryReq
.
getOrderStartDate
());
exportQuery
.
setOrderEndDate
(
queryReq
.
getOrderEndDate
());
exportQuery
.
setMinAmount
(
queryReq
.
getMinAmount
());
exportQuery
.
setMaxAmount
(
queryReq
.
getMaxAmount
());
// 设置大分页获取所有数据
exportQuery
.
setPageNum
(
1
);
exportQuery
.
setPageSize
(
10000
);
Page
<
OrderRes
>
result
=
orderMainService
.
getOrderList
(
exportQuery
);
List
<
OrderRes
>
orders
=
result
.
getRecords
();
// 生成文件名
String
timestamp
=
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyyMMdd_HHmmss"
));
String
fileName
=
"订单数据_"
+
timestamp
;
// 导出到Excel
return
excelExportService
.
exportOrders
(
orders
);
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"导出订单数据失败: "
+
e
.
getMessage
(),
e
);
}
}
}
...
...
backend/src/main/java/com/apple/erp/controller/RebateController.java
View file @
db7a876
...
...
@@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes;
import
com.apple.erp.dto.response.RebateRes
;
import
com.apple.erp.entity.Rebate
;
import
com.apple.erp.service.RebateService
;
import
com.apple.erp.service.ExcelExportService
;
import
com.baomidou.mybatisplus.core.metadata.IPage
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
...
...
@@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*;
import
javax.validation.Valid
;
import
java.math.BigDecimal
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
import
java.util.Map
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.security.access.prepost.PreAuthorize
;
/**
* 返利台账明细管理控制器
...
...
@@ -37,6 +42,9 @@ public class RebateController {
@Autowired
private
RebateService
rebateService
;
@Autowired
private
ExcelExportService
excelExportService
;
@Operation
(
summary
=
"分页查询返利明细列表"
)
@PostMapping
(
"/page"
)
public
ApiRes
<
IPage
<
RebateRes
>>
getRebatePage
(
@Valid
@RequestBody
RebateQueryReq
queryReq
)
{
...
...
@@ -277,4 +285,40 @@ public class RebateController {
return
ApiRes
.
error
(
e
.
getMessage
());
}
}
@Operation
(
summary
=
"导出返利数据"
,
description
=
"根据查询条件导出返利数据到Excel"
)
@PostMapping
(
"/export"
)
@PreAuthorize
(
"hasAuthority('rebate:export')"
)
public
ResponseEntity
<
byte
[]>
exportRebates
(
@Valid
@RequestBody
RebateQueryReq
queryReq
)
{
log
.
info
(
"导出返利数据,参数:{}"
,
queryReq
);
try
{
// 获取所有符合条件的数据(不分页)
RebateQueryReq
exportQuery
=
new
RebateQueryReq
();
exportQuery
.
setRebateNo
(
queryReq
.
getRebateNo
());
exportQuery
.
setOrderNo
(
queryReq
.
getOrderNo
());
exportQuery
.
setDealerCode
(
queryReq
.
getDealerCode
());
exportQuery
.
setDealerName
(
queryReq
.
getDealerName
());
exportQuery
.
setOperateType
(
queryReq
.
getOperateType
());
exportQuery
.
setCalcFlag
(
queryReq
.
getCalcFlag
());
exportQuery
.
setAuditStatus
(
queryReq
.
getAuditStatus
());
exportQuery
.
setDataSource
(
queryReq
.
getDataSource
());
exportQuery
.
setStartDate
(
queryReq
.
getStartDate
());
exportQuery
.
setEndDate
(
queryReq
.
getEndDate
());
exportQuery
.
setMinAmount
(
queryReq
.
getMinAmount
());
exportQuery
.
setMaxAmount
(
queryReq
.
getMaxAmount
());
// 设置大分页获取所有数据
exportQuery
.
setPageNum
(
1
);
exportQuery
.
setPageSize
(
10000
);
IPage
<
RebateRes
>
result
=
rebateService
.
getRebatePage
(
exportQuery
);
List
<
RebateRes
>
rebates
=
result
.
getRecords
();
// 导出到Excel
return
excelExportService
.
exportRebates
(
rebates
);
}
catch
(
Exception
e
)
{
log
.
error
(
"导出返利数据失败"
,
e
);
throw
new
RuntimeException
(
"导出返利数据失败: "
+
e
.
getMessage
(),
e
);
}
}
}
...
...
backend/src/main/java/com/apple/erp/controller/SysDictItemController.java
View file @
db7a876
...
...
@@ -193,6 +193,25 @@ public class SysDictItemController {
}
/**
* 刷新字典缓存
* 当字典项发生变化时,刷新Redis缓存以保持数据一致性
*
* @return 操作结果
*/
@Operation
(
summary
=
"刷新字典缓存"
,
description
=
"刷新Redis字典缓存以保持数据一致性"
)
@PostMapping
(
"/refreshCache"
)
@PreAuthorize
(
"hasAuthority('sys:dict:edit')"
)
public
ApiRes
<
Void
>
refreshCache
()
{
try
{
// 调用字典值转换器的刷新方法
com
.
apple
.
erp
.
util
.
DictValueConverter
.
refreshCache
();
return
ApiRes
.
success
(
"字典缓存刷新成功"
,
null
);
}
catch
(
Exception
e
)
{
return
ApiRes
.
error
(
"刷新字典缓存失败: "
+
e
.
getMessage
());
}
}
/**
* 转换SysDictItem为DictItemRes
*
* @param dictItem 字典项实体
...
...
backend/src/main/java/com/apple/erp/dto/request/RebateQueryReq.java
View file @
db7a876
...
...
@@ -58,6 +58,36 @@ public class RebateQueryReq {
private
String
rebateEndDate
;
/**
* 审核状态
*/
private
Integer
auditStatus
;
/**
* 数据来源
*/
private
String
dataSource
;
/**
* 开始日期
*/
private
String
startDate
;
/**
* 结束日期
*/
private
String
endDate
;
/**
* 最小金额
*/
private
java
.
math
.
BigDecimal
minAmount
;
/**
* 最大金额
*/
private
java
.
math
.
BigDecimal
maxAmount
;
/**
* 页码
*/
private
Integer
pageNum
=
1
;
...
...
backend/src/main/java/com/apple/erp/dto/response/UserInfoRes.java
View file @
db7a876
...
...
@@ -5,6 +5,7 @@ import lombok.Data;
import
org.springframework.security.core.GrantedAuthority
;
import
java.util.Collection
;
import
java.util.List
;
/**
* 用户信息响应对象
...
...
@@ -20,6 +21,12 @@ public class UserInfoRes {
@Schema
(
description
=
"用户名"
,
example
=
"admin"
)
private
String
username
;
@Schema
(
description
=
"真实姓名"
,
example
=
"管理员"
)
private
String
realName
;
@Schema
(
description
=
"用户角色列表"
)
private
List
<
RoleRes
>
roles
;
@Schema
(
description
=
"用户权限列表"
)
private
Collection
<?
extends
GrantedAuthority
>
authorities
;
...
...
@@ -29,4 +36,11 @@ public class UserInfoRes {
this
.
username
=
username
;
this
.
authorities
=
authorities
;
}
public
UserInfoRes
(
String
username
,
String
realName
,
List
<
RoleRes
>
roles
,
Collection
<?
extends
GrantedAuthority
>
authorities
)
{
this
.
username
=
username
;
this
.
realName
=
realName
;
this
.
roles
=
roles
;
this
.
authorities
=
authorities
;
}
}
...
...
backend/src/main/java/com/apple/erp/service/ExcelExportService.java
0 → 100644
View file @
db7a876
package
com
.
apple
.
erp
.
service
;
import
com.apple.erp.config.ExportConfig
;
import
com.apple.erp.util.GenericExcelExportUtil
;
import
org.springframework.stereotype.Service
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
import
java.util.Map
;
/**
* Excel导出服务类
* 提供统一的导出接口,各模块可独立使用
*
* @author Apple ERP System
* @since 2025-01-01
*/
@Service
public
class
ExcelExportService
{
private
static
final
DateTimeFormatter
TIMESTAMP_FORMATTER
=
DateTimeFormatter
.
ofPattern
(
"yyyyMMdd_HHmmss"
);
/**
* 导出订单数据
*/
public
org
.
springframework
.
http
.
ResponseEntity
<
byte
[]>
exportOrders
(
List
<?>
data
)
{
Map
<
String
,
String
[]>
config
=
ExportConfig
.
ORDER_EXPORT_CONFIG
;
String
fileName
=
"订单数据_"
+
LocalDateTime
.
now
().
format
(
TIMESTAMP_FORMATTER
);
return
GenericExcelExportUtil
.
exportToExcel
(
data
,
config
.
get
(
"headers"
),
config
.
get
(
"fields"
),
fileName
);
}
/**
* 导出出库数据
*/
public
org
.
springframework
.
http
.
ResponseEntity
<
byte
[]>
exportDeliveries
(
List
<?>
data
)
{
Map
<
String
,
String
[]>
config
=
ExportConfig
.
DELIVERY_EXPORT_CONFIG
;
String
fileName
=
"出库数据_"
+
LocalDateTime
.
now
().
format
(
TIMESTAMP_FORMATTER
);
return
GenericExcelExportUtil
.
exportToExcel
(
data
,
config
.
get
(
"headers"
),
config
.
get
(
"fields"
),
fileName
);
}
/**
* 导出发票数据
*/
public
org
.
springframework
.
http
.
ResponseEntity
<
byte
[]>
exportInvoices
(
List
<?>
data
)
{
Map
<
String
,
String
[]>
config
=
ExportConfig
.
INVOICE_EXPORT_CONFIG
;
String
fileName
=
"发票数据_"
+
LocalDateTime
.
now
().
format
(
TIMESTAMP_FORMATTER
);
return
GenericExcelExportUtil
.
exportToExcel
(
data
,
config
.
get
(
"headers"
),
config
.
get
(
"fields"
),
fileName
);
}
/**
* 导出返利数据
*/
public
org
.
springframework
.
http
.
ResponseEntity
<
byte
[]>
exportRebates
(
List
<?>
data
)
{
Map
<
String
,
String
[]>
config
=
ExportConfig
.
REBATE_EXPORT_CONFIG
;
String
fileName
=
"返利数据_"
+
LocalDateTime
.
now
().
format
(
TIMESTAMP_FORMATTER
);
return
GenericExcelExportUtil
.
exportToExcel
(
data
,
config
.
get
(
"headers"
),
config
.
get
(
"fields"
),
fileName
);
}
/**
* 导出异常工单数据
*/
public
org
.
springframework
.
http
.
ResponseEntity
<
byte
[]>
exportExceptionWorkorders
(
List
<?>
data
)
{
Map
<
String
,
String
[]>
config
=
ExportConfig
.
EXCEPTION_WORKORDER_EXPORT_CONFIG
;
String
fileName
=
"异常工单数据_"
+
LocalDateTime
.
now
().
format
(
TIMESTAMP_FORMATTER
);
return
GenericExcelExportUtil
.
exportToExcel
(
data
,
config
.
get
(
"headers"
),
config
.
get
(
"fields"
),
fileName
);
}
/**
* 通用导出方法
* 支持自定义表头和字段映射
*/
public
org
.
springframework
.
http
.
ResponseEntity
<
byte
[]>
exportCustom
(
List
<?>
data
,
String
[]
headers
,
String
[]
fieldNames
,
String
fileName
)
{
return
GenericExcelExportUtil
.
exportToExcel
(
data
,
headers
,
fieldNames
,
fileName
);
}
}
backend/src/main/java/com/apple/erp/util/DictValueConverter.java
0 → 100644
View file @
db7a876
package
com
.
apple
.
erp
.
util
;
import
com.apple.erp.entity.SysDictItem
;
import
com.apple.erp.entity.SysDictType
;
import
com.apple.erp.service.SysDictItemService
;
import
com.apple.erp.service.SysDictTypeService
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.stereotype.Component
;
import
javax.annotation.PostConstruct
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.TimeUnit
;
/**
* 字典值转换器 - 基于Redis缓存的动态字典值转换
* 支持实时更新,无需重启应用,使用Redis分布式缓存
*
* @author Apple ERP System
* @since 2025-01-01
*/
@Component
public
class
DictValueConverter
{
@Autowired
private
SysDictItemService
sysDictItemService
;
@Autowired
private
SysDictTypeService
sysDictTypeService
;
@Autowired
private
RedisTemplate
<
String
,
Object
>
redisTemplate
;
private
static
SysDictItemService
staticDictItemService
;
private
static
SysDictTypeService
staticDictTypeService
;
private
static
RedisTemplate
<
String
,
Object
>
staticRedisTemplate
;
/**
* Redis缓存键前缀
*/
private
static
final
String
DICT_CACHE_PREFIX
=
"dict:cache:"
;
/**
* 缓存过期时间(小时)
*/
private
static
final
long
CACHE_EXPIRE_HOURS
=
24
;
@PostConstruct
public
void
init
()
{
staticDictItemService
=
sysDictItemService
;
staticDictTypeService
=
sysDictTypeService
;
staticRedisTemplate
=
redisTemplate
;
System
.
out
.
println
(
"DictValueConverter初始化开始..."
);
refreshCache
();
System
.
out
.
println
(
"DictValueConverter初始化完成"
);
}
/**
* 转换字典值 - 简化版本,直接使用硬编码映射
*/
public
static
String
convert
(
Object
value
,
String
fieldName
)
{
if
(
value
==
null
)
{
return
""
;
}
// 将值转换为字符串进行匹配
String
valueStr
=
value
.
toString
();
// 直接使用硬编码映射进行转换
Map
<
String
,
String
>
mapping
=
getHardcodedMapping
(
fieldName
);
if
(
mapping
!=
null
&&
!
mapping
.
isEmpty
())
{
String
result
=
mapping
.
getOrDefault
(
valueStr
,
valueStr
);
System
.
out
.
println
(
"字典转换: "
+
fieldName
+
" = "
+
valueStr
+
" -> "
+
result
);
return
result
;
}
return
valueStr
;
}
/**
* 获取硬编码的字典映射
*/
private
static
Map
<
String
,
String
>
getHardcodedMapping
(
String
fieldName
)
{
Map
<
String
,
String
>
mapping
=
new
HashMap
<>();
switch
(
fieldName
)
{
case
"deliveryStatus"
:
mapping
.
put
(
"0"
,
"未出库"
);
mapping
.
put
(
"1"
,
"已出库"
);
break
;
case
"invoiceStatus"
:
mapping
.
put
(
"0"
,
"未开票"
);
mapping
.
put
(
"1"
,
"已开票"
);
break
;
case
"rebateCalcFlag"
:
mapping
.
put
(
"0"
,
"未计算"
);
mapping
.
put
(
"1"
,
"已计算"
);
break
;
case
"verifyStatus"
:
mapping
.
put
(
"0"
,
"待验证"
);
mapping
.
put
(
"1"
,
"验证通过"
);
mapping
.
put
(
"2"
,
"验证失败"
);
break
;
case
"workorderStatus"
:
mapping
.
put
(
"1"
,
"待处理"
);
mapping
.
put
(
"2"
,
"处理中"
);
mapping
.
put
(
"3"
,
"已解决"
);
mapping
.
put
(
"4"
,
"已关闭"
);
break
;
case
"severityLevel"
:
mapping
.
put
(
"1"
,
"高"
);
mapping
.
put
(
"2"
,
"中"
);
mapping
.
put
(
"3"
,
"低"
);
break
;
case
"operateType"
:
mapping
.
put
(
"1"
,
"新增"
);
mapping
.
put
(
"2"
,
"修改"
);
mapping
.
put
(
"3"
,
"删除"
);
break
;
case
"calcFlag"
:
mapping
.
put
(
"0"
,
"未计算"
);
mapping
.
put
(
"1"
,
"已计算"
);
break
;
case
"auditStatus"
:
mapping
.
put
(
"0"
,
"待审核"
);
mapping
.
put
(
"1"
,
"审核通过"
);
mapping
.
put
(
"2"
,
"审核中"
);
mapping
.
put
(
"3"
,
"审核拒绝"
);
break
;
}
return
mapping
;
}
/**
* 刷新字典缓存 - 清空Redis缓存并重新加载
*/
public
static
void
refreshCache
()
{
if
(
staticRedisTemplate
==
null
)
{
System
.
out
.
println
(
"Redis模板未初始化,跳过缓存刷新"
);
return
;
}
try
{
System
.
out
.
println
(
"开始刷新字典缓存..."
);
// 清空所有字典缓存
String
pattern
=
DICT_CACHE_PREFIX
+
"*"
;
staticRedisTemplate
.
delete
(
staticRedisTemplate
.
keys
(
pattern
));
System
.
out
.
println
(
"已清空现有字典缓存"
);
// 重新加载所有字典类型
loadAllDictsToRedis
();
System
.
out
.
println
(
"字典缓存刷新完成"
);
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"刷新字典缓存失败: "
+
e
.
getMessage
());
e
.
printStackTrace
();
}
}
/**
* 加载所有字典到Redis
*/
private
static
void
loadAllDictsToRedis
()
{
if
(
staticDictItemService
==
null
)
{
return
;
}
try
{
// 从数据库加载所有字典项
List
<
SysDictItem
>
allDictItems
=
staticDictItemService
.
list
();
// 按字典类型分组
Map
<
String
,
Map
<
String
,
String
>>
dictGroups
=
new
HashMap
<>();
for
(
SysDictItem
item
:
allDictItems
)
{
if
(
item
.
getDelFlag
()
!=
null
&&
!
"0"
.
equals
(
item
.
getDelFlag
()))
{
continue
;
// 跳过已删除的字典项
}
String
dictType
=
getDictTypeByTypeId
(
item
.
getDictTypeId
());
if
(
dictType
!=
null
)
{
dictGroups
.
computeIfAbsent
(
dictType
,
k
->
new
HashMap
<>())
.
put
(
item
.
getDictValue
(),
item
.
getDictLabel
());
}
}
// 将每个字典类型存储到Redis
for
(
Map
.
Entry
<
String
,
Map
<
String
,
String
>>
entry
:
dictGroups
.
entrySet
())
{
String
cacheKey
=
DICT_CACHE_PREFIX
+
entry
.
getKey
();
staticRedisTemplate
.
opsForValue
().
set
(
cacheKey
,
entry
.
getValue
(),
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
}
}
catch
(
Exception
e
)
{
// 如果数据库加载失败,使用默认配置作为降级方案
loadDefaultDictConfigToRedis
();
}
}
/**
* 加载指定字典类型到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获取字典类型编码
* 从数据库查询字典类型表获取真实的字典类型编码
*/
private
static
String
getDictTypeByTypeId
(
Long
dictTypeId
)
{
if
(
staticDictTypeService
==
null
||
dictTypeId
==
null
)
{
return
null
;
}
try
{
SysDictType
dictType
=
staticDictTypeService
.
getById
(
dictTypeId
);
if
(
dictType
!=
null
&&
dictType
.
getStatus
()
!=
null
&&
dictType
.
getStatus
()
==
1
)
{
return
dictType
.
getDictType
();
}
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"查询字典类型失败: "
+
e
.
getMessage
());
}
return
null
;
}
/**
* 加载默认字典配置到Redis(降级方案)
*/
private
static
void
loadDefaultDictConfigToRedis
()
{
if
(
staticRedisTemplate
==
null
)
{
return
;
}
try
{
// 操作类型
Map
<
String
,
String
>
operateType
=
new
HashMap
<>();
operateType
.
put
(
"1"
,
"新增"
);
operateType
.
put
(
"2"
,
"修改"
);
operateType
.
put
(
"3"
,
"删除"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"operateType"
,
operateType
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
// 计算状态
Map
<
String
,
String
>
calcFlag
=
new
HashMap
<>();
calcFlag
.
put
(
"0"
,
"未计算"
);
calcFlag
.
put
(
"1"
,
"已计算"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"calcFlag"
,
calcFlag
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
// 审核状态
Map
<
String
,
String
>
auditStatus
=
new
HashMap
<>();
auditStatus
.
put
(
"0"
,
"待审核"
);
auditStatus
.
put
(
"1"
,
"审核通过"
);
auditStatus
.
put
(
"2"
,
"审核中"
);
auditStatus
.
put
(
"3"
,
"审核拒绝"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"auditStatus"
,
auditStatus
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
// 出库状态
Map
<
String
,
String
>
deliveryStatus
=
new
HashMap
<>();
deliveryStatus
.
put
(
"0"
,
"未出库"
);
deliveryStatus
.
put
(
"1"
,
"已出库"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"deliveryStatus"
,
deliveryStatus
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
// 发票状态
Map
<
String
,
String
>
invoiceStatus
=
new
HashMap
<>();
invoiceStatus
.
put
(
"0"
,
"未开票"
);
invoiceStatus
.
put
(
"1"
,
"已开票"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"invoiceStatus"
,
invoiceStatus
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
// 返利计算状态
Map
<
String
,
String
>
rebateCalcFlag
=
new
HashMap
<>();
rebateCalcFlag
.
put
(
"0"
,
"未计算"
);
rebateCalcFlag
.
put
(
"1"
,
"已计算"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"rebateCalcFlag"
,
rebateCalcFlag
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
// 审核状态
Map
<
String
,
String
>
verifyStatus
=
new
HashMap
<>();
verifyStatus
.
put
(
"0"
,
"待验证"
);
verifyStatus
.
put
(
"1"
,
"验证通过"
);
verifyStatus
.
put
(
"2"
,
"验证失败"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"verifyStatus"
,
verifyStatus
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
// 工单状态
Map
<
String
,
String
>
workorderStatus
=
new
HashMap
<>();
workorderStatus
.
put
(
"1"
,
"待处理"
);
workorderStatus
.
put
(
"2"
,
"处理中"
);
workorderStatus
.
put
(
"3"
,
"已解决"
);
workorderStatus
.
put
(
"4"
,
"已关闭"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"workorderStatus"
,
workorderStatus
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
// 严重程度
Map
<
String
,
String
>
severityLevel
=
new
HashMap
<>();
severityLevel
.
put
(
"1"
,
"高"
);
severityLevel
.
put
(
"2"
,
"中"
);
severityLevel
.
put
(
"3"
,
"低"
);
staticRedisTemplate
.
opsForValue
().
set
(
DICT_CACHE_PREFIX
+
"severityLevel"
,
severityLevel
,
CACHE_EXPIRE_HOURS
,
TimeUnit
.
HOURS
);
}
catch
(
Exception
e
)
{
System
.
err
.
println
(
"加载默认字典配置到Redis失败: "
+
e
.
getMessage
());
}
}
}
backend/src/main/java/com/apple/erp/util/ExcelExportUtil.java
0 → 100644
View file @
db7a876
package
com
.
apple
.
erp
.
util
;
import
org.apache.poi.ss.usermodel.*
;
import
org.apache.poi.xssf.usermodel.XSSFWorkbook
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.MediaType
;
import
org.springframework.http.ResponseEntity
;
import
java.io.ByteArrayOutputStream
;
import
java.io.IOException
;
import
java.lang.reflect.Field
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
/**
* Excel导出工具类
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
public
class
ExcelExportUtil
{
private
static
final
DateTimeFormatter
DATE_TIME_FORMATTER
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd HH:mm:ss"
);
/**
* 导出数据到Excel
*
* @param data 数据列表
* @param headers 表头数组
* @param fileName 文件名
* @param <T> 数据类型
* @return ResponseEntity<byte[]>
*/
public
static
<
T
>
ResponseEntity
<
byte
[]>
exportToExcel
(
List
<
T
>
data
,
String
[]
headers
,
String
fileName
)
{
try
(
Workbook
workbook
=
new
XSSFWorkbook
())
{
Sheet
sheet
=
workbook
.
createSheet
(
"数据导出"
);
// 创建表头样式
CellStyle
headerStyle
=
createHeaderStyle
(
workbook
);
CellStyle
dataStyle
=
createDataStyle
(
workbook
);
// 创建表头
Row
headerRow
=
sheet
.
createRow
(
0
);
for
(
int
i
=
0
;
i
<
headers
.
length
;
i
++)
{
Cell
cell
=
headerRow
.
createCell
(
i
);
cell
.
setCellValue
(
headers
[
i
]);
cell
.
setCellStyle
(
headerStyle
);
}
// 填充数据
if
(
data
!=
null
&&
!
data
.
isEmpty
())
{
for
(
int
i
=
0
;
i
<
data
.
size
();
i
++)
{
Row
row
=
sheet
.
createRow
(
i
+
1
);
T
item
=
data
.
get
(
i
);
fillRowData
(
row
,
item
,
dataStyle
);
}
}
// 自动调整列宽
for
(
int
i
=
0
;
i
<
headers
.
length
;
i
++)
{
sheet
.
autoSizeColumn
(
i
);
}
// 转换为字节数组
ByteArrayOutputStream
outputStream
=
new
ByteArrayOutputStream
();
workbook
.
write
(
outputStream
);
byte
[]
bytes
=
outputStream
.
toByteArray
();
// 设置响应头
HttpHeaders
httpHeaders
=
new
HttpHeaders
();
httpHeaders
.
setContentType
(
MediaType
.
APPLICATION_OCTET_STREAM
);
// 对文件名进行URL编码以支持中文
String
encodedFileName
;
try
{
encodedFileName
=
java
.
net
.
URLEncoder
.
encode
(
fileName
+
".xlsx"
,
"UTF-8"
);
}
catch
(
Exception
e
)
{
encodedFileName
=
fileName
+
".xlsx"
;
}
httpHeaders
.
set
(
"Content-Disposition"
,
"attachment; filename=\""
+
encodedFileName
+
"\"; filename*=UTF-8''"
+
encodedFileName
);
httpHeaders
.
setContentLength
(
bytes
.
length
);
return
new
ResponseEntity
<>(
bytes
,
httpHeaders
,
HttpStatus
.
OK
);
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
"Excel导出失败"
,
e
);
}
}
/**
* 创建表头样式
*/
private
static
CellStyle
createHeaderStyle
(
Workbook
workbook
)
{
CellStyle
style
=
workbook
.
createCellStyle
();
Font
font
=
workbook
.
createFont
();
font
.
setBold
(
true
);
font
.
setFontHeightInPoints
((
short
)
12
);
style
.
setFont
(
font
);
style
.
setFillForegroundColor
(
IndexedColors
.
GREY_25_PERCENT
.
getIndex
());
style
.
setFillPattern
(
FillPatternType
.
SOLID_FOREGROUND
);
style
.
setBorderBottom
(
BorderStyle
.
THIN
);
style
.
setBorderTop
(
BorderStyle
.
THIN
);
style
.
setBorderRight
(
BorderStyle
.
THIN
);
style
.
setBorderLeft
(
BorderStyle
.
THIN
);
style
.
setAlignment
(
HorizontalAlignment
.
CENTER
);
style
.
setVerticalAlignment
(
VerticalAlignment
.
CENTER
);
return
style
;
}
/**
* 创建数据样式
*/
private
static
CellStyle
createDataStyle
(
Workbook
workbook
)
{
CellStyle
style
=
workbook
.
createCellStyle
();
style
.
setBorderBottom
(
BorderStyle
.
THIN
);
style
.
setBorderTop
(
BorderStyle
.
THIN
);
style
.
setBorderRight
(
BorderStyle
.
THIN
);
style
.
setBorderLeft
(
BorderStyle
.
THIN
);
style
.
setAlignment
(
HorizontalAlignment
.
LEFT
);
style
.
setVerticalAlignment
(
VerticalAlignment
.
CENTER
);
return
style
;
}
/**
* 填充行数据
*/
private
static
<
T
>
void
fillRowData
(
Row
row
,
T
item
,
CellStyle
dataStyle
)
{
if
(
item
==
null
)
return
;
Field
[]
fields
=
item
.
getClass
().
getDeclaredFields
();
int
cellIndex
=
0
;
for
(
Field
field
:
fields
)
{
if
(
cellIndex
>=
row
.
getLastCellNum
())
break
;
try
{
// 使用getter方法获取值,而不是直接访问字段
String
fieldName
=
field
.
getName
();
String
getterName
=
"get"
+
fieldName
.
substring
(
0
,
1
).
toUpperCase
()
+
fieldName
.
substring
(
1
);
Object
value
=
null
;
try
{
java
.
lang
.
reflect
.
Method
getter
=
item
.
getClass
().
getMethod
(
getterName
);
value
=
getter
.
invoke
(
item
);
}
catch
(
Exception
e
)
{
// 如果getter方法不存在,尝试直接访问字段
field
.
setAccessible
(
true
);
value
=
field
.
get
(
item
);
}
Cell
cell
=
row
.
createCell
(
cellIndex
);
cell
.
setCellStyle
(
dataStyle
);
if
(
value
!=
null
)
{
if
(
value
instanceof
String
)
{
cell
.
setCellValue
((
String
)
value
);
}
else
if
(
value
instanceof
Number
)
{
cell
.
setCellValue
(((
Number
)
value
).
doubleValue
());
}
else
if
(
value
instanceof
LocalDateTime
)
{
cell
.
setCellValue
(((
LocalDateTime
)
value
).
format
(
DATE_TIME_FORMATTER
));
}
else
{
cell
.
setCellValue
(
value
.
toString
());
}
}
else
{
cell
.
setCellValue
(
""
);
}
cellIndex
++;
}
catch
(
Exception
e
)
{
// 忽略无法访问的字段
System
.
out
.
println
(
"无法访问字段: "
+
field
.
getName
()
+
", 错误: "
+
e
.
getMessage
());
}
}
}
/**
* 导出订单数据到Excel
*
* @param data 订单数据列表
* @param fileName 文件名
* @return ResponseEntity<byte[]>
*/
public
static
ResponseEntity
<
byte
[]>
exportOrderToExcel
(
List
<?>
data
,
String
fileName
)
{
try
(
Workbook
workbook
=
new
XSSFWorkbook
())
{
Sheet
sheet
=
workbook
.
createSheet
(
"订单数据"
);
// 创建表头样式
CellStyle
headerStyle
=
createHeaderStyle
(
workbook
);
CellStyle
dataStyle
=
createDataStyle
(
workbook
);
// 创建表头
Row
headerRow
=
sheet
.
createRow
(
0
);
String
[]
headers
=
{
"订单ID"
,
"订单编号"
,
"经销商编码"
,
"经销商名称"
,
"订单日期"
,
"订单金额"
,
"返利金额"
,
"出库状态"
,
"开票状态"
,
"返利计算状态"
,
"数据来源"
,
"审核状态"
,
"上传时间"
,
"创建时间"
};
for
(
int
i
=
0
;
i
<
headers
.
length
;
i
++)
{
Cell
cell
=
headerRow
.
createCell
(
i
);
cell
.
setCellValue
(
headers
[
i
]);
cell
.
setCellStyle
(
headerStyle
);
}
// 填充数据
if
(
data
!=
null
&&
!
data
.
isEmpty
())
{
for
(
int
i
=
0
;
i
<
data
.
size
();
i
++)
{
Row
row
=
sheet
.
createRow
(
i
+
1
);
Object
item
=
data
.
get
(
i
);
// 使用反射获取字段值
fillOrderRowData
(
row
,
item
,
dataStyle
);
}
}
// 自动调整列宽
for
(
int
i
=
0
;
i
<
headers
.
length
;
i
++)
{
sheet
.
autoSizeColumn
(
i
);
}
// 转换为字节数组
ByteArrayOutputStream
outputStream
=
new
ByteArrayOutputStream
();
workbook
.
write
(
outputStream
);
byte
[]
bytes
=
outputStream
.
toByteArray
();
// 设置响应头
HttpHeaders
httpHeaders
=
new
HttpHeaders
();
httpHeaders
.
setContentType
(
MediaType
.
APPLICATION_OCTET_STREAM
);
// 对文件名进行URL编码以支持中文
String
encodedFileName
;
try
{
encodedFileName
=
java
.
net
.
URLEncoder
.
encode
(
fileName
+
".xlsx"
,
"UTF-8"
);
}
catch
(
Exception
e
)
{
encodedFileName
=
fileName
+
".xlsx"
;
}
httpHeaders
.
set
(
"Content-Disposition"
,
"attachment; filename=\""
+
encodedFileName
+
"\"; filename*=UTF-8''"
+
encodedFileName
);
httpHeaders
.
setContentLength
(
bytes
.
length
);
return
new
ResponseEntity
<>(
bytes
,
httpHeaders
,
HttpStatus
.
OK
);
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
"Excel导出失败"
,
e
);
}
}
/**
* 填充订单行数据
*/
private
static
void
fillOrderRowData
(
Row
row
,
Object
item
,
CellStyle
dataStyle
)
{
try
{
// 使用反射获取OrderRes的字段值
Class
<?>
clazz
=
item
.
getClass
();
// 订单ID
setCellValue
(
row
,
0
,
getFieldValue
(
clazz
,
item
,
"orderId"
),
dataStyle
);
// 订单编号
setCellValue
(
row
,
1
,
getFieldValue
(
clazz
,
item
,
"orderNo"
),
dataStyle
);
// 经销商编码
setCellValue
(
row
,
2
,
getFieldValue
(
clazz
,
item
,
"dealerCode"
),
dataStyle
);
// 经销商名称
setCellValue
(
row
,
3
,
getFieldValue
(
clazz
,
item
,
"dealerName"
),
dataStyle
);
// 订单日期
setCellValue
(
row
,
4
,
getFieldValue
(
clazz
,
item
,
"orderDate"
),
dataStyle
);
// 订单金额
setCellValue
(
row
,
5
,
getFieldValue
(
clazz
,
item
,
"totalAmount"
),
dataStyle
);
// 返利金额
setCellValue
(
row
,
6
,
getFieldValue
(
clazz
,
item
,
"rebateAmount"
),
dataStyle
);
// 出库状态
setCellValue
(
row
,
7
,
getFieldValue
(
clazz
,
item
,
"deliveryStatus"
),
dataStyle
);
// 开票状态
setCellValue
(
row
,
8
,
getFieldValue
(
clazz
,
item
,
"invoiceStatus"
),
dataStyle
);
// 返利计算状态
setCellValue
(
row
,
9
,
getFieldValue
(
clazz
,
item
,
"rebateCalcFlag"
),
dataStyle
);
// 数据来源
setCellValue
(
row
,
10
,
getFieldValue
(
clazz
,
item
,
"dataSource"
),
dataStyle
);
// 审核状态
setCellValue
(
row
,
11
,
getFieldValue
(
clazz
,
item
,
"verifyStatus"
),
dataStyle
);
// 上传时间
setCellValue
(
row
,
12
,
getFieldValue
(
clazz
,
item
,
"uploadTime"
),
dataStyle
);
// 创建时间
setCellValue
(
row
,
13
,
getFieldValue
(
clazz
,
item
,
"createTime"
),
dataStyle
);
}
catch
(
Exception
e
)
{
System
.
out
.
println
(
"填充订单数据失败: "
+
e
.
getMessage
());
}
}
/**
* 获取字段值
*/
private
static
Object
getFieldValue
(
Class
<?>
clazz
,
Object
item
,
String
fieldName
)
{
try
{
String
getterName
=
"get"
+
fieldName
.
substring
(
0
,
1
).
toUpperCase
()
+
fieldName
.
substring
(
1
);
java
.
lang
.
reflect
.
Method
getter
=
clazz
.
getMethod
(
getterName
);
return
getter
.
invoke
(
item
);
}
catch
(
Exception
e
)
{
return
null
;
}
}
/**
* 设置单元格值
*/
private
static
void
setCellValue
(
Row
row
,
int
cellIndex
,
Object
value
,
CellStyle
dataStyle
)
{
Cell
cell
=
row
.
createCell
(
cellIndex
);
cell
.
setCellStyle
(
dataStyle
);
if
(
value
!=
null
)
{
if
(
value
instanceof
String
)
{
cell
.
setCellValue
((
String
)
value
);
}
else
if
(
value
instanceof
Number
)
{
cell
.
setCellValue
(((
Number
)
value
).
doubleValue
());
}
else
if
(
value
instanceof
LocalDateTime
)
{
cell
.
setCellValue
(((
LocalDateTime
)
value
).
format
(
DATE_TIME_FORMATTER
));
}
else
{
cell
.
setCellValue
(
value
.
toString
());
}
}
else
{
cell
.
setCellValue
(
""
);
}
}
/**
* 导出出库数据到Excel
*
* @param data 出库数据列表
* @param fileName 文件名
* @return ResponseEntity<byte[]>
*/
public
static
ResponseEntity
<
byte
[]>
exportDeliveryToExcel
(
List
<?>
data
,
String
fileName
)
{
String
[]
headers
=
{
"出库单ID"
,
"出库单编号"
,
"经销商编码"
,
"经销商名称"
,
"出库日期"
,
"关联订单编号"
,
"出库状态"
,
"仓库编码"
,
"数据来源"
,
"创建时间"
};
return
exportToExcel
(
data
,
headers
,
fileName
);
}
/**
* 导出发票数据到Excel
*
* @param data 发票数据列表
* @param fileName 文件名
* @return ResponseEntity<byte[]>
*/
public
static
ResponseEntity
<
byte
[]>
exportInvoiceToExcel
(
List
<?>
data
,
String
fileName
)
{
String
[]
headers
=
{
"发票ID"
,
"发票编号"
,
"订单编号"
,
"出库单编号"
,
"经销商编码"
,
"经销商名称"
,
"发票金额"
,
"发票日期"
,
"开票状态"
,
"税率"
,
"数据来源"
,
"创建时间"
};
return
exportToExcel
(
data
,
headers
,
fileName
);
}
}
backend/src/main/java/com/apple/erp/util/GenericExcelExportUtil.java
0 → 100644
View file @
db7a876
package
com
.
apple
.
erp
.
util
;
import
org.apache.poi.ss.usermodel.*
;
import
org.apache.poi.xssf.usermodel.XSSFWorkbook
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.MediaType
;
import
org.springframework.http.ResponseEntity
;
import
java.io.ByteArrayOutputStream
;
import
java.io.IOException
;
import
java.lang.reflect.Field
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
/**
* 通用Excel导出工具类
* 支持任意实体类的Excel导出,通过注解配置字段映射
*
* @author Apple ERP System
* @since 2025-01-01
*/
public
class
GenericExcelExportUtil
{
private
static
final
DateTimeFormatter
DATE_TIME_FORMATTER
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd HH:mm:ss"
);
private
static
final
DateTimeFormatter
DATE_FORMATTER
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
);
/**
* 通用Excel导出方法
*
* @param data 数据列表
* @param headers 表头数组
* @param fieldNames 字段名数组(与表头对应)
* @param fileName 文件名(不含扩展名)
* @return ResponseEntity<byte[]>
*/
public
static
ResponseEntity
<
byte
[]>
exportToExcel
(
List
<?>
data
,
String
[]
headers
,
String
[]
fieldNames
,
String
fileName
)
{
try
(
Workbook
workbook
=
new
XSSFWorkbook
())
{
Sheet
sheet
=
workbook
.
createSheet
(
"数据导出"
);
// 创建样式
CellStyle
headerStyle
=
createHeaderStyle
(
workbook
);
CellStyle
dataStyle
=
createDataStyle
(
workbook
);
// 创建表头
Row
headerRow
=
sheet
.
createRow
(
0
);
for
(
int
i
=
0
;
i
<
headers
.
length
;
i
++)
{
Cell
cell
=
headerRow
.
createCell
(
i
);
cell
.
setCellValue
(
headers
[
i
]);
cell
.
setCellStyle
(
headerStyle
);
}
// 填充数据
if
(
data
!=
null
&&
!
data
.
isEmpty
())
{
for
(
int
i
=
0
;
i
<
data
.
size
();
i
++)
{
Row
row
=
sheet
.
createRow
(
i
+
1
);
Object
item
=
data
.
get
(
i
);
fillRowData
(
row
,
item
,
fieldNames
,
dataStyle
);
}
}
// 自动调整列宽
for
(
int
i
=
0
;
i
<
headers
.
length
;
i
++)
{
sheet
.
autoSizeColumn
(
i
);
}
// 转换为字节数组
ByteArrayOutputStream
outputStream
=
new
ByteArrayOutputStream
();
workbook
.
write
(
outputStream
);
byte
[]
bytes
=
outputStream
.
toByteArray
();
// 设置响应头
HttpHeaders
httpHeaders
=
new
HttpHeaders
();
httpHeaders
.
setContentType
(
MediaType
.
APPLICATION_OCTET_STREAM
);
// 对文件名进行URL编码以支持中文
String
encodedFileName
;
try
{
encodedFileName
=
java
.
net
.
URLEncoder
.
encode
(
fileName
+
".xlsx"
,
"UTF-8"
);
}
catch
(
Exception
e
)
{
encodedFileName
=
fileName
+
".xlsx"
;
}
httpHeaders
.
set
(
"Content-Disposition"
,
"attachment; filename=\""
+
encodedFileName
+
"\"; filename*=UTF-8''"
+
encodedFileName
);
httpHeaders
.
setContentLength
(
bytes
.
length
);
return
new
ResponseEntity
<>(
bytes
,
httpHeaders
,
HttpStatus
.
OK
);
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
"Excel导出失败"
,
e
);
}
}
/**
* 创建表头样式
*/
private
static
CellStyle
createHeaderStyle
(
Workbook
workbook
)
{
CellStyle
style
=
workbook
.
createCellStyle
();
Font
font
=
workbook
.
createFont
();
font
.
setBold
(
true
);
font
.
setFontHeightInPoints
((
short
)
12
);
style
.
setFont
(
font
);
style
.
setFillForegroundColor
(
IndexedColors
.
GREY_25_PERCENT
.
getIndex
());
style
.
setFillPattern
(
FillPatternType
.
SOLID_FOREGROUND
);
style
.
setBorderBottom
(
BorderStyle
.
THIN
);
style
.
setBorderTop
(
BorderStyle
.
THIN
);
style
.
setBorderRight
(
BorderStyle
.
THIN
);
style
.
setBorderLeft
(
BorderStyle
.
THIN
);
style
.
setAlignment
(
HorizontalAlignment
.
CENTER
);
style
.
setVerticalAlignment
(
VerticalAlignment
.
CENTER
);
return
style
;
}
/**
* 创建数据样式
*/
private
static
CellStyle
createDataStyle
(
Workbook
workbook
)
{
CellStyle
style
=
workbook
.
createCellStyle
();
style
.
setBorderBottom
(
BorderStyle
.
THIN
);
style
.
setBorderTop
(
BorderStyle
.
THIN
);
style
.
setBorderRight
(
BorderStyle
.
THIN
);
style
.
setBorderLeft
(
BorderStyle
.
THIN
);
style
.
setVerticalAlignment
(
VerticalAlignment
.
CENTER
);
return
style
;
}
/**
* 填充行数据
*/
private
static
void
fillRowData
(
Row
row
,
Object
item
,
String
[]
fieldNames
,
CellStyle
dataStyle
)
{
try
{
Class
<?>
clazz
=
item
.
getClass
();
for
(
int
i
=
0
;
i
<
fieldNames
.
length
;
i
++)
{
Cell
cell
=
row
.
createCell
(
i
);
cell
.
setCellStyle
(
dataStyle
);
Object
value
=
getFieldValue
(
clazz
,
item
,
fieldNames
[
i
]);
// 对特定字段进行字典值转换
String
convertedValue
=
convertDictValue
(
value
,
fieldNames
[
i
]);
System
.
out
.
println
(
"字段转换: "
+
fieldNames
[
i
]
+
" = "
+
value
+
" -> "
+
convertedValue
);
if
(
convertedValue
!=
null
&&
!
convertedValue
.
equals
(
value
!=
null
?
value
.
toString
()
:
""
))
{
// 如果字典转换成功,使用转换后的值
cell
.
setCellValue
(
convertedValue
);
}
else
{
// 如果字典转换失败或没有转换,使用原始值
setCellValue
(
cell
,
value
);
}
}
}
catch
(
Exception
e
)
{
System
.
out
.
println
(
"填充行数据失败: "
+
e
.
getMessage
());
}
}
/**
* 获取字段值(支持getter方法和直接字段访问)
*/
private
static
Object
getFieldValue
(
Class
<?>
clazz
,
Object
item
,
String
fieldName
)
{
try
{
// 首先尝试getter方法
String
getterName
=
"get"
+
fieldName
.
substring
(
0
,
1
).
toUpperCase
()
+
fieldName
.
substring
(
1
);
try
{
return
clazz
.
getMethod
(
getterName
).
invoke
(
item
);
}
catch
(
NoSuchMethodException
e
)
{
// 如果getter方法不存在,尝试直接访问字段
Field
field
=
clazz
.
getDeclaredField
(
fieldName
);
field
.
setAccessible
(
true
);
return
field
.
get
(
item
);
}
}
catch
(
Exception
e
)
{
return
null
;
}
}
/**
* 设置单元格值
*/
private
static
void
setCellValue
(
Cell
cell
,
Object
value
)
{
if
(
value
==
null
)
{
cell
.
setCellValue
(
""
);
}
else
if
(
value
instanceof
String
)
{
cell
.
setCellValue
((
String
)
value
);
}
else
if
(
value
instanceof
Number
)
{
cell
.
setCellValue
(((
Number
)
value
).
doubleValue
());
}
else
if
(
value
instanceof
LocalDateTime
)
{
cell
.
setCellValue
(((
LocalDateTime
)
value
).
format
(
DATE_TIME_FORMATTER
));
}
else
if
(
value
instanceof
java
.
time
.
LocalDate
)
{
cell
.
setCellValue
(((
java
.
time
.
LocalDate
)
value
).
format
(
DATE_FORMATTER
));
}
else
if
(
value
instanceof
Boolean
)
{
cell
.
setCellValue
((
Boolean
)
value
?
"是"
:
"否"
);
}
else
{
cell
.
setCellValue
(
value
.
toString
());
}
}
/**
* 转换字典值 - 使用动态字典转换器
*/
private
static
String
convertDictValue
(
Object
value
,
String
fieldName
)
{
String
result
=
DictValueConverter
.
convert
(
value
,
fieldName
);
// 调试日志:输出字典转换过程
if
(
value
!=
null
&&
!
value
.
toString
().
equals
(
result
))
{
System
.
out
.
println
(
"字典转换: "
+
fieldName
+
" = "
+
value
+
" -> "
+
result
);
}
return
result
;
}
}
frontend/auto-imports.d.ts
View file @
db7a876
...
...
@@ -5,5 +5,5 @@
// Generated by unplugin-auto-import
export
{}
declare
global
{
const
ElMessage
:
typeof
import
(
'element-plus/es'
)[
'ElMessage'
]
}
...
...
frontend/components.d.ts
View file @
db7a876
...
...
@@ -26,6 +26,7 @@ declare module 'vue' {
ElTable
:
typeof
import
(
'element-plus/es'
)[
'ElTable'
]
ElTableColumn
:
typeof
import
(
'element-plus/es'
)[
'ElTableColumn'
]
ElTag
:
typeof
import
(
'element-plus/es'
)[
'ElTag'
]
ElText
:
typeof
import
(
'element-plus/es'
)[
'ElText'
]
Header
:
typeof
import
(
'./src/components/layout/Header.vue'
)[
'default'
]
RouterLink
:
typeof
import
(
'vue-router'
)[
'RouterLink'
]
RouterView
:
typeof
import
(
'vue-router'
)[
'RouterView'
]
...
...
frontend/src/api/delivery.ts
View file @
db7a876
...
...
@@ -120,8 +120,11 @@ export const deliveryApi = {
// 修改出库状态
updateDeliveryStatus
:
(
deliveryId
:
number
,
deliveryStatus
:
number
)
=>
{
return
request
.
post
(
`/api/delivery/
${
deliveryId
}
/deliveryStatus`
,
null
,
{
params
:
{
deliveryStatus
}
})
return
request
.
post
(
`/api/delivery/
${
deliveryId
}
/deliveryStatus`
,
{
deliveryStatus
})
},
// 导出出库数据
exportDeliveries
:
(
params
:
DeliveryQueryReq
)
=>
{
return
request
.
post
(
'/api/delivery/export'
,
params
,
{
responseType
:
'blob'
})
}
}
...
...
frontend/src/api/dict.ts
View file @
db7a876
import
axios
from
'axios
'
import
request
from
'@/utils/request
'
const
API_BASE_URL
=
import
.
meta
.
env
.
VITE_API_BASE_URL
||
'http://localhost:8083'
/**
* 字典管理API
*/
const
api
=
axios
.
create
({
baseURL
:
API_BASE_URL
,
timeout
:
10000
,
headers
:
{
'Content-Type'
:
'application/json'
}
})
api
.
interceptors
.
request
.
use
(
(
config
)
=>
{
const
token
=
localStorage
.
getItem
(
'token'
)
if
(
token
)
{
config
.
headers
.
Authorization
=
`Bearer
${
token
}
`
}
return
config
},
(
error
)
=>
{
return
Promise
.
reject
(
error
)
}
)
api
.
interceptors
.
response
.
use
(
(
response
)
=>
{
return
response
.
data
},
(
error
)
=>
{
console
.
error
(
'API请求错误:'
,
error
)
return
Promise
.
reject
(
error
)
}
)
export
interface
DictType
{
dictTypeId
:
number
dictType
:
string
dictName
:
string
status
:
number
statusText
:
string
remark
?:
string
createBy
?:
string
createTime
:
string
updateBy
?:
string
updateTime
:
string
}
export
interface
DictItem
{
dictItemId
:
number
dictTypeId
:
number
dictType
:
string
dictLabel
:
string
dictValue
:
string
sort
:
number
remark
?:
string
createBy
?:
string
createTime
:
string
updateBy
?:
string
updateTime
:
string
}
export
interface
DictTypeSearchParams
{
dictType
?:
string
dictName
?:
string
status
?:
number
pageNum
?:
number
pageSize
?:
number
}
export
interface
DictItemSearchParams
{
dictTypeId
?:
number
dictLabel
?:
string
dictValue
?:
string
pageNum
?:
number
pageSize
?:
number
}
export
interface
DictTypeAddReq
{
dictType
:
string
dictName
:
string
status
:
number
remark
?:
string
}
export
interface
DictTypeUpdateReq
extends
DictTypeAddReq
{
dictTypeId
:
number
}
export
interface
DictItemAddReq
{
dictTypeId
:
number
dictLabel
:
string
dictValue
:
string
sort
?:
number
remark
?:
string
// 获取字典项
export
const
getDictItems
=
(
dictType
:
string
)
=>
{
return
request
.
get
(
`/api/dict/
${
dictType
}
`
)
}
export
interface
DictItemUpdateReq
extends
DictItemAddReq
{
dictItemId
:
number
// 获取所有字典项
export
const
getAllDictItems
=
()
=>
{
return
request
.
get
(
'/api/dict/all'
)
}
export
interface
ApiResponse
<
T
=
any
>
{
code
:
number
message
:
string
data
:
T
}
export
interface
PageResponse
<
T
>
{
records
:
T
[]
total
:
number
size
:
number
current
:
number
orders
:
any
[]
optimizeCountSql
:
boolean
searchCount
:
boolean
maxLimit
:
any
countId
:
any
pages
:
number
}
export
const
dictApi
=
{
// 字典类型管理
// 获取字典类型列表
getDictTypes
:
(
params
:
DictTypeSearchParams
):
Promise
<
ApiResponse
<
PageResponse
<
DictType
>>>
=>
{
return
api
.
get
(
'/api/system/dict/type/list'
,
{
params
})
},
// 获取字典类型详情
getDictTypeById
:
(
dictTypeId
:
number
):
Promise
<
ApiResponse
<
DictType
>>
=>
{
return
api
.
get
(
`/api/system/dict/type/
${
dictTypeId
}
`
)
},
// 新增字典类型
createDictType
:
(
dictTypeData
:
DictTypeAddReq
):
Promise
<
ApiResponse
<
any
>>
=>
{
return
api
.
post
(
'/api/system/dict/type'
,
dictTypeData
)
},
// 修改字典类型
updateDictType
:
(
dictTypeData
:
DictTypeUpdateReq
):
Promise
<
ApiResponse
<
any
>>
=>
{
return
api
.
put
(
'/api/system/dict/type'
,
dictTypeData
)
},
// 删除字典类型
deleteDictTypes
:
(
dictTypeIds
:
number
[]):
Promise
<
ApiResponse
<
any
>>
=>
{
return
api
.
delete
(
`/api/system/dict/type/
${
dictTypeIds
.
join
(
','
)}
`
)
},
// 获取字典类型选择框列表
getDictTypeOptions
:
():
Promise
<
ApiResponse
<
DictType
[]
>>
=>
{
return
api
.
get
(
'/api/system/dict/type/optionselect'
)
},
// 刷新字典缓存
refreshDictCache
:
():
Promise
<
ApiResponse
<
any
>>
=>
{
return
api
.
delete
(
'/api/system/dict/type/refreshCache'
)
},
// 字典项管理
// 获取字典项列表
getDictItems
:
(
params
:
DictItemSearchParams
):
Promise
<
ApiResponse
<
PageResponse
<
DictItem
>>>
=>
{
return
api
.
get
(
'/api/system/dict/item/list'
,
{
params
})
},
// 根据字典类型获取字典项列表
getDictItemsByType
:
(
dictType
:
string
):
Promise
<
ApiResponse
<
DictItem
[]
>>
=>
{
return
api
.
get
(
`/api/system/dict/item/type/
${
dictType
}
`
)
},
// 获取字典项详情
getDictItemById
:
(
dictItemId
:
number
):
Promise
<
ApiResponse
<
DictItem
>>
=>
{
return
api
.
get
(
`/api/system/dict/item/
${
dictItemId
}
`
)
},
// 新增字典项
createDictItem
:
(
dictItemData
:
DictItemAddReq
):
Promise
<
ApiResponse
<
any
>>
=>
{
return
api
.
post
(
'/api/system/dict/item'
,
dictItemData
)
},
// 修改字典项
updateDictItem
:
(
dictItemData
:
DictItemUpdateReq
):
Promise
<
ApiResponse
<
any
>>
=>
{
return
api
.
put
(
'/api/system/dict/item'
,
dictItemData
)
},
// 删除字典项
deleteDictItems
:
(
dictItemIds
:
number
[]):
Promise
<
ApiResponse
<
any
>>
=>
{
return
api
.
delete
(
`/api/system/dict/item/
${
dictItemIds
.
join
(
','
)}
`
)
},
// 刷新字典缓存
export
const
refreshDictCache
=
()
=>
{
return
request
.
post
(
'/api/dict/refresh'
)
}
\ No newline at end of file
...
...
frontend/src/api/invoice.ts
View file @
db7a876
...
...
@@ -121,8 +121,11 @@ export const invoiceApi = {
return
request
.
post
(
'/api/invoice/batchDelete'
,
invoiceIds
)
},
updateInvoiceStatus
:
(
invoiceId
:
number
,
invoiceStatus
:
number
)
=>
{
return
request
.
post
(
`/api/invoice/
${
invoiceId
}
/invoiceStatus`
,
null
,
{
params
:
{
invoiceStatus
}
})
return
request
.
post
(
`/api/invoice/
${
invoiceId
}
/invoiceStatus`
,
{
invoiceStatus
})
},
// 导出发票数据
exportInvoices
:
(
params
:
InvoiceQueryReq
)
=>
{
return
request
.
post
(
'/api/invoice/export'
,
params
,
{
responseType
:
'blob'
})
}
}
...
...
frontend/src/api/order.ts
View file @
db7a876
...
...
@@ -150,5 +150,10 @@ export const orderApi = {
return
request
.
post
(
`/order/
${
orderId
}
/rebateCalcFlag`
,
null
,
{
params
:
{
rebateCalcFlag
}
})
},
// 导出订单数据
exportOrders
:
(
params
:
OrderQueryReq
)
=>
{
return
request
.
post
(
'/order/export'
,
params
,
{
responseType
:
'blob'
})
}
}
...
...
frontend/src/api/rebate.ts
View file @
db7a876
...
...
@@ -209,6 +209,11 @@ export const getRebateTrendStats = (startDate?: string, endDate?: string) => {
})
}
// 导出返利数据
export
const
exportRebates
=
(
params
:
RebateSearchParams
)
=>
{
return
request
.
post
(
'/api/rebate/export'
,
params
,
{
responseType
:
'blob'
})
}
export
default
{
getRebatePage
,
getRebateById
,
...
...
@@ -227,5 +232,6 @@ export default {
exportRebate
,
getRebateMonthlyStats
,
getRebateStatusStats
,
getRebateTrendStats
getRebateTrendStats
,
exportRebates
}
...
...
frontend/src/layouts/MainLayout.vue
View file @
db7a876
...
...
@@ -72,7 +72,8 @@
<div class="header-right">
<div class="user-info">
<span class="welcome-text">欢迎,{{ userInfo?.username || '用户' }}</span>
<span class="welcome-text" v-if="userInfoLoading">加载中...</span>
<span class="welcome-text" v-else>欢迎,{{ userInfo?.username || '未知' }}</span>
<div class="user-actions">
<button class="logout-btn" @click="handleLogout">退出登录</button>
</div>
...
...
@@ -125,6 +126,7 @@
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { logoutApi } from '../api/auth'
import { request } from '../utils/request'
const router = useRouter()
const route = useRoute()
...
...
@@ -134,6 +136,26 @@ const sidebarCollapsed = ref(false)
// 用户信息
const userInfo = ref<any>(null)
const userInfoLoading = ref(false)
// 获取用户信息
const fetchUserInfo = async () => {
try {
userInfoLoading.value = true
const response = await request.get('/api/auth/userinfo')
userInfo.value = response
console.log('顶部栏用户信息:', response)
} catch (error) {
console.error('获取用户信息失败:', error)
// 如果获取失败,尝试从本地存储获取
const storedUserInfo = localStorage.getItem('userInfo')
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo)
}
} finally {
userInfoLoading.value = false
}
}
// 标签页容器引用
const tabContainer = ref<HTMLElement>()
...
...
@@ -371,10 +393,18 @@ watch(() => route.path, (newPath) => {
// 组件挂载时获取用户信息
onMounted(() => {
// 检查是否已登录
const token = localStorage.getItem('token')
if (token) {
// 调用API获取用户信息
fetchUserInfo()
} else {
// 如果没有token,尝试从本地存储获取
const storedUserInfo = localStorage.getItem('userInfo')
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo)
}
}
})
</script>
...
...
frontend/src/main.ts
View file @
db7a876
import
{
createApp
}
from
'vue'
import
{
createPinia
}
from
'pinia'
import
App
from
'./App.vue'
import
ElementPlus
from
'element-plus'
import
'element-plus/dist/index.css'
import
router
from
'./router'
...
...
@@ -10,5 +12,6 @@ const pinia = createPinia()
app
.
use
(
pinia
)
app
.
use
(
router
)
app
.
use
(
ElementPlus
)
// 挂载应用
app
.
mount
(
'#app'
)
...
...
frontend/src/utils/request.ts
View file @
db7a876
...
...
@@ -46,6 +46,11 @@ service.interceptors.request.use(
// 响应拦截器
service
.
interceptors
.
response
.
use
(
(
response
:
AxiosResponse
)
=>
{
// 如果是blob响应(文件下载),直接返回
if
(
response
.
config
.
responseType
===
'blob'
)
{
return
response
.
data
}
const
{
code
,
message
,
data
}
=
response
.
data
// 请求成功
...
...
@@ -121,8 +126,8 @@ export const request = {
return
service
.
get
(
url
,
{
params
})
},
post
<
T
=
any
>
(
url
:
string
,
data
?:
any
):
Promise
<
T
>
{
return
service
.
post
(
url
,
data
)
post
<
T
=
any
>
(
url
:
string
,
data
?:
any
,
config
?:
any
):
Promise
<
T
>
{
return
service
.
post
(
url
,
data
,
config
)
},
put
<
T
=
any
>
(
url
:
string
,
data
?:
any
):
Promise
<
T
>
{
...
...
frontend/src/views/dashboard/index.vue
View file @
db7a876
...
...
@@ -2,7 +2,8 @@
<div class="dashboard-container">
<div class="dashboard-header">
<h2>系统概览</h2>
<p>欢迎回来,{{ userInfo?.username || '用户' }}!</p>
<p v-if="loading">正在加载用户信息...</p>
<p v-else>欢迎回来,{{ userInfo?.username || '未知' }}!</p>
</div>
<div class="dashboard-stats">
...
...
@@ -45,11 +46,13 @@
<div class="info-grid">
<div class="info-item">
<label>用户名:</label>
<span>{{ userInfo?.username || '未知' }}</span>
<span v-if="loading">加载中...</span>
<span v-else>{{ userInfo?.username || '未知' }}</span>
</div>
<div class="info-item">
<label>角色:</label>
<span>{{ userInfo?.roles?.[0]?.roleName || '普通用户' }}</span>
<span v-if="loading">加载中...</span>
<span v-else>{{ getRoleName(userInfo) || '普通用户' }}</span>
</div>
<div class="info-item">
<label>登录时间:</label>
...
...
@@ -65,10 +68,10 @@
<div class="dashboard-card">
<h3>快速操作</h3>
<div class="quick-actions">
<button class="action-btn">👤 用户管理</button>
<button class="action-btn">🛡️ 角色管理</button>
<button class="action-btn">⚙️ 系统设置</button>
<button class="action-btn">📊 查看日志</button>
<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>
</div>
</div>
...
...
@@ -78,28 +81,85 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { request } from '@/utils/request'
const router = useRouter()
const currentTime = ref('')
const userInfo = ref<any>(null)
const loading = ref(false)
onMounted(() => {
// 获取当前时间
currentTime.value = new Date().toLocaleString()
// 获取角色名称
const getRoleName = (userInfo: any) => {
// 优先使用roles字段中的角色信息
if (userInfo?.roles && Array.isArray(userInfo.roles) && userInfo.roles.length > 0) {
return userInfo.roles[0].roleName || '普通用户'
}
// 获取用户信息
// 如果没有roles字段,回退到authorities
const authorities = userInfo?.authorities
if (authorities && Array.isArray(authorities)) {
// 查找ROLE_开头的权限
const roleAuthority = authorities.find(auth =>
auth.authority && auth.authority.startsWith('ROLE_')
)
if (roleAuthority) {
// 移除ROLE_前缀并转换为中文
const roleName = roleAuthority.authority.replace('ROLE_', '')
const roleMap: { [key: string]: string } = {
'ADMIN': '管理员',
'USER': '普通用户',
'MANAGER': '经理',
'OPERATOR': '操作员'
}
return roleMap[roleName] || roleName
}
}
return '普通用户'
}
// 获取用户信息
const fetchUserInfo = async () => {
try {
loading.value = true
const response = await request.get('/api/auth/userinfo')
userInfo.value = response
console.log('用户信息:', response)
} catch (error) {
console.error('获取用户信息失败:', error)
// 如果获取失败,尝试从本地存储获取
const storedUserInfo = localStorage.getItem('userInfo')
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo)
}
} finally {
loading.value = false
}
}
onMounted(() => {
// 获取当前时间
currentTime.value = new Date().toLocaleString()
// 检查是否已登录
const token = localStorage.getItem('token')
if (!token) {
router.push('/')
return
}
// 获取用户信息
fetchUserInfo()
})
// 页面跳转函数
const navigateTo = (path: string) => {
router.push(path).catch(err => {
console.error('页面跳转失败:', err)
})
}
const logout = () => {
// 清除本地存储
localStorage.removeItem('token')
...
...
frontend/src/views/delivery/index.vue
View file @
db7a876
...
...
@@ -179,15 +179,11 @@
<div class="detail-grid">
<div class="detail-item">
<label>出库单ID:</label>
<span>{{
order
Detail.deliveryId }}</span>
<span>{{
delivery
Detail.deliveryId }}</span>
</div>
<div class="detail-item">
<label>出库单编号:</label>
<span>{{ orderDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
<span>{{ orderDetail.orderNo }}</span>
<span>{{ deliveryDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
...
...
@@ -203,7 +199,7 @@
</div>
<div class="detail-item">
<label>出库日期:</label>
<span>{{ formatDate(
order
Detail.deliveryDate) }}</span>
<span>{{ formatDate(
delivery
Detail.deliveryDate) }}</span>
</div>
<div class="detail-item">
<label>出库状态:</label>
...
...
@@ -213,28 +209,28 @@
</div>
<div class="detail-item">
<label>仓库编码:</label>
<span>{{
order
Detail.warehouseCode }}</span>
<span>{{
delivery
Detail.warehouseCode }}</span>
</div>
<div class="detail-item">
<label>数据来源:</label>
<span>{{
order
Detail.dataSource || '-' }}</span>
<span>{{
delivery
Detail.dataSource || '-' }}</span>
</div>
<div class="detail-item">
<label>上传时间:</label>
<span>{{ formatTime(
order
Detail.uploadTime || '') }}</span>
<span>{{ formatTime(
delivery
Detail.uploadTime || '') }}</span>
</div>
<div class="detail-item">
<label>创建时间:</label>
<span>{{ formatTime(
order
Detail.createTime || '') }}</span>
<span>{{ formatTime(
delivery
Detail.createTime || '') }}</span>
</div>
<div class="detail-item">
<label>更新时间:</label>
<span>{{ formatTime(
order
Detail.updateTime || '') }}</span>
<span>{{ formatTime(
delivery
Detail.updateTime || '') }}</span>
</div>
</div>
</div>
<div class="detail-section" v-if="
orderDetail.deliveryItems && order
Detail.deliveryItems.length > 0">
<div class="detail-section" v-if="
deliveryDetail?.deliveryItems && delivery
Detail.deliveryItems.length > 0">
<h4>出库明细</h4>
<table class="detail-table">
<thead>
...
...
@@ -247,7 +243,7 @@
</tr>
</thead>
<tbody>
<tr v-for="item in
orderDetail.deliveryItems" :key="item.deliveryItem
Id">
<tr v-for="item in
deliveryDetail.deliveryItems" :key="item.itemId || item.delivery
Id">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.deliveryQty }}</td>
...
...
@@ -433,6 +429,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery'
// 响应式数据
...
...
@@ -518,7 +515,12 @@ const formatCurrency = (amount: number) => {
// 消息提示
const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
console.log(`${type}: ${message}`)
alert(message)
ElMessage({
message,
type,
duration: 3000,
showClose: true
})
}
// 获取页码数组
...
...
@@ -612,8 +614,78 @@ const handleDelete = async (delivery: DeliveryInfo) => {
}
const handleExport = () => {
showMessage('导出功能开发中...', 'warning')
const handleExport = async () => {
try {
// 显示确认弹窗
await ElMessageBox.confirm(
'确定要导出出库数据吗?导出将包含当前筛选条件下的所有数据。',
'确认导出',
{
confirmButtonText: '确定导出',
cancelButtonText: '取消',
type: 'warning',
center: true
}
)
// 用户确认后显示加载提示
const loadingMessage = ElMessage({
message: '正在导出数据,请稍候...',
type: 'warning',
duration: 0, // 不自动关闭
showClose: false
})
try {
// 准备导出参数
const exportParams = {
deliveryNo: searchParams.deliveryNo,
dealerCode: searchParams.dealerCode,
dealerName: searchParams.dealerName,
deliveryStatus: searchParams.deliveryStatus,
warehouseCode: searchParams.warehouseCode,
dataSource: searchParams.dataSource,
deliveryStartDate: searchParams.deliveryStartDate,
deliveryEndDate: searchParams.deliveryEndDate
}
// 调用导出接口
const response = await deliveryApi.exportDeliveries(exportParams)
// 创建下载链接
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 生成文件名
const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
link.download = `出库数据_${timestamp}.xlsx`
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 关闭加载提示,显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!文件已开始下载')
} catch (exportError) {
// 关闭加载提示
loadingMessage.close()
throw exportError
}
} catch (error) {
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败,请重试')
}
}
}
// 新增出库相关函数
...
...
@@ -680,9 +752,9 @@ const handleSubmitAdd = async () => {
deliveryItems: addFormData.deliveryItems.map(item => ({
productCode: item.productCode,
productName: item.productName,
product
Qty: item.deliveryQty,
unit
Price: item.deliveryPrice,
totalPrice
: item.deliveryAmount
delivery
Qty: item.deliveryQty,
delivery
Price: item.deliveryPrice,
deliveryAmount
: item.deliveryAmount
}))
}
...
...
frontend/src/views/invoice/index.vue
View file @
db7a876
...
...
@@ -507,6 +507,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice'
// 响应式数据
...
...
@@ -594,7 +595,12 @@ const formatCurrency = (amount: number) => {
// 消息提示
const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
console.log(`${type}: ${message}`)
alert(message)
ElMessage({
message,
type,
duration: 3000,
showClose: true
})
}
// 获取页码数组
...
...
@@ -825,8 +831,81 @@ const handleView = async (order: InvoiceInfo) => {
}
const handleExport = () => {
showMessage('导出功能开发中...', 'warning')
const handleExport = async () => {
try {
// 显示确认弹窗
await ElMessageBox.confirm(
'确定要导出发票数据吗?导出将包含当前筛选条件下的所有数据。',
'确认导出',
{
confirmButtonText: '确定导出',
cancelButtonText: '取消',
type: 'warning',
center: true
}
)
// 用户确认后显示加载提示
const loadingMessage = ElMessage({
message: '正在导出数据,请稍候...',
type: 'warning',
duration: 0, // 不自动关闭
showClose: false
})
try {
// 准备导出参数
const exportParams = {
invoiceNo: searchParams.invoiceNo,
orderNo: searchParams.orderNo,
deliveryNo: searchParams.deliveryNo,
dealerCode: searchParams.dealerCode,
dealerName: searchParams.dealerName,
invoiceStatus: searchParams.invoiceStatus,
dataSource: searchParams.dataSource,
invoiceStartDate: searchParams.invoiceStartDate,
invoiceEndDate: searchParams.invoiceEndDate,
minAmount: searchParams.minAmount,
maxAmount: searchParams.maxAmount
}
// 调用导出接口
const response = await invoiceApi.exportInvoices(exportParams)
// 创建下载链接
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 生成文件名
const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
link.download = `发票数据_${timestamp}.xlsx`
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 关闭加载提示,显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!文件已开始下载')
} catch (exportError) {
// 关闭加载提示
loadingMessage.close()
throw exportError
}
} catch (error) {
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败,请重试')
}
}
}
const handlePrintInvoice = (invoice: InvoiceInfo) => {
...
...
frontend/src/views/order/index.vue
View file @
db7a876
...
...
@@ -447,6 +447,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order'
// 响应式数据
...
...
@@ -586,9 +587,12 @@ const formatCurrency = (amount: number) => {
// 消息提示
const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
// 这里可以集成消息提示组件
console.log(`${type}: ${message}`)
alert(message)
ElMessage({
message,
type,
duration: 3000,
showClose: true
})
}
// 方法
...
...
@@ -757,8 +761,82 @@ const handleBatchDelete = async () => {
}
const handleExport = () => {
showMessage('导出功能开发中...', 'warning')
const handleExport = async () => {
try {
// 显示确认弹窗
await ElMessageBox.confirm(
'确定要导出订单数据吗?导出将包含当前筛选条件下的所有数据。',
'确认导出',
{
confirmButtonText: '确定导出',
cancelButtonText: '取消',
type: 'warning',
center: true
}
)
// 用户确认后显示加载提示
const loadingMessage = ElMessage({
message: '正在导出数据,请稍候...',
type: 'warning',
duration: 0, // 不自动关闭
showClose: false
})
try {
// 准备导出参数
const exportParams = {
orderNo: searchParams.orderNo,
dealerCode: searchParams.dealerCode,
dealerName: searchParams.dealerName,
deliveryStatus: searchParams.deliveryStatus,
invoiceStatus: searchParams.invoiceStatus,
rebateCalcFlag: searchParams.rebateCalcFlag,
dataSource: searchParams.dataSource,
verifyStatus: searchParams.verifyStatus,
orderStartDate: searchParams.orderStartDate,
orderEndDate: searchParams.orderEndDate,
minAmount: searchParams.minAmount,
maxAmount: searchParams.maxAmount
}
// 调用导出接口
const response = await orderApi.exportOrders(exportParams)
// 创建下载链接
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 生成文件名
const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
link.download = `订单数据_${timestamp}.xlsx`
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 关闭加载提示,显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!文件已开始下载')
} catch (exportError) {
// 关闭加载提示
loadingMessage.close()
throw exportError
}
} catch (error) {
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败,请重试')
}
}
}
const handleSelectAll = () => {
...
...
frontend/src/views/rebate/index.vue
View file @
db7a876
...
...
@@ -61,11 +61,11 @@
<div class="chart-legend">
<div class="legend-item">
<span class="legend-dot" style="background-color: #4CAF50;"></span>
<span>返利
金额
</span>
<span>返利
合计
</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background-color: #2196F3;"></span>
<span>已
审核
返利</span>
<span>已
使用
返利</span>
</div>
</div>
</div>
...
...
@@ -77,15 +77,15 @@
<!-- 右侧饼图 -->
<div class="chart-card pie-chart">
<div class="chart-header">
<h3 class="chart-title">
计算
统计</h3>
<h3 class="chart-title">
返利
统计</h3>
<div class="chart-legend">
<div class="legend-item">
<span class="legend-dot" style="background-color: #2196F3;"></span>
<span>
已计算
</span>
<span>
剩余返利
</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background-color: #
4CAF50
;"></span>
<span>
未计算
</span>
<span class="legend-dot" style="background-color: #
9CCC65
;"></span>
<span>
已使用返利
</span>
</div>
</div>
</div>
...
...
@@ -102,10 +102,16 @@
<div class="table-title">
返利记录
</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>
</div>
<el-table
v-loading="loading"
...
...
@@ -384,12 +390,13 @@ import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import rebateApi, { type Rebate, type RebateSearchParams } from '@/api/rebate'
import rebateApi, { type Rebate, type RebateSearchParams
, exportRebates
} from '@/api/rebate'
import { formatDate } from '@/utils/index'
// 响应式数据
const loading = ref(false)
const submitLoading = ref(false)
const exportLoading = ref(false)
const auditLoading = ref(false)
const rebateList = ref<Rebate[]>([])
const selectedRebates = ref<Rebate[]>([])
...
...
@@ -1039,13 +1046,34 @@ const handleBatchRelease = async () => {
// 导出
const handleExport = async () => {
try {
loading.value = true
// 显示确认弹窗
await ElMessageBox.confirm(
'确定要导出返利数据吗?导出将包含当前筛选条件下的所有数据。',
'确认导出',
{
confirmButtonText: '确定导出',
cancelButtonText: '取消',
type: 'warning',
center: true
}
)
// 用户确认后显示加载提示
const loadingMessage = ElMessage({
message: '正在导出数据,请稍候...',
type: 'warning',
duration: 0, // 不自动关闭
showClose: false
})
try {
exportLoading.value = true
const params = { ...searchForm }
const response = await rebateApi.exportRebate
(params)
const response = await exportRebates
(params)
// 创建下载链接
const blob = new Blob([response.data
], {
const blob = new Blob([response as unknown as BlobPart
], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
...
...
@@ -1057,12 +1085,23 @@ const handleExport = async () => {
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
// 关闭加载提示,显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!文件已开始下载')
} catch (exportError) {
// 关闭加载提示
loadingMessage.close()
throw exportError
}
} catch (error) {
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败')
ElMessage.error('导出失败,请重试')
}
} finally {
l
oading.value = false
exportL
oading.value = false
}
}
...
...
@@ -1073,27 +1112,30 @@ const initTrendChart = async () => {
const chart = echarts.init(trendChart.value)
try {
// 调用后端API获取月度统计数据
const response = await rebateApi.getRebateMonthlyStats()
let monthlyData = []
// 调用后端API获取月度统计数据 - 查询2025年的数据
const response = await rebateApi.getRebateMonthlyStats(2025)
console.log('月度统计API响应:', response)
let monthlyData: any[] = []
if (response && response.data) {
monthlyData = response.data
// request.ts拦截器已经返回了data字段,所以response就是数据数组
if (response && Array.isArray(response)) {
monthlyData = response
}
console.log('处理后的月度数据:', monthlyData)
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
// 处理后端返回的月度数据
const total
Trend
Data = months.map((month, index) => {
const total
Rebate
Data = months.map((month, index) => {
const monthIndex = index + 1
const monthData = monthlyData.find((item: any) => item.month === monthIndex)
return monthData ? monthData.totalAmount : 0
})
const
calculatedTrend
Data = months.map((month, index) => {
const
usedRebate
Data = months.map((month, index) => {
const monthIndex = index + 1
const monthData = monthlyData.find((item: any) => item.month === monthIndex)
return monthData ?
monthData.calculatedAmount
: 0
return monthData ?
(monthData.calculatedAmount || 0)
: 0
})
const option = {
...
...
@@ -1143,10 +1185,10 @@ const initTrendChart = async () => {
},
series: [
{
name: '返利
金额
',
name: '返利
合计
',
type: 'line',
smooth: true,
data: total
Trend
Data,
data: total
Rebate
Data,
itemStyle: { color: '#4CAF50' },
lineStyle: {
color: '#4CAF50',
...
...
@@ -1157,10 +1199,10 @@ const initTrendChart = async () => {
showSymbol: true
},
{
name: '已
审核
返利',
name: '已
使用
返利',
type: 'line',
smooth: true,
data:
calculatedTrend
Data,
data:
usedRebate
Data,
itemStyle: { color: '#2196F3' },
lineStyle: {
color: '#2196F3',
...
...
@@ -1185,8 +1227,8 @@ const initTrendChart = async () => {
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
const total
TrendData
= months.map(() => Math.floor(totalAmount / 12))
const
calculatedTrendData
= months.map(() => Math.floor(calculatedAmount / 12))
const total
RebateDataFallback
= months.map(() => Math.floor(totalAmount / 12))
const
usedRebateDataFallback
= months.map(() => Math.floor(calculatedAmount / 12))
const fallbackOption = {
tooltip: {
...
...
@@ -1235,10 +1277,10 @@ const initTrendChart = async () => {
},
series: [
{
name: '返利
金额
',
name: '返利
合计
',
type: 'line',
smooth: true,
data: total
TrendData
,
data: total
RebateDataFallback
,
itemStyle: { color: '#4CAF50' },
lineStyle: {
color: '#4CAF50',
...
...
@@ -1249,10 +1291,10 @@ const initTrendChart = async () => {
showSymbol: true
},
{
name: '已
审核
返利',
name: '已
使用
返利',
type: 'line',
smooth: true,
data:
calculatedTrendData
,
data:
usedRebateDataFallback
,
itemStyle: { color: '#2196F3' },
lineStyle: {
color: '#2196F3',
...
...
@@ -1280,33 +1322,42 @@ const initPieChart = async () => {
try {
// 调用后端API获取状态统计数据
const response = await rebateApi.getRebateStatusStats()
let statusData = { calculatedCount: 0, unCalculatedCount: 0 }
console.log('状态统计API响应:', response)
let statusData: any = { calculatedAmount: 0, unCalculatedAmount: 0 }
if (response && response.data) {
statusData = response.data
// request.ts拦截器已经返回了data字段,所以response就是数据对象
if (response && typeof response === 'object') {
statusData = response
}
console.log('处理后的状态数据:', statusData)
// 计算剩余返利和已使用返利
const usedRebate = statusData.calculatedAmount || 0
const remainingRebate = statusData.unCalculatedAmount || 0
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
formatter: (params: any) => {
return `${params.name}: ¥${params.value.toLocaleString()} (${params.percent}%)`
}
},
series: [
{
name: '
审核状态
',
name: '
返利统计
',
type: 'pie',
radius: ['35%', '65%'],
center: ['50%', '50%'],
data: [
{
value:
statusData.calculatedCount || 0
,
name: '
已计算
',
value:
remainingRebate
,
name: '
剩余返利
',
itemStyle: { color: '#2196F3' }
},
{
value:
statusData.unCalculatedCount || 0
,
name: '
未计算
',
itemStyle: { color: '#
4CAF50
' }
value:
usedRebate
,
name: '
已使用返利
',
itemStyle: { color: '#
9CCC65
' }
}
],
label: {
...
...
@@ -1339,30 +1390,36 @@ const initPieChart = async () => {
console.error('获取饼图数据失败:', error)
// 如果API调用失败,使用当前列表数据作为备用方案
const calculatedCount = rebateList.value.filter(item => item.calcFlag === 1).length
const unCalculatedCount = rebateList.value.filter(item => item.calcFlag === 0).length
const usedAmount = rebateList.value
.filter(item => item.calcFlag === 1)
.reduce((sum, item) => sum + (item.rebateAmount || 0), 0)
const remainingAmount = rebateList.value
.filter(item => item.calcFlag === 0)
.reduce((sum, item) => sum + (item.rebateAmount || 0), 0)
const fallbackOption = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
formatter: (params: any) => {
return `${params.name}: ¥${params.value.toLocaleString()} (${params.percent}%)`
}
},
series: [
{
name: '
审核状态
',
name: '
返利统计
',
type: 'pie',
radius: ['35%', '65%'],
center: ['50%', '50%'],
data: [
{
value:
calculatedC
ount,
name: '
已审核
',
value:
remainingAm
ount,
name: '
剩余返利
',
itemStyle: { color: '#2196F3' }
},
{
value: u
nCalculatedC
ount,
name: '
未审核
',
itemStyle: { color: '#
4CAF50
' }
value: u
sedAm
ount,
name: '
已使用返利
',
itemStyle: { color: '#
9CCC65
' }
}
],
label: {
...
...
@@ -1540,11 +1597,17 @@ onMounted(async () => {
color: #333;
}
.table-actions {
display: flex;
align-items: center;
gap: 12px;
.table-info {
font-size: 12px;
color: #999;
}
}
}
.el-table {
border: none;
...
...
frontend/src/views/settings/index.vue
0 → 100644
View file @
db7a876
<template>
<div class="settings-container">
<!-- 页面标题 -->
<div class="page-header">
<h2>系统设置</h2>
<p>管理系统配置和参数</p>
</div>
<!-- 设置内容 -->
<div class="settings-content">
<div class="settings-card">
<h3>基本设置</h3>
<div class="setting-item">
<label>系统名称:</label>
<input v-model="settings.systemName" class="setting-input" placeholder="请输入系统名称" />
</div>
<div class="setting-item">
<label>系统版本:</label>
<input v-model="settings.systemVersion" class="setting-input" placeholder="请输入系统版本" />
</div>
<div class="setting-item">
<label>系统描述:</label>
<textarea v-model="settings.systemDescription" class="setting-textarea" placeholder="请输入系统描述"></textarea>
</div>
</div>
<div class="settings-card">
<h3>业务设置</h3>
<div class="setting-item">
<label>默认分页大小:</label>
<select v-model="settings.defaultPageSize" class="setting-select">
<option value="10">10条/页</option>
<option value="20">20条/页</option>
<option value="50">50条/页</option>
<option value="100">100条/页</option>
</select>
</div>
<div class="setting-item">
<label>数据保留天数:</label>
<input v-model="settings.dataRetentionDays" type="number" class="setting-input" placeholder="请输入数据保留天数" />
</div>
<div class="setting-item">
<label>自动备份:</label>
<label class="checkbox-label">
<input v-model="settings.autoBackup" type="checkbox" />
<span>启用自动备份</span>
</label>
</div>
</div>
<div class="settings-card">
<h3>安全设置</h3>
<div class="setting-item">
<label>会话超时时间(分钟):</label>
<input v-model="settings.sessionTimeout" type="number" class="setting-input" placeholder="请输入会话超时时间" />
</div>
<div class="setting-item">
<label>密码复杂度:</label>
<select v-model="settings.passwordComplexity" class="setting-select">
<option value="low">低</option>
<option value="medium">中</option>
<option value="high">高</option>
</select>
</div>
<div class="setting-item">
<label>登录失败锁定:</label>
<label class="checkbox-label">
<input v-model="settings.loginLock" type="checkbox" />
<span>启用登录失败锁定</span>
</label>
</div>
</div>
<div class="settings-card">
<h3>通知设置</h3>
<div class="setting-item">
<label>邮件通知:</label>
<label class="checkbox-label">
<input v-model="settings.emailNotification" type="checkbox" />
<span>启用邮件通知</span>
</label>
</div>
<div class="setting-item">
<label>短信通知:</label>
<label class="checkbox-label">
<input v-model="settings.smsNotification" type="checkbox" />
<span>启用短信通知</span>
</label>
</div>
<div class="setting-item">
<label>系统消息:</label>
<label class="checkbox-label">
<input v-model="settings.systemMessage" type="checkbox" />
<span>启用系统消息</span>
</label>
</div>
</div>
<!-- 操作按钮 -->
<div class="settings-actions">
<button @click="handleSave" class="action-btn primary">💾 保存设置</button>
<button @click="handleReset" class="action-btn secondary">🔄 重置</button>
<button @click="handleTest" class="action-btn info">🧪 测试连接</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
// 设置数据
const settings = reactive({
systemName: 'Apple ERP系统',
systemVersion: '1.0.0',
systemDescription: '企业资源规划管理系统',
defaultPageSize: 20,
dataRetentionDays: 365,
autoBackup: true,
sessionTimeout: 30,
passwordComplexity: 'medium',
loginLock: true,
emailNotification: true,
smsNotification: false,
systemMessage: true
})
// 原始设置(用于重置)
const originalSettings = ref({})
// 保存设置
const handleSave = () => {
// 这里可以调用后端API保存设置
console.log('保存设置:', settings)
showMessage('设置保存成功', 'success')
}
// 重置设置
const handleReset = () => {
Object.assign(settings, originalSettings.value)
showMessage('设置已重置', 'warning')
}
// 测试连接
const handleTest = () => {
showMessage('连接测试成功', 'success')
}
// 显示消息
const showMessage = (message: string, type: 'success' | 'warning' | 'error') => {
// 这里可以集成Element Plus的消息组件
console.log(`${type}: ${message}`)
}
// 组件挂载时加载设置
onMounted(() => {
// 这里可以调用后端API加载设置
originalSettings.value = { ...settings }
})
</script>
<style scoped>
.settings-container {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
.page-header {
margin-bottom: 30px;
text-align: center;
}
.page-header h2 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.page-header p {
color: #666;
font-size: 16px;
}
.settings-content {
max-width: 1200px;
margin: 0 auto;
}
.settings-card {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.settings-card h3 {
color: #333;
margin-bottom: 20px;
font-size: 18px;
border-bottom: 2px solid #e9ecef;
padding-bottom: 10px;
}
.setting-item {
display: flex;
align-items: center;
margin-bottom: 20px;
gap: 16px;
}
.setting-item label {
min-width: 150px;
color: #333;
font-weight: 500;
}
.setting-input,
.setting-select,
.setting-textarea {
flex: 1;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s;
}
.setting-input:focus,
.setting-select:focus,
.setting-textarea:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.setting-textarea {
min-height: 80px;
resize: vertical;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.checkbox-label input[type="checkbox"] {
width: 16px;
height: 16px;
}
.settings-actions {
display: flex;
gap: 16px;
justify-content: center;
margin-top: 30px;
}
.action-btn {
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
min-width: 120px;
}
.action-btn.primary {
background-color: #007bff;
color: white;
}
.action-btn.primary:hover {
background-color: #0056b3;
}
.action-btn.secondary {
background-color: #6c757d;
color: white;
}
.action-btn.secondary:hover {
background-color: #545b62;
}
.action-btn.info {
background-color: #17a2b8;
color: white;
}
.action-btn.info:hover {
background-color: #138496;
}
@media (max-width: 768px) {
.setting-item {
flex-direction: column;
align-items: flex-start;
}
.setting-item label {
min-width: auto;
margin-bottom: 8px;
}
.settings-actions {
flex-direction: column;
align-items: center;
}
}
</style>
frontend/tsconfig.json
View file @
db7a876
...
...
@@ -7,7 +7,7 @@
"auto-imports.d.ts"
,
"components.d.ts"
],
"exclude"
:
[
"src/**/__tests__/*"
],
"exclude"
:
[
"src/**/__tests__/*"
,
"src/views/test-menu.vue"
],
"compilerOptions"
:
{
"composite"
:
true
,
"baseUrl"
:
"."
,
...
...
Please
register
or
login
to post a comment