zhouhui.jiang

update 基础数据添加

Showing 32 changed files with 6314 additions and 37 deletions
......@@ -667,8 +667,709 @@
- 超级管理员状态不允许修改
- 所有操作都会记录操作日志
## 7. 产品管理 (ProductInfoController)
### 7.1 分页查询产品列表
**接口路径:** `GET /api/system/product/list`
**功能描述:** 根据条件分页查询产品列表
**权限要求:** `product:list`
**请求参数:**
| 参数名 | 类型 | 必填 | 描述 | 示例 |
|--------|------|------|------|------|
| productCode | String | 否 | 产品编码 | APL-IP15-128G-BK |
| productName | String | 否 | 产品名称 | iPhone 15 |
| productModel | String | 否 | 产品型号 | A2848 |
| productType | String | 否 | 产品类别 | iPhone |
| storageCapacity | String | 否 | 存储容量 | 128GB |
| color | String | 否 | 产品颜色 | 黑色 |
| saleStatus | Integer | 否 | 销售状态(0-下架/1-在售/2-预售) | 1 |
| rebateFlag | Integer | 否 | 是否参与返利(0-否/1-是) | 1 |
| minPrice | BigDecimal | 否 | 最低价格 | 1000.00 |
| maxPrice | BigDecimal | 否 | 最高价格 | 10000.00 |
| saleStartDateBegin | String | 否 | 销售起始日期开始 | 2024-01-01 |
| saleStartDateEnd | String | 否 | 销售起始日期结束 | 2024-12-31 |
| saleEndDateBegin | String | 否 | 销售终止日期开始 | 2024-01-01 |
| saleEndDateEnd | String | 否 | 销售终止日期结束 | 2024-12-31 |
| pageNum | Integer | 否 | 页码 | 1 |
| pageSize | Integer | 否 | 每页大小 | 10 |
**响应示例:**
```json
{
"code": 200,
"message": "操作成功",
"data": {
"records": [
{
"productId": 1,
"productCode": "APL-IP15-128G-BK",
"productName": "iPhone 15",
"productModel": "A2848",
"productType": "iPhone",
"storageCapacity": "128GB",
"color": "黑色",
"productImgUrl": "https://example.com/iphone15.jpg",
"officialPrice": 5999.00,
"saleStatus": 1,
"rebateFlag": 1,
"saleStartDate": "2023-09-15",
"saleEndDate": "2024-12-31",
"remark": "iPhone 15 128GB 黑色",
"createBy": "admin",
"createTime": "2024-01-15 10:30:00",
"updateBy": "admin",
"updateTime": "2024-01-15 10:30:00",
"delFlag": "0"
}
],
"total": 1,
"size": 10,
"current": 1,
"pages": 1
}
}
```
### 7.2 获取产品详情
**接口路径:** `GET /api/system/product/{productId}`
**功能描述:** 根据产品ID获取产品详情
**权限要求:** `product:detail`
**路径参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| productId | Long | 是 | 产品ID |
**响应示例:**
```json
{
"code": 200,
"message": "操作成功",
"data": {
"productId": 1,
"productCode": "APL-IP15-128G-BK",
"productName": "iPhone 15",
"productModel": "A2848",
"productType": "iPhone",
"storageCapacity": "128GB",
"color": "黑色",
"productImgUrl": "https://example.com/iphone15.jpg",
"officialPrice": 5999.00,
"saleStatus": 1,
"rebateFlag": 1,
"saleStartDate": "2023-09-15",
"saleEndDate": "2024-12-31",
"remark": "iPhone 15 128GB 黑色",
"createBy": "admin",
"createTime": "2024-01-15 10:30:00",
"updateBy": "admin",
"updateTime": "2024-01-15 10:30:00",
"delFlag": "0"
}
}
```
### 7.3 新增产品
**接口路径:** `POST /api/system/product`
**功能描述:** 新增产品信息
**权限要求:** `product:add`
**请求体参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| productCode | String | 是 | 产品编码 |
| productName | String | 是 | 产品名称 |
| productModel | String | 是 | 产品型号 |
| productType | String | 是 | 产品类别 |
| storageCapacity | String | 否 | 存储容量 |
| color | String | 否 | 产品颜色 |
| productImgUrl | String | 否 | 产品图片URL |
| officialPrice | BigDecimal | 否 | 官方指导价 |
| saleStatus | Integer | 是 | 销售状态(0-下架/1-在售/2-预售) |
| rebateFlag | Integer | 是 | 是否参与返利(0-否/1-是) |
| saleStartDate | String | 否 | 销售起始日期 |
| saleEndDate | String | 否 | 销售终止日期 |
| remark | String | 否 | 产品备注 |
**请求示例:**
```json
{
"productCode": "APL-IP15-128G-BK",
"productName": "iPhone 15",
"productModel": "A2848",
"productType": "iPhone",
"storageCapacity": "128GB",
"color": "黑色",
"productImgUrl": "https://example.com/iphone15.jpg",
"officialPrice": 5999.00,
"saleStatus": 1,
"rebateFlag": 1,
"saleStartDate": "2023-09-15",
"saleEndDate": "2024-12-31",
"remark": "iPhone 15 128GB 黑色"
}
```
**响应示例:**
```json
{
"code": 200,
"message": "新增产品成功",
"data": null
}
```
### 7.4 修改产品
**接口路径:** `POST /api/product/update`
**功能描述:** 修改产品信息
**权限要求:** `product:edit`
**请求体参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| productId | Long | 是 | 产品ID |
| productCode | String | 是 | 产品编码 |
| productName | String | 是 | 产品名称 |
| productModel | String | 是 | 产品型号 |
| productType | String | 是 | 产品类别 |
| storageCapacity | String | 否 | 存储容量 |
| color | String | 否 | 产品颜色 |
| productImgUrl | String | 否 | 产品图片URL |
| officialPrice | BigDecimal | 否 | 官方指导价 |
| saleStatus | Integer | 是 | 销售状态(0-下架/1-在售/2-预售) |
| rebateFlag | Integer | 是 | 是否参与返利(0-否/1-是) |
| saleStartDate | String | 否 | 销售起始日期 |
| saleEndDate | String | 否 | 销售终止日期 |
| remark | String | 否 | 产品备注 |
**请求示例:**
```json
{
"productId": 1,
"productCode": "APL-IP15-128G-BK",
"productName": "iPhone 15",
"productModel": "A2848",
"productType": "iPhone",
"storageCapacity": "128GB",
"color": "黑色",
"productImgUrl": "https://example.com/iphone15.jpg",
"officialPrice": 5999.00,
"saleStatus": 1,
"rebateFlag": 1,
"saleStartDate": "2023-09-15",
"saleEndDate": "2024-12-31",
"remark": "iPhone 15 128GB 黑色"
}
```
**响应示例:**
```json
{
"code": 200,
"message": "修改产品成功",
"data": null
}
```
### 7.5 删除产品
**接口路径:** `DELETE /api/system/product/{productId}`
**功能描述:** 根据产品ID删除产品
**权限要求:** `product:delete`
**路径参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| productId | Long | 是 | 产品ID |
**响应示例:**
```json
{
"code": 200,
"message": "删除产品成功",
"data": null
}
```
### 7.6 批量删除产品
**接口路径:** `POST /api/product/batchDelete`
**功能描述:** 批量删除产品
**权限要求:** `product:delete`
**请求体参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| productIds | List<Long> | 是 | 产品ID列表 |
**请求示例:**
```json
[1, 2, 3]
```
**响应示例:**
```json
{
"code": 200,
"message": "批量删除产品成功",
"data": null
}
```
### 7.7 修改产品状态
**接口路径:** `POST /api/product/{productId}/status`
**功能描述:** 修改产品销售状态
**权限要求:** `product:edit`
**路径参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| productId | Long | 是 | 产品ID |
**请求参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| saleStatus | Integer | 是 | 销售状态(0-下架/1-在售/2-预售) |
**响应示例:**
```json
{
"code": 200,
"message": "修改产品状态成功",
"data": null
}
```
### 7.8 修改返利标识
**接口路径:** `POST /api/product/{productId}/rebate`
**功能描述:** 修改产品返利标识
**权限要求:** `product:edit`
**路径参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| productId | Long | 是 | 产品ID |
**请求参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| rebateFlag | Integer | 是 | 返利标识(0-否/1-是) |
**响应示例:**
```json
{
"code": 200,
"message": "修改返利标识成功",
"data": null
}
```
## 8. 经销商管理 (DealerInfoController)
### 8.1 分页查询经销商列表
**接口路径:** `GET /api/dealer/list`
**功能描述:** 根据条件分页查询经销商列表
**权限要求:** `dealer:list`
**请求参数:**
| 参数名 | 类型 | 必填 | 描述 | 示例 |
|--------|------|------|------|------|
| dealerCode | String | 否 | 经销商编码 | DL001 |
| dealerName | String | 否 | 经销商名称 | 北京经销商 |
| creditCode | String | 否 | 统一社会信用代码 | 91110000123456789X |
| dealerLevel | Integer | 否 | 经销商等级(1-一级经销商/2-二级经销商) | 1 |
| region | String | 否 | 所在区域 | 北京 |
| contactPerson | String | 否 | 联系人 | 张三 |
| contactPhone | String | 否 | 联系电话 | 13800138000 |
| cooperateStatus | Integer | 否 | 合作状态(1-正常合作/2-暂停合作/3-终止合作) | 1 |
| qualificationAuditStatus | Integer | 否 | 资质审核状态(1-待审核/2-审核通过/3-审核不通过) | 2 |
| cooperateStartDateStart | String | 否 | 合作起始日期开始 | 2024-01-01 |
| cooperateStartDateEnd | String | 否 | 合作起始日期结束 | 2024-12-31 |
| pageNum | Integer | 否 | 页码 | 1 |
| pageSize | Integer | 否 | 每页大小 | 10 |
**响应示例:**
```json
{
"code": 200,
"message": "操作成功",
"data": {
"records": [
{
"dealerId": 1,
"dealerCode": "DL001",
"dealerName": "北京经销商",
"creditCode": "91110000123456789X",
"dealerLevel": 1,
"region": "北京",
"contactPerson": "张三",
"contactPhone": "13800138000",
"cooperateStartDate": "2024-01-01",
"cooperateStatus": 1,
"businessLicenseUrl": "https://example.com/license.jpg",
"cooperationAgreementUrl": "https://example.com/agreement.pdf",
"qualificationAuditStatus": 2,
"auditOpinion": "审核通过",
"totalRebateAmount": 10000.00,
"usedRebateAmount": 5000.00,
"pendingRebateAmount": 5000.00,
"lastRebateUpdateTime": "2024-01-15 10:30:00",
"createBy": "admin",
"createTime": "2024-01-15 10:30:00",
"updateBy": "admin",
"updateTime": "2024-01-15 10:30:00",
"delFlag": "0"
}
],
"total": 1,
"size": 10,
"current": 1,
"pages": 1
}
}
```
### 8.2 获取经销商详情
**接口路径:** `GET /api/dealer/{dealerId}`
**功能描述:** 根据经销商ID获取经销商详情
**权限要求:** `dealer:detail`
**路径参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| dealerId | Long | 是 | 经销商ID |
**响应示例:**
```json
{
"code": 200,
"message": "操作成功",
"data": {
"dealerId": 1,
"dealerCode": "DL001",
"dealerName": "北京经销商",
"creditCode": "91110000123456789X",
"dealerLevel": 1,
"region": "北京",
"contactPerson": "张三",
"contactPhone": "13800138000",
"cooperateStartDate": "2024-01-01",
"cooperateStatus": 1,
"businessLicenseUrl": "https://example.com/license.jpg",
"cooperationAgreementUrl": "https://example.com/agreement.pdf",
"qualificationAuditStatus": 2,
"auditOpinion": "审核通过",
"totalRebateAmount": 10000.00,
"usedRebateAmount": 5000.00,
"pendingRebateAmount": 5000.00,
"lastRebateUpdateTime": "2024-01-15 10:30:00",
"createBy": "admin",
"createTime": "2024-01-15 10:30:00",
"updateBy": "admin",
"updateTime": "2024-01-15 10:30:00",
"delFlag": "0"
}
}
```
### 8.3 新增经销商
**接口路径:** `POST /api/dealer`
**功能描述:** 新增经销商信息
**权限要求:** `dealer:add`
**请求体参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| dealerCode | String | 是 | 经销商编码(6-20位大写字母和数字) |
| dealerName | String | 是 | 经销商名称 |
| creditCode | String | 是 | 统一社会信用代码 |
| dealerLevel | Integer | 是 | 经销商等级(1-一级经销商/2-二级经销商) |
| region | String | 是 | 所在区域 |
| contactPerson | String | 否 | 联系人 |
| contactPhone | String | 否 | 联系电话 |
| cooperateStartDate | String | 是 | 合作起始日期 |
| cooperateStatus | Integer | 否 | 合作状态(1-正常合作/2-暂停合作/3-终止合作) |
| businessLicenseUrl | String | 否 | 营业执照URL |
| cooperationAgreementUrl | String | 否 | 合作协议URL |
| qualificationAuditStatus | Integer | 否 | 资质审核状态(1-待审核/2-审核通过/3-审核不通过) |
| auditOpinion | String | 否 | 审核意见 |
**请求示例:**
```json
{
"dealerCode": "DL001",
"dealerName": "北京经销商",
"creditCode": "91110000123456789X",
"dealerLevel": 1,
"region": "北京",
"contactPerson": "张三",
"contactPhone": "13800138000",
"cooperateStartDate": "2024-01-01",
"cooperateStatus": 1,
"businessLicenseUrl": "https://example.com/license.jpg",
"cooperationAgreementUrl": "https://example.com/agreement.pdf",
"qualificationAuditStatus": 1,
"auditOpinion": ""
}
```
**响应示例:**
```json
{
"code": 200,
"message": "经销商新增成功",
"data": null
}
```
### 8.4 修改经销商
**接口路径:** `POST /api/dealer/update`
**功能描述:** 修改经销商信息
**权限要求:** `dealer:update`
**请求体参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| dealerId | Long | 是 | 经销商ID |
| dealerCode | String | 是 | 经销商编码(6-20位大写字母和数字) |
| dealerName | String | 是 | 经销商名称 |
| creditCode | String | 是 | 统一社会信用代码 |
| dealerLevel | Integer | 是 | 经销商等级(1-一级经销商/2-二级经销商) |
| region | String | 是 | 所在区域 |
| contactPerson | String | 否 | 联系人 |
| contactPhone | String | 否 | 联系电话 |
| cooperateStartDate | String | 是 | 合作起始日期 |
| cooperateStatus | Integer | 否 | 合作状态(1-正常合作/2-暂停合作/3-终止合作) |
| businessLicenseUrl | String | 否 | 营业执照URL |
| cooperationAgreementUrl | String | 否 | 合作协议URL |
| qualificationAuditStatus | Integer | 否 | 资质审核状态(1-待审核/2-审核通过/3-审核不通过) |
| auditOpinion | String | 否 | 审核意见 |
**请求示例:**
```json
{
"dealerId": 1,
"dealerCode": "DL001",
"dealerName": "北京经销商",
"creditCode": "91110000123456789X",
"dealerLevel": 1,
"region": "北京",
"contactPerson": "张三",
"contactPhone": "13800138000",
"cooperateStartDate": "2024-01-01",
"cooperateStatus": 1,
"businessLicenseUrl": "https://example.com/license.jpg",
"cooperationAgreementUrl": "https://example.com/agreement.pdf",
"qualificationAuditStatus": 2,
"auditOpinion": "审核通过"
}
```
**响应示例:**
```json
{
"code": 200,
"message": "经销商修改成功",
"data": null
}
```
### 8.5 删除经销商
**接口路径:** `DELETE /api/dealer/{dealerId}`
**功能描述:** 根据经销商ID删除经销商
**权限要求:** `dealer:delete`
**路径参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| dealerId | Long | 是 | 经销商ID |
**响应示例:**
```json
{
"code": 200,
"message": "经销商删除成功",
"data": null
}
```
### 8.6 批量删除经销商
**接口路径:** `DELETE /api/dealer/batch`
**功能描述:** 批量删除经销商
**权限要求:** `dealer:batchDelete`
**请求体参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| dealerIds | List<Long> | 是 | 经销商ID列表 |
**请求示例:**
```json
[1, 2, 3]
```
**响应示例:**
```json
{
"code": 200,
"message": "经销商批量删除成功",
"data": null
}
```
### 8.7 修改经销商合作状态
**接口路径:** `POST /api/dealer/{dealerId}/cooperateStatus/{cooperateStatus}`
**功能描述:** 修改经销商的合作状态
**权限要求:** `dealer:updateStatus`
**路径参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| dealerId | Long | 是 | 经销商ID |
| cooperateStatus | Integer | 是 | 合作状态(1-正常合作/2-暂停合作/3-终止合作) |
**响应示例:**
```json
{
"code": 200,
"message": "经销商合作状态修改成功",
"data": null
}
```
### 8.8 修改经销商资质审核状态
**接口路径:** `POST /api/dealer/{dealerId}/auditStatus`
**功能描述:** 修改经销商的资质审核状态
**权限要求:** `dealer:audit`
**路径参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| dealerId | Long | 是 | 经销商ID |
**请求参数:**
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| qualificationAuditStatus | Integer | 是 | 资质审核状态(1-待审核/2-审核通过/3-审核不通过) |
| auditOpinion | String | 否 | 审核意见 |
**响应示例:**
```json
{
"code": 200,
"message": "经销商资质审核状态修改成功",
"data": null
}
```
## 数据字典
### 销售状态(saleStatus)
| 值 | 描述 |
|----|------|
| 0 | 下架 |
| 1 | 在售 |
| 2 | 预售 |
### 返利标识(rebateFlag)
| 值 | 描述 |
|----|------|
| 0 | 否 |
| 1 | 是 |
### 经销商等级(dealerLevel)
| 值 | 描述 |
|----|------|
| 1 | 一级经销商 |
| 2 | 二级经销商 |
### 合作状态(cooperateStatus)
| 值 | 描述 |
|----|------|
| 1 | 正常合作 |
| 2 | 暂停合作 |
| 3 | 终止合作 |
### 资质审核状态(qualificationAuditStatus)
| 值 | 描述 |
|----|------|
| 1 | 待审核 |
| 2 | 审核通过 |
| 3 | 审核不通过 |
---
**文档版本:** 1.0.0
**最后更新:** 2024-01-01
**最后更新:** 2025-01-27
**维护人员:** Apple ERP Team
......
package com.apple.erp.controller;
import com.apple.erp.dto.DealerAddReq;
import com.apple.erp.dto.DealerQueryReq;
import com.apple.erp.dto.DealerUpdateReq;
import com.apple.erp.entity.DealerInfo;
import com.apple.erp.service.DealerInfoService;
import com.apple.erp.dto.response.ApiRes;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "经销商管理", description = "经销商信息管理接口")
@RestController
@RequestMapping("/api/dealer")
@RequiredArgsConstructor
public class DealerInfoController {
private final DealerInfoService dealerInfoService;
@Operation(summary = "获取经销商列表", description = "分页查询经销商信息列表")
@GetMapping("/list")
@PreAuthorize("hasAuthority('dealer:list')")
public ApiRes<IPage<DealerInfo>> getDealerList(@Valid DealerQueryReq queryReq) {
IPage<DealerInfo> page = dealerInfoService.getDealerList(queryReq);
return ApiRes.success(page);
}
@Operation(summary = "获取经销商详情", description = "根据经销商ID获取经销商详细信息")
@GetMapping("/{dealerId}")
@PreAuthorize("hasAuthority('dealer:detail')")
public ApiRes<DealerInfo> getDealerDetail(@Parameter(description = "经销商ID") @PathVariable Long dealerId) {
DealerInfo dealerInfo = dealerInfoService.getDealerDetail(dealerId);
if (dealerInfo != null) {
return ApiRes.success(dealerInfo);
} else {
return ApiRes.error("经销商不存在");
}
}
@Operation(summary = "新增经销商", description = "新增一个经销商信息")
@PostMapping
@PreAuthorize("hasAuthority('dealer:add')")
public ApiRes<String> addDealer(@Parameter(description = "经销商新增请求") @Valid @RequestBody DealerAddReq addReq) {
try {
boolean success = dealerInfoService.addDealer(addReq);
return success ? ApiRes.success("经销商新增成功", null) : ApiRes.error("经销商新增失败");
} catch (IllegalArgumentException e) {
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "修改经销商", description = "修改一个经销商信息")
@PostMapping("/update")
@PreAuthorize("hasAuthority('dealer:update')")
public ApiRes<String> updateDealer(@Parameter(description = "经销商修改请求") @Valid @RequestBody DealerUpdateReq updateReq) {
try {
boolean success = dealerInfoService.updateDealer(updateReq);
return success ? ApiRes.success("经销商修改成功", null) : ApiRes.error("经销商修改失败");
} catch (IllegalArgumentException e) {
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "删除经销商", description = "根据经销商ID删除经销商信息(软删除)")
@DeleteMapping("/{dealerId}")
@PreAuthorize("hasAuthority('dealer:delete')")
public ApiRes<String> deleteDealer(@Parameter(description = "经销商ID") @PathVariable Long dealerId) {
try {
boolean success = dealerInfoService.deleteDealer(dealerId);
return success ? ApiRes.success("经销商删除成功", null) : ApiRes.error("经销商删除失败");
} catch (IllegalArgumentException e) {
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "批量删除经销商", description = "根据经销商ID列表批量删除经销商信息(软删除)")
@DeleteMapping("/batch")
@PreAuthorize("hasAuthority('dealer:batchDelete')")
public ApiRes<String> batchDeleteDealers(@Parameter(description = "经销商ID列表") @RequestBody List<Long> dealerIds) {
boolean success = dealerInfoService.batchDeleteDealers(dealerIds);
return success ? ApiRes.success("经销商批量删除成功", null) : ApiRes.error("经销商批量删除失败");
}
@Operation(summary = "修改经销商合作状态", description = "修改经销商的合作状态")
@PostMapping("/{dealerId}/cooperateStatus/{cooperateStatus}")
@PreAuthorize("hasAuthority('dealer:updateStatus')")
public ApiRes<String> updateDealerCooperateStatus(
@Parameter(description = "经销商ID") @PathVariable Long dealerId,
@Parameter(description = "合作状态(1-正常合作/2-暂停合作/3-终止合作)") @PathVariable Integer cooperateStatus) {
try {
boolean success = dealerInfoService.updateDealerCooperateStatus(dealerId, cooperateStatus);
return success ? ApiRes.success("经销商合作状态修改成功", null) : ApiRes.error("经销商合作状态修改失败");
} catch (IllegalArgumentException e) {
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "修改经销商资质审核状态", description = "修改经销商的资质审核状态")
@PostMapping("/{dealerId}/auditStatus")
@PreAuthorize("hasAuthority('dealer:audit')")
public ApiRes<String> updateDealerAuditStatus(
@Parameter(description = "经销商ID") @PathVariable Long dealerId,
@Parameter(description = "资质审核状态(1-待审核/2-审核通过/3-审核不通过)") @RequestParam Integer qualificationAuditStatus,
@Parameter(description = "审核意见") @RequestParam(required = false) String auditOpinion) {
try {
boolean success = dealerInfoService.updateDealerAuditStatus(dealerId, qualificationAuditStatus, auditOpinion);
return success ? ApiRes.success("经销商资质审核状态修改成功", null) : ApiRes.error("经销商资质审核状态修改失败");
} catch (IllegalArgumentException e) {
return ApiRes.error(e.getMessage());
}
}
}
package com.apple.erp.controller;
import com.apple.erp.dto.ProductAddReq;
import com.apple.erp.dto.ProductQueryReq;
import com.apple.erp.dto.ProductUpdateReq;
import com.apple.erp.entity.ProductInfo;
import com.apple.erp.service.ProductInfoService;
import com.apple.erp.dto.response.ApiRes;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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.util.List;
/**
* 产品信息管理控制器
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Tag(name = "产品信息管理", description = "产品信息的增删改查等操作")
@RestController
@RequestMapping("/api/product")
@RequiredArgsConstructor
@Validated
public class ProductInfoController {
private final ProductInfoService productInfoService;
@Operation(summary = "分页查询产品列表", description = "根据条件分页查询产品列表")
@GetMapping("/list")
@PreAuthorize("hasAuthority('product:list')")
public ApiRes<IPage<ProductInfo>> getProductList(@Valid ProductQueryReq queryReq) {
try {
IPage<ProductInfo> result = productInfoService.getProductPage(queryReq);
return ApiRes.success(result);
} catch (Exception e) {
return ApiRes.error("查询产品列表失败:" + e.getMessage());
}
}
@Operation(summary = "获取产品详情", description = "根据产品ID获取产品详情")
@GetMapping("/{productId}")
@PreAuthorize("hasAuthority('product:detail')")
public ApiRes<ProductInfo> getProductDetail(
@Parameter(description = "产品ID", required = true)
@PathVariable Long productId) {
try {
ProductInfo productInfo = productInfoService.getProductById(productId);
if (productInfo == null) {
return ApiRes.error("产品不存在");
}
return ApiRes.success(productInfo);
} catch (Exception e) {
return ApiRes.error("获取产品详情失败:" + e.getMessage());
}
}
@Operation(summary = "新增产品", description = "新增产品信息")
@PostMapping
@PreAuthorize("hasAuthority('product:add')")
public ApiRes<String> addProduct(@Valid @RequestBody ProductAddReq addReq) {
try {
boolean success = productInfoService.addProduct(addReq);
if (success) {
return ApiRes.success("新增产品成功");
} else {
return ApiRes.error("新增产品失败");
}
} catch (RuntimeException e) {
return ApiRes.error(e.getMessage());
} catch (Exception e) {
return ApiRes.error("新增产品失败:" + e.getMessage());
}
}
@Operation(summary = "修改产品", description = "修改产品信息")
@PostMapping("/update")
@PreAuthorize("hasAuthority('product:edit')")
public ApiRes<String> updateProduct(@Valid @RequestBody ProductUpdateReq updateReq) {
try {
boolean success = productInfoService.updateProduct(updateReq);
if (success) {
return ApiRes.success("修改产品成功");
} else {
return ApiRes.error("修改产品失败");
}
} catch (RuntimeException e) {
return ApiRes.error(e.getMessage());
} catch (Exception e) {
return ApiRes.error("修改产品失败:" + e.getMessage());
}
}
@Operation(summary = "删除产品", description = "根据产品ID删除产品")
@DeleteMapping("/{productId}")
@PreAuthorize("hasAuthority('product:delete')")
public ApiRes<String> deleteProduct(
@Parameter(description = "产品ID", required = true)
@PathVariable Long productId) {
try {
boolean success = productInfoService.deleteProduct(productId);
if (success) {
return ApiRes.success("删除产品成功");
} else {
return ApiRes.error("删除产品失败");
}
} catch (Exception e) {
return ApiRes.error("删除产品失败:" + e.getMessage());
}
}
@Operation(summary = "批量删除产品", description = "批量删除产品")
@PostMapping("/batchDelete")
@PreAuthorize("hasAuthority('product:delete')")
public ApiRes<String> batchDeleteProducts(@RequestBody List<Long> productIds) {
try {
if (productIds == null || productIds.isEmpty()) {
return ApiRes.error("请选择要删除的产品");
}
boolean success = productInfoService.batchDeleteProducts(productIds);
if (success) {
return ApiRes.success("批量删除产品成功");
} else {
return ApiRes.error("批量删除产品失败");
}
} catch (Exception e) {
return ApiRes.error("批量删除产品失败:" + e.getMessage());
}
}
@Operation(summary = "修改产品状态", description = "修改产品销售状态")
@PostMapping("/{productId}/status")
@PreAuthorize("hasAuthority('product:edit')")
public ApiRes<String> updateProductStatus(
@Parameter(description = "产品ID", required = true)
@PathVariable Long productId,
@Parameter(description = "销售状态", required = true)
@RequestParam Integer saleStatus) {
try {
boolean success = productInfoService.updateProductStatus(productId, saleStatus);
if (success) {
return ApiRes.success("修改产品状态成功");
} else {
return ApiRes.error("修改产品状态失败");
}
} catch (Exception e) {
return ApiRes.error("修改产品状态失败:" + e.getMessage());
}
}
@Operation(summary = "修改返利标识", description = "修改产品返利标识")
@PostMapping("/{productId}/rebate")
@PreAuthorize("hasAuthority('product:edit')")
public ApiRes<String> updateRebateFlag(
@Parameter(description = "产品ID", required = true)
@PathVariable Long productId,
@Parameter(description = "返利标识", required = true)
@RequestParam Integer rebateFlag) {
try {
boolean success = productInfoService.updateRebateFlag(productId, rebateFlag);
if (success) {
return ApiRes.success("修改返利标识成功");
} else {
return ApiRes.error("修改返利标识失败");
}
} catch (Exception e) {
return ApiRes.error("修改返利标识失败:" + e.getMessage());
}
}
}
package com.apple.erp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(description = "经销商新增请求DTO")
public class DealerAddReq {
@NotBlank(message = "经销商编码不能为空")
@Pattern(regexp = "^[A-Z0-9]{6,20}$", message = "经销商编码格式不正确,应为6-20位大写字母和数字")
@Schema(description = "经销商编码", required = true)
private String dealerCode;
@NotBlank(message = "经销商名称不能为空")
@Schema(description = "经销商名称", required = true)
private String dealerName;
@NotBlank(message = "统一社会信用代码不能为空")
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}[0-9]{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "统一社会信用代码格式不正确")
@Schema(description = "统一社会信用代码", required = true)
private String creditCode;
@NotNull(message = "经销商等级不能为空")
@Schema(description = "经销商等级(1-一级经销商/2-二级经销商)", required = true)
private Integer dealerLevel;
@NotBlank(message = "所在区域不能为空")
@Schema(description = "所在区域", required = true)
private String region;
@Schema(description = "联系人")
private String contactPerson;
@Schema(description = "联系电话")
private String contactPhone;
@NotNull(message = "合作起始日期不能为空")
@Schema(description = "合作起始日期", required = true)
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate cooperateStartDate;
@Schema(description = "合作状态(1-正常合作/2-暂停合作/3-终止合作)")
private Integer cooperateStatus = 1;
@Schema(description = "营业执照URL")
private String businessLicenseUrl;
@Schema(description = "合作协议URL")
private String cooperationAgreementUrl;
@Schema(description = "资质审核状态(1-待审核/2-审核通过/3-审核不通过)")
private Integer qualificationAuditStatus = 1;
@Schema(description = "审核意见")
private String auditOpinion;
}
package com.apple.erp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(description = "经销商查询请求DTO")
public class DealerQueryReq {
@Schema(description = "经销商编码")
private String dealerCode;
@Schema(description = "经销商名称")
private String dealerName;
@Schema(description = "统一社会信用代码")
private String creditCode;
@Schema(description = "经销商等级(1-一级经销商/2-二级经销商)")
private Integer dealerLevel;
@Schema(description = "所在区域")
private String region;
@Schema(description = "联系人")
private String contactPerson;
@Schema(description = "联系电话")
private String contactPhone;
@Schema(description = "合作状态(1-正常合作/2-暂停合作/3-终止合作)")
private Integer cooperateStatus;
@Schema(description = "资质审核状态(1-待审核/2-审核通过/3-审核不通过)")
private Integer qualificationAuditStatus;
@Schema(description = "合作起始日期开始")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate cooperateStartDateStart;
@Schema(description = "合作起始日期结束")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate cooperateStartDateEnd;
@Schema(description = "当前页码")
private Long pageNum = 1L;
@Schema(description = "每页大小")
private Long pageSize = 10L;
}
package com.apple.erp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(description = "经销商修改请求DTO")
public class DealerUpdateReq {
@NotNull(message = "经销商ID不能为空")
@Schema(description = "经销商ID", required = true)
private Long dealerId;
@NotBlank(message = "经销商编码不能为空")
@Pattern(regexp = "^[A-Z0-9]{6,20}$", message = "经销商编码格式不正确,应为6-20位大写字母和数字")
@Schema(description = "经销商编码", required = true)
private String dealerCode;
@NotBlank(message = "经销商名称不能为空")
@Schema(description = "经销商名称", required = true)
private String dealerName;
@NotBlank(message = "统一社会信用代码不能为空")
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}[0-9]{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "统一社会信用代码格式不正确")
@Schema(description = "统一社会信用代码", required = true)
private String creditCode;
@NotNull(message = "经销商等级不能为空")
@Schema(description = "经销商等级(1-一级经销商/2-二级经销商)", required = true)
private Integer dealerLevel;
@NotBlank(message = "所在区域不能为空")
@Schema(description = "所在区域", required = true)
private String region;
@Schema(description = "联系人")
private String contactPerson;
@Schema(description = "联系电话")
private String contactPhone;
@NotNull(message = "合作起始日期不能为空")
@Schema(description = "合作起始日期", required = true)
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate cooperateStartDate;
@Schema(description = "合作状态(1-正常合作/2-暂停合作/3-终止合作)")
private Integer cooperateStatus;
@Schema(description = "营业执照URL")
private String businessLicenseUrl;
@Schema(description = "合作协议URL")
private String cooperationAgreementUrl;
@Schema(description = "资质审核状态(1-待审核/2-审核通过/3-审核不通过)")
private Integer qualificationAuditStatus;
@Schema(description = "审核意见")
private String auditOpinion;
}
package com.apple.erp.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 产品新增请求DTO
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Data
@Schema(description = "产品新增请求")
public class ProductAddReq {
@Schema(description = "产品编码", required = true)
@NotBlank(message = "产品编码不能为空")
private String productCode;
@Schema(description = "产品名称", required = true)
@NotBlank(message = "产品名称不能为空")
private String productName;
@Schema(description = "产品型号", required = true)
@NotBlank(message = "产品型号不能为空")
private String productModel;
@Schema(description = "产品类别", required = true)
@NotBlank(message = "产品类别不能为空")
private String productType;
@Schema(description = "存储容量")
private String storageCapacity;
@Schema(description = "产品颜色")
private String color;
@Schema(description = "产品图片URL")
private String productImgUrl;
@Schema(description = "官方指导价")
private BigDecimal officialPrice;
@Schema(description = "销售状态(0-下架/1-在售/2-预售)", required = true)
@NotNull(message = "销售状态不能为空")
private Integer saleStatus;
@Schema(description = "是否参与返利(0-否/1-是)", required = true)
@NotNull(message = "返利标识不能为空")
private Integer rebateFlag;
@Schema(description = "销售起始日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleStartDate;
@Schema(description = "销售终止日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleEndDate;
@Schema(description = "产品备注")
private String remark;
}
package com.apple.erp.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 产品查询请求DTO
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Data
@Schema(description = "产品查询请求")
public class ProductQueryReq {
@Schema(description = "产品编码")
private String productCode;
@Schema(description = "产品名称")
private String productName;
@Schema(description = "产品型号")
private String productModel;
@Schema(description = "产品类别")
private String productType;
@Schema(description = "存储容量")
private String storageCapacity;
@Schema(description = "产品颜色")
private String color;
@Schema(description = "销售状态(0-下架/1-在售/2-预售)")
private Integer saleStatus;
@Schema(description = "是否参与返利(0-否/1-是)")
private Integer rebateFlag;
@Schema(description = "最低价格")
private BigDecimal minPrice;
@Schema(description = "最高价格")
private BigDecimal maxPrice;
@Schema(description = "销售起始日期开始")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleStartDateBegin;
@Schema(description = "销售起始日期结束")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleStartDateEnd;
@Schema(description = "销售终止日期开始")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleEndDateBegin;
@Schema(description = "销售终止日期结束")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleEndDateEnd;
@Schema(description = "页码", example = "1")
private Integer pageNum = 1;
@Schema(description = "每页大小", example = "10")
private Integer pageSize = 10;
}
package com.apple.erp.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 产品修改请求DTO
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Data
@Schema(description = "产品修改请求")
public class ProductUpdateReq {
@Schema(description = "产品ID", required = true)
@NotNull(message = "产品ID不能为空")
private Long productId;
@Schema(description = "产品编码", required = true)
@NotBlank(message = "产品编码不能为空")
private String productCode;
@Schema(description = "产品名称", required = true)
@NotBlank(message = "产品名称不能为空")
private String productName;
@Schema(description = "产品型号", required = true)
@NotBlank(message = "产品型号不能为空")
private String productModel;
@Schema(description = "产品类别", required = true)
@NotBlank(message = "产品类别不能为空")
private String productType;
@Schema(description = "存储容量")
private String storageCapacity;
@Schema(description = "产品颜色")
private String color;
@Schema(description = "产品图片URL")
private String productImgUrl;
@Schema(description = "官方指导价")
private BigDecimal officialPrice;
@Schema(description = "销售状态(0-下架/1-在售/2-预售)", required = true)
@NotNull(message = "销售状态不能为空")
private Integer saleStatus;
@Schema(description = "是否参与返利(0-否/1-是)", required = true)
@NotNull(message = "返利标识不能为空")
private Integer rebateFlag;
@Schema(description = "销售起始日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleStartDate;
@Schema(description = "销售终止日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleEndDate;
@Schema(description = "产品备注")
private String remark;
}
package com.apple.erp.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("t_dealer_info")
@Schema(description = "经销商信息实体")
public class DealerInfo {
@TableId(type = IdType.AUTO)
@Schema(description = "经销商ID")
private Long dealerId;
@Schema(description = "经销商编码")
private String dealerCode;
@Schema(description = "经销商名称")
private String dealerName;
@Schema(description = "统一社会信用代码")
private String creditCode;
@Schema(description = "经销商等级(1-一级经销商/2-二级经销商)")
private Integer dealerLevel;
@Schema(description = "所在区域")
private String region;
@Schema(description = "联系人")
private String contactPerson;
@Schema(description = "联系电话")
private String contactPhone;
@Schema(description = "合作起始日期")
private LocalDate cooperateStartDate;
@Schema(description = "合作状态(1-正常合作/2-暂停合作/3-终止合作)")
private Integer cooperateStatus;
@Schema(description = "营业执照URL")
private String businessLicenseUrl;
@Schema(description = "合作协议URL")
private String cooperationAgreementUrl;
@Schema(description = "资质审核状态(1-待审核/2-审核通过/3-审核不通过)")
private Integer qualificationAuditStatus;
@Schema(description = "审核意见")
private String auditOpinion;
@Schema(description = "累计应返金额")
private BigDecimal totalRebateAmount;
@Schema(description = "累计已返金额")
private BigDecimal usedRebateAmount;
@Schema(description = "待返利总金额")
private BigDecimal pendingRebateAmount;
@Schema(description = "最后返利更新时间")
private LocalDateTime lastRebateUpdateTime;
@Schema(description = "创建者")
private String createBy;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新者")
private String updateBy;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "删除标志(0代表存在 2代表删除)")
private String delFlag;
}
package com.apple.erp.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 产品信息表
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_product_info")
public class ProductInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 产品ID
*/
@TableId(value = "product_id", type = IdType.AUTO)
private Long productId;
/**
* 产品编码
*/
private String productCode;
/**
* 产品名称
*/
private String productName;
/**
* 产品型号
*/
private String productModel;
/**
* 产品类别
*/
private String productType;
/**
* 存储容量
*/
private String storageCapacity;
/**
* 产品颜色
*/
private String color;
/**
* 产品图片URL
*/
private String productImgUrl;
/**
* 官方指导价
*/
private BigDecimal officialPrice;
/**
* 销售状态(0-下架/1-在售/2-预售)
*/
private Integer saleStatus;
/**
* 是否参与返利(0-否/1-是)
*/
private Integer rebateFlag;
/**
* 销售起始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleStartDate;
/**
* 销售终止日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate saleEndDate;
/**
* 产品备注
*/
private String remark;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
/**
* 删除标志(0代表存在 2代表删除)
*/
private String delFlag;
}
package com.apple.erp.mapper;
import com.apple.erp.entity.DealerInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DealerInfoMapper extends BaseMapper<DealerInfo> {
}
package com.apple.erp.mapper;
import com.apple.erp.entity.ProductInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 产品信息表 Mapper 接口
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Mapper
public interface ProductInfoMapper extends BaseMapper<ProductInfo> {
}
package com.apple.erp.service;
import com.apple.erp.dto.DealerAddReq;
import com.apple.erp.dto.DealerQueryReq;
import com.apple.erp.dto.DealerUpdateReq;
import com.apple.erp.entity.DealerInfo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface DealerInfoService extends IService<DealerInfo> {
/**
* 分页查询经销商列表
* @param queryReq 查询条件
* @return 经销商分页数据
*/
IPage<DealerInfo> getDealerList(DealerQueryReq queryReq);
/**
* 根据ID获取经销商详情
* @param dealerId 经销商ID
* @return 经销商详情
*/
DealerInfo getDealerDetail(Long dealerId);
/**
* 新增经销商
* @param addReq 新增请求
* @return 是否成功
*/
boolean addDealer(DealerAddReq addReq);
/**
* 修改经销商
* @param updateReq 修改请求
* @return 是否成功
*/
boolean updateDealer(DealerUpdateReq updateReq);
/**
* 删除经销商
* @param dealerId 经销商ID
* @return 是否成功
*/
boolean deleteDealer(Long dealerId);
/**
* 批量删除经销商
* @param dealerIds 经销商ID列表
* @return 是否成功
*/
boolean batchDeleteDealers(List<Long> dealerIds);
/**
* 修改经销商合作状态
* @param dealerId 经销商ID
* @param cooperateStatus 合作状态
* @return 是否成功
*/
boolean updateDealerCooperateStatus(Long dealerId, Integer cooperateStatus);
/**
* 修改经销商资质审核状态
* @param dealerId 经销商ID
* @param qualificationAuditStatus 资质审核状态
* @param auditOpinion 审核意见
* @return 是否成功
*/
boolean updateDealerAuditStatus(Long dealerId, Integer qualificationAuditStatus, String auditOpinion);
}
package com.apple.erp.service;
import com.apple.erp.dto.ProductAddReq;
import com.apple.erp.dto.ProductQueryReq;
import com.apple.erp.dto.ProductUpdateReq;
import com.apple.erp.entity.ProductInfo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 产品信息表 服务类
*
* @author Apple ERP Team
* @since 2025-01-27
*/
public interface ProductInfoService extends IService<ProductInfo> {
/**
* 分页查询产品列表
*
* @param queryReq 查询条件
* @return 产品分页列表
*/
IPage<ProductInfo> getProductPage(ProductQueryReq queryReq);
/**
* 根据产品ID获取产品详情
*
* @param productId 产品ID
* @return 产品详情
*/
ProductInfo getProductById(Long productId);
/**
* 新增产品
*
* @param addReq 新增请求
* @return 是否成功
*/
boolean addProduct(ProductAddReq addReq);
/**
* 修改产品
*
* @param updateReq 修改请求
* @return 是否成功
*/
boolean updateProduct(ProductUpdateReq updateReq);
/**
* 删除产品
*
* @param productId 产品ID
* @return 是否成功
*/
boolean deleteProduct(Long productId);
/**
* 批量删除产品
*
* @param productIds 产品ID列表
* @return 是否成功
*/
boolean batchDeleteProducts(List<Long> productIds);
/**
* 修改产品状态
*
* @param productId 产品ID
* @param saleStatus 销售状态
* @return 是否成功
*/
boolean updateProductStatus(Long productId, Integer saleStatus);
/**
* 修改返利标识
*
* @param productId 产品ID
* @param rebateFlag 返利标识
* @return 是否成功
*/
boolean updateRebateFlag(Long productId, Integer rebateFlag);
/**
* 检查产品编码是否存在
*
* @param productCode 产品编码
* @param productId 产品ID(修改时排除自己)
* @return 是否存在
*/
boolean checkProductCodeExists(String productCode, Long productId);
}
package com.apple.erp.service.impl;
import com.apple.erp.dto.DealerAddReq;
import com.apple.erp.dto.DealerQueryReq;
import com.apple.erp.dto.DealerUpdateReq;
import com.apple.erp.entity.DealerInfo;
import com.apple.erp.mapper.DealerInfoMapper;
import com.apple.erp.service.DealerInfoService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
public class DealerInfoServiceImpl extends ServiceImpl<DealerInfoMapper, DealerInfo> implements DealerInfoService {
@Override
public IPage<DealerInfo> getDealerList(DealerQueryReq queryReq) {
LambdaQueryWrapper<DealerInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DealerInfo::getDelFlag, "0"); // 未删除
if (StringUtils.hasText(queryReq.getDealerCode())) {
queryWrapper.like(DealerInfo::getDealerCode, queryReq.getDealerCode());
}
if (StringUtils.hasText(queryReq.getDealerName())) {
queryWrapper.like(DealerInfo::getDealerName, queryReq.getDealerName());
}
if (StringUtils.hasText(queryReq.getCreditCode())) {
queryWrapper.like(DealerInfo::getCreditCode, queryReq.getCreditCode());
}
if (queryReq.getDealerLevel() != null) {
queryWrapper.eq(DealerInfo::getDealerLevel, queryReq.getDealerLevel());
}
if (StringUtils.hasText(queryReq.getRegion())) {
queryWrapper.like(DealerInfo::getRegion, queryReq.getRegion());
}
if (StringUtils.hasText(queryReq.getContactPerson())) {
queryWrapper.like(DealerInfo::getContactPerson, queryReq.getContactPerson());
}
if (StringUtils.hasText(queryReq.getContactPhone())) {
queryWrapper.like(DealerInfo::getContactPhone, queryReq.getContactPhone());
}
if (queryReq.getCooperateStatus() != null) {
queryWrapper.eq(DealerInfo::getCooperateStatus, queryReq.getCooperateStatus());
}
if (queryReq.getQualificationAuditStatus() != null) {
queryWrapper.eq(DealerInfo::getQualificationAuditStatus, queryReq.getQualificationAuditStatus());
}
if (queryReq.getCooperateStartDateStart() != null) {
queryWrapper.ge(DealerInfo::getCooperateStartDate, queryReq.getCooperateStartDateStart());
}
if (queryReq.getCooperateStartDateEnd() != null) {
queryWrapper.le(DealerInfo::getCooperateStartDate, queryReq.getCooperateStartDateEnd());
}
queryWrapper.orderByDesc(DealerInfo::getCreateTime);
Page<DealerInfo> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize());
return page(page, queryWrapper);
}
@Override
public DealerInfo getDealerDetail(Long dealerId) {
return getOne(new LambdaQueryWrapper<DealerInfo>()
.eq(DealerInfo::getDealerId, dealerId)
.eq(DealerInfo::getDelFlag, "0"));
}
@Override
@Transactional
public boolean addDealer(DealerAddReq addReq) {
// 检查经销商编码是否重复
if (isDealerCodeDuplicated(addReq.getDealerCode(), null)) {
throw new IllegalArgumentException("经销商编码已存在");
}
// 检查统一社会信用代码是否重复
if (isCreditCodeDuplicated(addReq.getCreditCode(), null)) {
throw new IllegalArgumentException("统一社会信用代码已存在");
}
DealerInfo dealerInfo = new DealerInfo();
dealerInfo.setDealerCode(addReq.getDealerCode());
dealerInfo.setDealerName(addReq.getDealerName());
dealerInfo.setCreditCode(addReq.getCreditCode());
dealerInfo.setDealerLevel(addReq.getDealerLevel());
dealerInfo.setRegion(addReq.getRegion());
dealerInfo.setContactPerson(addReq.getContactPerson());
dealerInfo.setContactPhone(addReq.getContactPhone());
dealerInfo.setCooperateStartDate(addReq.getCooperateStartDate());
dealerInfo.setCooperateStatus(addReq.getCooperateStatus());
dealerInfo.setBusinessLicenseUrl(addReq.getBusinessLicenseUrl());
dealerInfo.setCooperationAgreementUrl(addReq.getCooperationAgreementUrl());
dealerInfo.setQualificationAuditStatus(addReq.getQualificationAuditStatus());
dealerInfo.setAuditOpinion(addReq.getAuditOpinion());
dealerInfo.setCreateTime(LocalDateTime.now());
dealerInfo.setUpdateTime(LocalDateTime.now());
dealerInfo.setDelFlag("0"); // 默认未删除
// TODO: 设置createBy和updateBy
return save(dealerInfo);
}
@Override
@Transactional
public boolean updateDealer(DealerUpdateReq updateReq) {
DealerInfo existingDealer = getById(updateReq.getDealerId());
if (existingDealer == null || "2".equals(existingDealer.getDelFlag())) {
throw new IllegalArgumentException("经销商不存在或已被删除");
}
// 检查经销商编码是否重复 (排除自身)
if (isDealerCodeDuplicated(updateReq.getDealerCode(), updateReq.getDealerId())) {
throw new IllegalArgumentException("经销商编码已存在");
}
// 检查统一社会信用代码是否重复 (排除自身)
if (isCreditCodeDuplicated(updateReq.getCreditCode(), updateReq.getDealerId())) {
throw new IllegalArgumentException("统一社会信用代码已存在");
}
DealerInfo dealerInfo = new DealerInfo();
dealerInfo.setDealerId(updateReq.getDealerId());
dealerInfo.setDealerCode(updateReq.getDealerCode());
dealerInfo.setDealerName(updateReq.getDealerName());
dealerInfo.setCreditCode(updateReq.getCreditCode());
dealerInfo.setDealerLevel(updateReq.getDealerLevel());
dealerInfo.setRegion(updateReq.getRegion());
dealerInfo.setContactPerson(updateReq.getContactPerson());
dealerInfo.setContactPhone(updateReq.getContactPhone());
dealerInfo.setCooperateStartDate(updateReq.getCooperateStartDate());
dealerInfo.setCooperateStatus(updateReq.getCooperateStatus());
dealerInfo.setBusinessLicenseUrl(updateReq.getBusinessLicenseUrl());
dealerInfo.setCooperationAgreementUrl(updateReq.getCooperationAgreementUrl());
dealerInfo.setQualificationAuditStatus(updateReq.getQualificationAuditStatus());
dealerInfo.setAuditOpinion(updateReq.getAuditOpinion());
dealerInfo.setUpdateTime(LocalDateTime.now());
// TODO: 设置updateBy
return updateById(dealerInfo);
}
@Override
@Transactional
public boolean deleteDealer(Long dealerId) {
DealerInfo dealerInfo = getById(dealerId);
if (dealerInfo == null || "2".equals(dealerInfo.getDelFlag())) {
throw new IllegalArgumentException("经销商不存在或已被删除");
}
dealerInfo.setDelFlag("2"); // 软删除
dealerInfo.setUpdateTime(LocalDateTime.now());
// TODO: 设置updateBy
return updateById(dealerInfo);
}
@Override
@Transactional
public boolean batchDeleteDealers(List<Long> dealerIds) {
if (dealerIds == null || dealerIds.isEmpty()) {
return false;
}
List<DealerInfo> dealersToUpdate = listByIds(dealerIds);
dealersToUpdate.forEach(dealer -> {
dealer.setDelFlag("2"); // 软删除
dealer.setUpdateTime(LocalDateTime.now());
// TODO: 设置updateBy
});
return updateBatchById(dealersToUpdate);
}
@Override
@Transactional
public boolean updateDealerCooperateStatus(Long dealerId, Integer cooperateStatus) {
DealerInfo dealerInfo = getById(dealerId);
if (dealerInfo == null || "2".equals(dealerInfo.getDelFlag())) {
throw new IllegalArgumentException("经销商不存在或已被删除");
}
dealerInfo.setCooperateStatus(cooperateStatus);
dealerInfo.setUpdateTime(LocalDateTime.now());
// TODO: 设置updateBy
return updateById(dealerInfo);
}
@Override
@Transactional
public boolean updateDealerAuditStatus(Long dealerId, Integer qualificationAuditStatus, String auditOpinion) {
DealerInfo dealerInfo = getById(dealerId);
if (dealerInfo == null || "2".equals(dealerInfo.getDelFlag())) {
throw new IllegalArgumentException("经销商不存在或已被删除");
}
dealerInfo.setQualificationAuditStatus(qualificationAuditStatus);
dealerInfo.setAuditOpinion(auditOpinion);
dealerInfo.setUpdateTime(LocalDateTime.now());
// TODO: 设置updateBy
return updateById(dealerInfo);
}
/**
* 检查经销商编码是否重复
* @param dealerCode 经销商编码
* @param excludeDealerId 排除的经销商ID (用于更新操作)
* @return 是否重复
*/
private boolean isDealerCodeDuplicated(String dealerCode, Long excludeDealerId) {
LambdaQueryWrapper<DealerInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DealerInfo::getDealerCode, dealerCode);
queryWrapper.eq(DealerInfo::getDelFlag, "0"); // 只检查未删除的经销商
if (excludeDealerId != null) {
queryWrapper.ne(DealerInfo::getDealerId, excludeDealerId);
}
return count(queryWrapper) > 0;
}
/**
* 检查统一社会信用代码是否重复
* @param creditCode 统一社会信用代码
* @param excludeDealerId 排除的经销商ID (用于更新操作)
* @return 是否重复
*/
private boolean isCreditCodeDuplicated(String creditCode, Long excludeDealerId) {
LambdaQueryWrapper<DealerInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DealerInfo::getCreditCode, creditCode);
queryWrapper.eq(DealerInfo::getDelFlag, "0"); // 只检查未删除的经销商
if (excludeDealerId != null) {
queryWrapper.ne(DealerInfo::getDealerId, excludeDealerId);
}
return count(queryWrapper) > 0;
}
}
package com.apple.erp.service.impl;
import com.apple.erp.dto.ProductAddReq;
import com.apple.erp.dto.ProductQueryReq;
import com.apple.erp.dto.ProductUpdateReq;
import com.apple.erp.entity.ProductInfo;
import com.apple.erp.mapper.ProductInfoMapper;
import com.apple.erp.service.ProductInfoService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 产品信息表 服务实现类
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Service
public class ProductInfoServiceImpl extends ServiceImpl<ProductInfoMapper, ProductInfo> implements ProductInfoService {
@Override
public IPage<ProductInfo> getProductPage(ProductQueryReq queryReq) {
Page<ProductInfo> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize());
LambdaQueryWrapper<ProductInfo> queryWrapper = getQueryWrapper(queryReq);
return this.page(page, queryWrapper);
}
@Override
public ProductInfo getProductById(Long productId) {
return this.getById(productId);
}
@Override
public boolean addProduct(ProductAddReq addReq) {
// 检查产品编码是否已存在
if (checkProductCodeExists(addReq.getProductCode(), null)) {
throw new RuntimeException("产品编码已存在");
}
ProductInfo productInfo = new ProductInfo();
BeanUtils.copyProperties(addReq, productInfo);
productInfo.setCreateTime(LocalDateTime.now());
productInfo.setDelFlag("0");
return this.save(productInfo);
}
@Override
public boolean updateProduct(ProductUpdateReq updateReq) {
// 检查产品编码是否已存在(排除自己)
if (checkProductCodeExists(updateReq.getProductCode(), updateReq.getProductId())) {
throw new RuntimeException("产品编码已存在");
}
ProductInfo productInfo = new ProductInfo();
BeanUtils.copyProperties(updateReq, productInfo);
productInfo.setUpdateTime(LocalDateTime.now());
return this.updateById(productInfo);
}
@Override
public boolean deleteProduct(Long productId) {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductId(productId);
productInfo.setDelFlag("2");
productInfo.setUpdateTime(LocalDateTime.now());
return this.updateById(productInfo);
}
@Override
public boolean batchDeleteProducts(List<Long> productIds) {
for (Long productId : productIds) {
deleteProduct(productId);
}
return true;
}
@Override
public boolean updateProductStatus(Long productId, Integer saleStatus) {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductId(productId);
productInfo.setSaleStatus(saleStatus);
productInfo.setUpdateTime(LocalDateTime.now());
return this.updateById(productInfo);
}
@Override
public boolean updateRebateFlag(Long productId, Integer rebateFlag) {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductId(productId);
productInfo.setRebateFlag(rebateFlag);
productInfo.setUpdateTime(LocalDateTime.now());
return this.updateById(productInfo);
}
@Override
public boolean checkProductCodeExists(String productCode, Long productId) {
LambdaQueryWrapper<ProductInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ProductInfo::getProductCode, productCode)
.eq(ProductInfo::getDelFlag, "0");
if (productId != null) {
queryWrapper.ne(ProductInfo::getProductId, productId);
}
return this.count(queryWrapper) > 0;
}
/**
* 构建查询条件
*
* @param queryReq 查询请求
* @return 查询条件
*/
private LambdaQueryWrapper<ProductInfo> getQueryWrapper(ProductQueryReq queryReq) {
LambdaQueryWrapper<ProductInfo> queryWrapper = new LambdaQueryWrapper<>();
// 基础查询条件
queryWrapper.eq(ProductInfo::getDelFlag, "0");
// 产品编码
if (StringUtils.hasText(queryReq.getProductCode())) {
queryWrapper.like(ProductInfo::getProductCode, queryReq.getProductCode());
}
// 产品名称
if (StringUtils.hasText(queryReq.getProductName())) {
queryWrapper.like(ProductInfo::getProductName, queryReq.getProductName());
}
// 产品型号
if (StringUtils.hasText(queryReq.getProductModel())) {
queryWrapper.like(ProductInfo::getProductModel, queryReq.getProductModel());
}
// 产品类别
if (StringUtils.hasText(queryReq.getProductType())) {
queryWrapper.eq(ProductInfo::getProductType, queryReq.getProductType());
}
// 存储容量
if (StringUtils.hasText(queryReq.getStorageCapacity())) {
queryWrapper.eq(ProductInfo::getStorageCapacity, queryReq.getStorageCapacity());
}
// 产品颜色
if (StringUtils.hasText(queryReq.getColor())) {
queryWrapper.eq(ProductInfo::getColor, queryReq.getColor());
}
// 销售状态
if (queryReq.getSaleStatus() != null) {
queryWrapper.eq(ProductInfo::getSaleStatus, queryReq.getSaleStatus());
}
// 返利标识
if (queryReq.getRebateFlag() != null) {
queryWrapper.eq(ProductInfo::getRebateFlag, queryReq.getRebateFlag());
}
// 价格范围
if (queryReq.getMinPrice() != null) {
queryWrapper.ge(ProductInfo::getOfficialPrice, queryReq.getMinPrice());
}
if (queryReq.getMaxPrice() != null) {
queryWrapper.le(ProductInfo::getOfficialPrice, queryReq.getMaxPrice());
}
// 销售起始日期范围
if (queryReq.getSaleStartDateBegin() != null) {
queryWrapper.ge(ProductInfo::getSaleStartDate, queryReq.getSaleStartDateBegin());
}
if (queryReq.getSaleStartDateEnd() != null) {
queryWrapper.le(ProductInfo::getSaleStartDate, queryReq.getSaleStartDateEnd());
}
// 销售终止日期范围
if (queryReq.getSaleEndDateBegin() != null) {
queryWrapper.ge(ProductInfo::getSaleEndDate, queryReq.getSaleEndDateBegin());
}
if (queryReq.getSaleEndDateEnd() != null) {
queryWrapper.le(ProductInfo::getSaleEndDate, queryReq.getSaleEndDateEnd());
}
// 排序
queryWrapper.orderByDesc(ProductInfo::getCreateTime);
return queryWrapper;
}
}
-- Apple经销商ERP系统 - 业务数据库初始化脚本
-- 按顺序执行所有业务数据库脚本
-- 1. 创建业务表
SOURCE 01_create_business_tables.sql;
-- 2. 创建业务索引
SOURCE 02_create_business_indexes.sql;
-- 3. 插入业务测试数据
SOURCE 03_insert_business_data.sql;
-- 4. 插入产品管理和经销商管理权限
SOURCE 06_insert_product_dealer_permissions.sql;
-- 完成业务数据库初始化
SELECT 'Apple经销商ERP系统业务数据库初始化完成!' AS message;
-- Apple经销商ERP系统 - 业务表创建脚本
-- 按照数据库设计文档创建订单管理、出库查询、发票管理等相关表
-- =============================================
-- 1. 订单主表(t_order_main)
-- =============================================
CREATE TABLE t_order_main (
order_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
order_no VARCHAR(30) NOT NULL COMMENT '订单编号',
dealer_code VARCHAR(20) NOT NULL COMMENT '经销商编码',
dealer_name VARCHAR(100) NOT NULL COMMENT '经销商名称',
order_date DATETIME NOT NULL COMMENT '订单创建日期',
total_amount DECIMAL(18,2) NOT NULL COMMENT '订单总金额',
rebate_amount DECIMAL(18,2) DEFAULT 0 COMMENT '订单返利金额',
delivery_status TINYINT DEFAULT 0 COMMENT '出库状态(0-未出库/1-已出库)',
invoice_status TINYINT DEFAULT 0 COMMENT '开票状态(0-未开票/1-已开票)',
rebate_calc_flag TINYINT DEFAULT 0 COMMENT '返利计算状态(0-未计算/1-已计算)',
data_source VARCHAR(20) COMMENT '数据来源',
verify_status TINYINT DEFAULT 0 COMMENT '数据验证状态(0-待验证/1-验证通过/2-验证失败)',
upload_time DATETIME COMMENT '数据上传时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单主表';
-- =============================================
-- 2. 订单商品明细表(t_order_item)
-- =============================================
CREATE TABLE t_order_item (
item_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单商品明细ID',
order_id BIGINT NOT NULL COMMENT '关联订单ID',
order_no VARCHAR(30) NOT NULL COMMENT '订单编号',
product_code VARCHAR(30) NOT NULL COMMENT '产品编码',
product_name VARCHAR(50) NOT NULL COMMENT '产品名称',
product_qty INT NOT NULL COMMENT '商品数量',
unit_price DECIMAL(18,2) NOT NULL COMMENT '商品单价',
item_amount DECIMAL(18,2) NOT NULL COMMENT '商品明细金额',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单商品明细表';
-- =============================================
-- 3. 出库主表(t_delivery_main)
-- =============================================
CREATE TABLE t_delivery_main (
delivery_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '出库单ID',
delivery_no VARCHAR(30) NOT NULL COMMENT '出库单编号',
dealer_code VARCHAR(20) NOT NULL COMMENT '经销商编码',
dealer_name VARCHAR(100) NOT NULL COMMENT '经销商名称',
order_no VARCHAR(30) NOT NULL COMMENT '关联订单编号',
delivery_date DATETIME NOT NULL COMMENT '出库日期',
delivery_status TINYINT DEFAULT 0 COMMENT '出库状态(0-未出库/1-已出库)',
warehouse_code VARCHAR(20) COMMENT '出库仓库编码',
data_source VARCHAR(20) COMMENT '数据来源',
upload_time DATETIME COMMENT '数据上传时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='出库主表';
-- =============================================
-- 4. 出库商品明细表(t_delivery_item)
-- =============================================
CREATE TABLE t_delivery_item (
delivery_item_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '出库商品明细ID',
delivery_id BIGINT NOT NULL COMMENT '关联出库单ID',
delivery_no VARCHAR(30) NOT NULL COMMENT '出库单编号',
order_no VARCHAR(30) NOT NULL COMMENT '关联订单编号',
product_code VARCHAR(30) NOT NULL COMMENT '产品编码',
product_name VARCHAR(50) NOT NULL COMMENT '产品名称',
delivery_qty INT NOT NULL COMMENT '出库数量',
delivery_price DECIMAL(18,2) NOT NULL COMMENT '出库单价',
delivery_amount DECIMAL(18,2) NOT NULL COMMENT '出库明细金额',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='出库商品明细表';
-- =============================================
-- 5. 发票主表(t_invoice_main)
-- =============================================
CREATE TABLE t_invoice_main (
invoice_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '发票ID',
invoice_no VARCHAR(30) NOT NULL COMMENT '发票编号',
order_no VARCHAR(30) NOT NULL COMMENT '关联订单编号',
delivery_no VARCHAR(30) COMMENT '关联出库单编号',
dealer_code VARCHAR(20) NOT NULL COMMENT '经销商编码',
dealer_name VARCHAR(100) NOT NULL COMMENT '经销商名称',
total_amount DECIMAL(18,2) NOT NULL COMMENT '发票总金额(含税)',
invoice_date DATE NOT NULL COMMENT '开票日期',
invoice_status TINYINT DEFAULT 0 COMMENT '发票状态(0-未开票/1-已开票)',
tax_rate DECIMAL(5,2) DEFAULT 13.00 COMMENT '税率',
data_source VARCHAR(20) COMMENT '数据来源',
upload_time DATETIME COMMENT '数据上传时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='发票主表';
-- =============================================
-- 6. 发票商品明细表(t_invoice_item)
-- =============================================
CREATE TABLE t_invoice_item (
invoice_item_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '发票商品明细ID',
invoice_id BIGINT NOT NULL COMMENT '关联发票ID',
invoice_no VARCHAR(30) NOT NULL COMMENT '发票编号',
order_no VARCHAR(30) NOT NULL COMMENT '关联订单编号',
product_code VARCHAR(30) NOT NULL COMMENT '产品编码',
product_name VARCHAR(50) NOT NULL COMMENT '产品名称',
invoice_qty INT NOT NULL COMMENT '开票数量',
unit_price_no_tax DECIMAL(18,2) NOT NULL COMMENT '不含税单价',
amount_no_tax DECIMAL(18,2) NOT NULL COMMENT '不含税明细金额',
tax_amount DECIMAL(18,2) NOT NULL COMMENT '税额',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='发票商品明细表';
-- =============================================
-- 7. 返利台账明细表(t_rebate_detail)
-- =============================================
CREATE TABLE t_rebate_detail (
rebate_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '返利明细ID',
rebate_no VARCHAR(30) NOT NULL COMMENT '返利明细编号',
order_no VARCHAR(30) NOT NULL COMMENT '关联订单编号',
dealer_code VARCHAR(20) NOT NULL COMMENT '经销商编码',
dealer_name VARCHAR(100) NOT NULL COMMENT '经销商名称',
product_code VARCHAR(30) NOT NULL COMMENT '产品编码',
rebate_amount DECIMAL(18,2) NOT NULL COMMENT '返利金额',
rebate_date DATE NOT NULL COMMENT '返利记录日期',
operate_type TINYINT NOT NULL COMMENT '操作类型(1-新增/2-扣减)',
calc_flag TINYINT DEFAULT 0 COMMENT '返利计算状态(0-未计算/1-已计算)',
upload_time DATETIME COMMENT '数据上传时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='返利台账明细表';
-- =============================================
-- 8. 异常工单表(t_exception_workorder)
-- =============================================
CREATE TABLE t_exception_workorder (
workorder_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '工单ID',
workorder_no VARCHAR(30) NOT NULL COMMENT '工单编号',
order_no VARCHAR(30) COMMENT '关联订单编号',
dealer_code VARCHAR(20) COMMENT '经销商编码',
dealer_name VARCHAR(100) COMMENT '经销商名称',
exception_type TINYINT NOT NULL COMMENT '异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)',
severity_level TINYINT NOT NULL COMMENT '严重程度(1-高/2-中/3-低)',
workorder_status TINYINT DEFAULT 1 COMMENT '工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '工单创建时间',
expect_complete_time DATETIME COMMENT '预计处理完成时间',
handler_user VARCHAR(20) COMMENT '处理人',
exception_desc VARCHAR(500) COMMENT '异常描述',
handle_suggest VARCHAR(500) COMMENT '处理建议',
data_source VARCHAR(20) COMMENT '数据来源',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='异常工单表';
-- =============================================
-- 9. 异常工单处理日志表(t_exception_workorder_log)
-- =============================================
CREATE TABLE t_exception_workorder_log (
log_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '日志ID',
workorder_id BIGINT NOT NULL COMMENT '关联工单ID',
workorder_no VARCHAR(30) NOT NULL COMMENT '关联工单编号',
handle_user VARCHAR(20) NOT NULL COMMENT '处理人',
handle_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '处理时间',
before_status TINYINT NOT NULL COMMENT '处理前状态',
after_status TINYINT NOT NULL COMMENT '处理后状态',
handle_opinion VARCHAR(500) COMMENT '处理意见',
attach_url VARCHAR(200) COMMENT '附件URL',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='异常工单处理日志表';
-- =============================================
-- 10. 经销商信息表(t_dealer_info)
-- =============================================
CREATE TABLE t_dealer_info (
dealer_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '经销商ID',
dealer_code VARCHAR(20) NOT NULL UNIQUE COMMENT '经销商编码',
dealer_name VARCHAR(100) NOT NULL COMMENT '经销商名称',
credit_code VARCHAR(18) NOT NULL UNIQUE COMMENT '统一社会信用代码',
dealer_level TINYINT NOT NULL COMMENT '经销商等级(1-一级经销商/2-二级经销商)',
region VARCHAR(50) NOT NULL COMMENT '所在区域',
contact_person VARCHAR(50) COMMENT '联系人',
contact_phone VARCHAR(20) COMMENT '联系电话',
cooperate_start_date DATE NOT NULL COMMENT '合作起始日期',
cooperate_status TINYINT DEFAULT 1 COMMENT '合作状态(1-正常合作/2-暂停合作/3-终止合作)',
business_license_url VARCHAR(200) COMMENT '营业执照URL',
cooperation_agreement_url VARCHAR(200) COMMENT '合作协议URL',
qualification_audit_status TINYINT DEFAULT 1 COMMENT '资质审核状态(1-待审核/2-审核通过/3-审核不通过)',
audit_opinion VARCHAR(500) COMMENT '审核意见',
total_rebate_amount DECIMAL(18,2) DEFAULT 0 COMMENT '累计应返金额',
used_rebate_amount DECIMAL(18,2) DEFAULT 0 COMMENT '累计已返金额',
pending_rebate_amount DECIMAL(18,2) DEFAULT 0 COMMENT '待返利总金额',
last_rebate_update_time DATETIME COMMENT '最后返利更新时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='经销商信息表';
-- =============================================
-- 11. 产品信息表(t_product_info)
-- =============================================
CREATE TABLE t_product_info (
product_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '产品ID',
product_code VARCHAR(30) NOT NULL UNIQUE COMMENT '产品编码',
product_name VARCHAR(50) NOT NULL COMMENT '产品名称',
product_model VARCHAR(20) NOT NULL COMMENT '产品型号',
product_type VARCHAR(20) NOT NULL COMMENT '产品类别',
storage_capacity VARCHAR(10) COMMENT '存储容量',
color VARCHAR(10) COMMENT '产品颜色',
product_img_url VARCHAR(200) COMMENT '产品图片URL',
official_price DECIMAL(18,2) COMMENT '官方指导价',
sale_status TINYINT DEFAULT 1 COMMENT '销售状态(0-下架/1-在售/2-预售)',
rebate_flag TINYINT DEFAULT 1 COMMENT '是否参与返利(0-否/1-是)',
sale_start_date DATE COMMENT '销售起始日期',
sale_end_date DATE COMMENT '销售终止日期',
remark VARCHAR(500) COMMENT '产品备注',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品信息表';
-- Apple经销商ERP系统 - 业务表索引创建脚本
-- 按照数据库设计文档创建索引
-- =============================================
-- 订单主表索引
-- =============================================
-- 业务唯一索引
CREATE UNIQUE INDEX uk_order_no ON t_order_main(order_no);
-- 查询优化索引
CREATE INDEX idx_order_dealer_code ON t_order_main(dealer_code);
CREATE INDEX idx_order_date ON t_order_main(order_date);
CREATE INDEX idx_order_status ON t_order_main(delivery_status, invoice_status);
CREATE INDEX idx_order_verify ON t_order_main(verify_status);
CREATE INDEX idx_order_upload_time ON t_order_main(upload_time);
-- =============================================
-- 订单商品明细表索引
-- =============================================
-- 外键索引
CREATE INDEX idx_item_order_id ON t_order_item(order_id);
CREATE INDEX idx_item_order_no ON t_order_item(order_no);
CREATE INDEX idx_item_product_code ON t_order_item(product_code);
-- =============================================
-- 出库主表索引
-- =============================================
-- 业务唯一索引
CREATE UNIQUE INDEX uk_delivery_no ON t_delivery_main(delivery_no);
-- 查询优化索引
CREATE INDEX idx_delivery_dealer_code ON t_delivery_main(dealer_code);
CREATE INDEX idx_delivery_order_no ON t_delivery_main(order_no);
CREATE INDEX idx_delivery_date ON t_delivery_main(delivery_date);
CREATE INDEX idx_delivery_status ON t_delivery_main(delivery_status);
-- =============================================
-- 出库商品明细表索引
-- =============================================
-- 外键索引
CREATE INDEX idx_delivery_item_delivery_id ON t_delivery_item(delivery_id);
CREATE INDEX idx_delivery_item_delivery_no ON t_delivery_item(delivery_no);
CREATE INDEX idx_delivery_item_order_no ON t_delivery_item(order_no);
CREATE INDEX idx_delivery_item_product_code ON t_delivery_item(product_code);
-- =============================================
-- 发票主表索引
-- =============================================
-- 业务唯一索引
CREATE UNIQUE INDEX uk_invoice_no ON t_invoice_main(invoice_no);
-- 查询优化索引
CREATE INDEX idx_invoice_dealer_code ON t_invoice_main(dealer_code);
CREATE INDEX idx_invoice_order_no ON t_invoice_main(order_no);
CREATE INDEX idx_invoice_delivery_no ON t_invoice_main(delivery_no);
CREATE INDEX idx_invoice_date ON t_invoice_main(invoice_date);
CREATE INDEX idx_invoice_status ON t_invoice_main(invoice_status);
-- =============================================
-- 发票商品明细表索引
-- =============================================
-- 外键索引
CREATE INDEX idx_invoice_item_invoice_id ON t_invoice_item(invoice_id);
CREATE INDEX idx_invoice_item_invoice_no ON t_invoice_item(invoice_no);
CREATE INDEX idx_invoice_item_order_no ON t_invoice_item(order_no);
CREATE INDEX idx_invoice_item_product_code ON t_invoice_item(product_code);
-- =============================================
-- 返利台账明细表索引
-- =============================================
-- 业务唯一索引
CREATE UNIQUE INDEX uk_rebate_no ON t_rebate_detail(rebate_no);
-- 查询优化索引
CREATE INDEX idx_rebate_dealer_code ON t_rebate_detail(dealer_code);
CREATE INDEX idx_rebate_order_no ON t_rebate_detail(order_no);
CREATE INDEX idx_rebate_product_code ON t_rebate_detail(product_code);
CREATE INDEX idx_rebate_date ON t_rebate_detail(rebate_date);
CREATE INDEX idx_rebate_operate_type ON t_rebate_detail(operate_type);
CREATE INDEX idx_rebate_calc_flag ON t_rebate_detail(calc_flag);
-- =============================================
-- 异常工单表索引
-- =============================================
-- 业务唯一索引
CREATE UNIQUE INDEX uk_workorder_no ON t_exception_workorder(workorder_no);
-- 查询优化索引
CREATE INDEX idx_workorder_dealer_code ON t_exception_workorder(dealer_code);
CREATE INDEX idx_workorder_order_no ON t_exception_workorder(order_no);
CREATE INDEX idx_workorder_exception_type ON t_exception_workorder(exception_type);
CREATE INDEX idx_workorder_severity_level ON t_exception_workorder(severity_level);
CREATE INDEX idx_workorder_status ON t_exception_workorder(workorder_status);
CREATE INDEX idx_workorder_create_time ON t_exception_workorder(create_time);
CREATE INDEX idx_workorder_handler_user ON t_exception_workorder(handler_user);
-- =============================================
-- 异常工单处理日志表索引
-- =============================================
-- 外键索引
CREATE INDEX idx_workorder_log_workorder_id ON t_exception_workorder_log(workorder_id);
CREATE INDEX idx_workorder_log_workorder_no ON t_exception_workorder_log(workorder_no);
CREATE INDEX idx_workorder_log_handle_user ON t_exception_workorder_log(handle_user);
CREATE INDEX idx_workorder_log_handle_time ON t_exception_workorder_log(handle_time);
-- =============================================
-- 经销商信息表索引
-- =============================================
-- 业务唯一索引(已在表定义中创建)
-- UNIQUE INDEX uk_dealer_code ON t_dealer_info(dealer_code);
-- UNIQUE INDEX uk_credit_code ON t_dealer_info(credit_code);
-- 查询优化索引
CREATE INDEX idx_dealer_name ON t_dealer_info(dealer_name);
CREATE INDEX idx_dealer_level ON t_dealer_info(dealer_level);
CREATE INDEX idx_dealer_region ON t_dealer_info(region);
CREATE INDEX idx_dealer_cooperate_status ON t_dealer_info(cooperate_status);
CREATE INDEX idx_dealer_qualification_audit_status ON t_dealer_info(qualification_audit_status);
CREATE INDEX idx_dealer_cooperate_start_date ON t_dealer_info(cooperate_start_date);
-- =============================================
-- 产品信息表索引
-- =============================================
-- 业务唯一索引(已在表定义中创建)
-- UNIQUE INDEX uk_product_code ON t_product_info(product_code);
-- 查询优化索引
CREATE INDEX idx_product_name ON t_product_info(product_name);
CREATE INDEX idx_product_model ON t_product_info(product_model);
CREATE INDEX idx_product_type ON t_product_info(product_type);
CREATE INDEX idx_product_sale_status ON t_product_info(sale_status);
CREATE INDEX idx_product_rebate_flag ON t_product_info(rebate_flag);
CREATE INDEX idx_product_sale_start_date ON t_product_info(sale_start_date);
CREATE INDEX idx_product_sale_end_date ON t_product_info(sale_end_date);
-- =============================================
-- 复合索引优化查询性能
-- =============================================
-- 订单查询优化
CREATE INDEX idx_order_complex_query ON t_order_main(dealer_code, order_date, delivery_status, invoice_status);
-- 出库查询优化
CREATE INDEX idx_delivery_complex_query ON t_delivery_main(dealer_code, delivery_date, delivery_status);
-- 发票查询优化
CREATE INDEX idx_invoice_complex_query ON t_invoice_main(dealer_code, invoice_date, invoice_status);
-- 返利查询优化
CREATE INDEX idx_rebate_complex_query ON t_rebate_detail(dealer_code, rebate_date, operate_type);
-- 异常工单查询优化
CREATE INDEX idx_workorder_complex_query ON t_exception_workorder(workorder_status, severity_level, create_time);
-- 经销商查询优化
CREATE INDEX idx_dealer_complex_query ON t_dealer_info(dealer_level, cooperate_status, region);
-- 产品查询优化
CREATE INDEX idx_product_complex_query ON t_product_info(product_type, sale_status, rebate_flag);
-- Apple经销商ERP系统 - 业务测试数据插入脚本
-- 按照数据库设计文档插入测试数据
-- =============================================
-- 1. 经销商信息数据
-- =============================================
INSERT INTO t_dealer_info (dealer_id, dealer_code, dealer_name, credit_code, dealer_level, region, contact_person, contact_phone, cooperate_start_date, cooperate_status, qualification_audit_status, total_rebate_amount, used_rebate_amount, pending_rebate_amount, create_by, create_time) VALUES
(1, 'APL-DLR-001', '北京苹果科技有限公司', '91110000123456789X', 1, '华北地区', '张三', '010-12345678', '2020-01-01', 1, 2, 50000.00, 30000.00, 20000.00, 'admin', NOW()),
(2, 'APL-DLR-002', '上海苹果贸易有限公司', '91310000987654321Y', 1, '华东地区', '李四', '021-87654321', '2020-03-15', 1, 2, 80000.00, 50000.00, 30000.00, 'admin', NOW()),
(3, 'APL-DLR-003', '深圳苹果电子有限公司', '91440300112233445Z', 2, '华南地区', '王五', '0755-11223344', '2021-06-01', 1, 2, 30000.00, 15000.00, 15000.00, 'admin', NOW()),
(4, 'APL-DLR-004', '成都苹果数码有限公司', '91510100556677889A', 2, '西南地区', '赵六', '028-55667788', '2021-09-01', 1, 2, 25000.00, 10000.00, 15000.00, 'admin', NOW()),
(5, 'APL-DLR-005', '西安苹果科技发展有限公司', '91610100998877665B', 2, '西北地区', '孙七', '029-99887766', '2022-01-01', 1, 2, 20000.00, 8000.00, 12000.00, 'admin', NOW());
-- =============================================
-- 2. 产品信息数据
-- =============================================
INSERT INTO t_product_info (product_id, product_code, product_name, product_model, product_type, storage_capacity, color, official_price, sale_status, rebate_flag, sale_start_date, create_by, create_time) VALUES
(1, 'APL-IP15-128G-BK', 'iPhone 15', 'A2848', 'iPhone', '128GB', '黑色', 5999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(2, 'APL-IP15-256G-BK', 'iPhone 15', 'A2848', 'iPhone', '256GB', '黑色', 6999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(3, 'APL-IP15-128G-BL', 'iPhone 15', 'A2848', 'iPhone', '128GB', '蓝色', 5999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(4, 'APL-IP15-256G-BL', 'iPhone 15', 'A2848', 'iPhone', '256GB', '蓝色', 6999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(5, 'APL-IP15-128G-PK', 'iPhone 15', 'A2848', 'iPhone', '128GB', '粉色', 5999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(6, 'APL-IP15-256G-PK', 'iPhone 15', 'A2848', 'iPhone', '256GB', '粉色', 6999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(7, 'APL-IP15P-128G-BK', 'iPhone 15 Pro', 'A2849', 'iPhone', '128GB', '黑色', 7999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(8, 'APL-IP15P-256G-BK', 'iPhone 15 Pro', 'A2849', 'iPhone', '256GB', '黑色', 8999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(9, 'APL-IP15P-512G-BK', 'iPhone 15 Pro', 'A2849', 'iPhone', '512GB', '黑色', 10999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(10, 'APL-IP15P-1T-BK', 'iPhone 15 Pro', 'A2849', 'iPhone', '1TB', '黑色', 12999.00, 1, 1, '2023-09-15', 'admin', NOW()),
(11, 'APL-IPAD-AIR-64G-WIFI', 'iPad Air', 'A2588', 'iPad', '64GB', '银色', 4399.00, 1, 1, '2022-03-08', 'admin', NOW()),
(12, 'APL-IPAD-AIR-256G-WIFI', 'iPad Air', 'A2588', 'iPad', '256GB', '银色', 5499.00, 1, 1, '2022-03-08', 'admin', NOW()),
(13, 'APL-MAC-AIR-M2-8-256', 'MacBook Air', 'A2337', 'MacBook', '256GB', '深空灰色', 8999.00, 1, 1, '2022-07-15', 'admin', NOW()),
(14, 'APL-MAC-AIR-M2-8-512', 'MacBook Air', 'A2337', 'MacBook', '512GB', '深空灰色', 10499.00, 1, 1, '2022-07-15', 'admin', NOW()),
(15, 'APL-WATCH-SE-44-GPS', 'Apple Watch SE', 'A2351', 'Apple Watch', '44mm', '银色', 1999.00, 1, 1, '2022-09-16', 'admin', NOW());
-- =============================================
-- 3. 订单主表数据
-- =============================================
INSERT INTO t_order_main (order_id, order_no, dealer_code, dealer_name, order_date, total_amount, rebate_amount, delivery_status, invoice_status, rebate_calc_flag, data_source, verify_status, upload_time, create_by, create_time) VALUES
(1, 'ORD-2024-001', 'APL-DLR-001', '北京苹果科技有限公司', '2024-01-15 10:30:00', 119980.00, 5999.00, 1, 1, 1, 'ERP系统', 1, '2024-01-15 10:35:00', 'admin', NOW()),
(2, 'ORD-2024-002', 'APL-DLR-002', '上海苹果贸易有限公司', '2024-01-16 14:20:00', 179980.00, 8999.00, 1, 1, 1, 'ERP系统', 1, '2024-01-16 14:25:00', 'admin', NOW()),
(3, 'ORD-2024-003', 'APL-DLR-001', '北京苹果科技有限公司', '2024-01-17 09:15:00', 43990.00, 2199.50, 1, 1, 1, 'ERP系统', 1, '2024-01-17 09:20:00', 'admin', NOW()),
(4, 'ORD-2024-004', 'APL-DLR-003', '深圳苹果电子有限公司', '2024-01-18 16:45:00', 89990.00, 4499.50, 1, 1, 1, 'ERP系统', 1, '2024-01-18 16:50:00', 'admin', NOW()),
(5, 'ORD-2024-005', 'APL-DLR-004', '成都苹果数码有限公司', '2024-01-19 11:30:00', 19990.00, 999.50, 1, 1, 1, 'ERP系统', 1, '2024-01-19 11:35:00', 'admin', NOW()),
(6, 'ORD-2024-006', 'APL-DLR-002', '上海苹果贸易有限公司', '2024-01-20 13:20:00', 89990.00, 4499.50, 0, 0, 0, 'ERP系统', 0, '2024-01-20 13:25:00', 'admin', NOW()),
(7, 'ORD-2024-007', 'APL-DLR-005', '西安苹果科技发展有限公司', '2024-01-21 15:10:00', 104990.00, 5249.50, 1, 1, 1, 'ERP系统', 1, '2024-01-21 15:15:00', 'admin', NOW()),
(8, 'ORD-2024-008', 'APL-DLR-001', '北京苹果科技有限公司', '2024-01-22 10:45:00', 39990.00, 1999.50, 1, 1, 1, 'ERP系统', 1, '2024-01-22 10:50:00', 'admin', NOW()),
(9, 'ORD-2024-009', 'APL-DLR-003', '深圳苹果电子有限公司', '2024-01-23 14:30:00', 269970.00, 13498.50, 1, 1, 1, 'ERP系统', 1, '2024-01-23 14:35:00', 'admin', NOW()),
(10, 'ORD-2024-010', 'APL-DLR-004', '成都苹果数码有限公司', '2024-01-24 09:20:00', 139980.00, 6999.00, 0, 0, 0, 'ERP系统', 0, '2024-01-24 09:25:00', 'admin', NOW());
-- =============================================
-- 4. 订单商品明细表数据
-- =============================================
INSERT INTO t_order_item (item_id, order_id, order_no, product_code, product_name, product_qty, unit_price, item_amount, create_by, create_time) VALUES
(1, 1, 'ORD-2024-001', 'APL-IP15-128G-BK', 'iPhone 15 128GB 黑色', 10, 5999.00, 59990.00, 'admin', NOW()),
(2, 1, 'ORD-2024-001', 'APL-IP15-256G-BK', 'iPhone 15 256GB 黑色', 10, 6999.00, 69990.00, 'admin', NOW()),
(3, 2, 'ORD-2024-002', 'APL-IP15-128G-BL', 'iPhone 15 128GB 蓝色', 15, 5999.00, 89985.00, 'admin', NOW()),
(4, 2, 'ORD-2024-002', 'APL-IP15-256G-BL', 'iPhone 15 256GB 蓝色', 15, 6999.00, 104985.00, 'admin', NOW()),
(5, 3, 'ORD-2024-003', 'APL-IPAD-AIR-64G-WIFI', 'iPad Air 64GB WiFi', 10, 4399.00, 43990.00, 'admin', NOW()),
(6, 4, 'ORD-2024-004', 'APL-MAC-AIR-M2-8-256', 'MacBook Air M2芯片 8GB+256GB', 10, 8999.00, 89990.00, 'admin', NOW()),
(7, 5, 'ORD-2024-005', 'APL-WATCH-SE-44-GPS', 'Apple Watch SE 44mm GPS', 10, 1999.00, 19990.00, 'admin', NOW()),
(8, 6, 'ORD-2024-006', 'APL-MAC-AIR-M2-8-512', 'MacBook Air M2芯片 8GB+512GB', 10, 10499.00, 104990.00, 'admin', NOW()),
(9, 7, 'ORD-2024-007', 'APL-MAC-AIR-M2-8-512', 'MacBook Air M2芯片 8GB+512GB', 10, 10499.00, 104990.00, 'admin', NOW()),
(10, 8, 'ORD-2024-008', 'APL-IPAD-AIR-256G-WIFI', 'iPad Air 256GB WiFi', 10, 5499.00, 54990.00, 'admin', NOW()),
(11, 9, 'ORD-2024-009', 'APL-IP15-128G-BK', 'iPhone 15 128GB 黑色', 20, 5999.00, 119980.00, 'admin', NOW()),
(12, 9, 'ORD-2024-009', 'APL-IP15-256G-BK', 'iPhone 15 256GB 黑色', 20, 6999.00, 139980.00, 'admin', NOW()),
(13, 10, 'ORD-2024-010', 'APL-IP15-128G-BL', 'iPhone 15 128GB 蓝色', 10, 5999.00, 59990.00, 'admin', NOW()),
(14, 10, 'ORD-2024-010', 'APL-IPAD-AIR-64G-WIFI', 'iPad Air 64GB WiFi', 10, 4399.00, 43990.00, 'admin', NOW()),
(15, 10, 'ORD-2024-010', 'APL-WATCH-SE-44-GPS', 'Apple Watch SE 44mm GPS', 10, 1999.00, 19990.00, 'admin', NOW());
-- =============================================
-- 5. 出库主表数据
-- =============================================
INSERT INTO t_delivery_main (delivery_id, delivery_no, dealer_code, dealer_name, order_no, delivery_date, delivery_status, warehouse_code, data_source, upload_time, create_by, create_time) VALUES
(1, 'DEL-2024-001', 'APL-DLR-001', '北京苹果科技有限公司', 'ORD-2024-001', '2024-01-15 11:00:00', 1, 'WH001', 'ERP系统', '2024-01-15 11:05:00', 'admin', NOW()),
(2, 'DEL-2024-002', 'APL-DLR-002', '上海苹果贸易有限公司', 'ORD-2024-002', '2024-01-16 15:00:00', 1, 'WH002', 'ERP系统', '2024-01-16 15:05:00', 'admin', NOW()),
(3, 'DEL-2024-003', 'APL-DLR-001', '北京苹果科技有限公司', 'ORD-2024-003', '2024-01-17 10:00:00', 1, 'WH001', 'ERP系统', '2024-01-17 10:05:00', 'admin', NOW()),
(4, 'DEL-2024-004', 'APL-DLR-003', '深圳苹果电子有限公司', 'ORD-2024-004', '2024-01-18 17:00:00', 1, 'WH003', 'ERP系统', '2024-01-18 17:05:00', 'admin', NOW()),
(5, 'DEL-2024-005', 'APL-DLR-004', '成都苹果数码有限公司', 'ORD-2024-005', '2024-01-19 12:00:00', 1, 'WH004', 'ERP系统', '2024-01-19 12:05:00', 'admin', NOW()),
(6, 'DEL-2024-006', 'APL-DLR-005', '西安苹果科技发展有限公司', 'ORD-2024-007', '2024-01-21 16:00:00', 1, 'WH005', 'ERP系统', '2024-01-21 16:05:00', 'admin', NOW()),
(7, 'DEL-2024-007', 'APL-DLR-001', '北京苹果科技有限公司', 'ORD-2024-008', '2024-01-22 11:00:00', 1, 'WH001', 'ERP系统', '2024-01-22 11:05:00', 'admin', NOW()),
(8, 'DEL-2024-008', 'APL-DLR-003', '深圳苹果电子有限公司', 'ORD-2024-009', '2024-01-23 15:00:00', 1, 'WH003', 'ERP系统', '2024-01-23 15:05:00', 'admin', NOW());
-- =============================================
-- 6. 出库商品明细表数据
-- =============================================
INSERT INTO t_delivery_item (delivery_item_id, delivery_id, delivery_no, order_no, product_code, product_name, delivery_qty, delivery_price, delivery_amount, create_by, create_time) VALUES
(1, 1, 'DEL-2024-001', 'ORD-2024-001', 'APL-IP15-128G-BK', 'iPhone 15 128GB 黑色', 10, 5999.00, 59990.00, 'admin', NOW()),
(2, 1, 'DEL-2024-001', 'ORD-2024-001', 'APL-IP15-256G-BK', 'iPhone 15 256GB 黑色', 10, 6999.00, 69990.00, 'admin', NOW()),
(3, 2, 'DEL-2024-002', 'ORD-2024-002', 'APL-IP15-128G-BL', 'iPhone 15 128GB 蓝色', 15, 5999.00, 89985.00, 'admin', NOW()),
(4, 2, 'DEL-2024-002', 'ORD-2024-002', 'APL-IP15-256G-BL', 'iPhone 15 256GB 蓝色', 15, 6999.00, 104985.00, 'admin', NOW()),
(5, 3, 'DEL-2024-003', 'ORD-2024-003', 'APL-IPAD-AIR-64G-WIFI', 'iPad Air 64GB WiFi', 10, 4399.00, 43990.00, 'admin', NOW()),
(6, 4, 'DEL-2024-004', 'ORD-2024-004', 'APL-MAC-AIR-M2-8-256', 'MacBook Air M2芯片 8GB+256GB', 10, 8999.00, 89990.00, 'admin', NOW()),
(7, 5, 'DEL-2024-005', 'ORD-2024-005', 'APL-WATCH-SE-44-GPS', 'Apple Watch SE 44mm GPS', 10, 1999.00, 19990.00, 'admin', NOW()),
(8, 6, 'DEL-2024-006', 'ORD-2024-007', 'APL-MAC-AIR-M2-8-512', 'MacBook Air M2芯片 8GB+512GB', 10, 10499.00, 104990.00, 'admin', NOW()),
(9, 7, 'DEL-2024-007', 'ORD-2024-008', 'APL-IPAD-AIR-256G-WIFI', 'iPad Air 256GB WiFi', 10, 5499.00, 54990.00, 'admin', NOW()),
(10, 8, 'DEL-2024-008', 'ORD-2024-009', 'APL-IP15-128G-BK', 'iPhone 15 128GB 黑色', 20, 5999.00, 119980.00, 'admin', NOW()),
(11, 8, 'DEL-2024-008', 'ORD-2024-009', 'APL-IP15-256G-BK', 'iPhone 15 256GB 黑色', 20, 6999.00, 139980.00, 'admin', NOW());
-- =============================================
-- 7. 发票主表数据
-- =============================================
INSERT INTO t_invoice_main (invoice_id, invoice_no, order_no, delivery_no, dealer_code, dealer_name, total_amount, invoice_date, invoice_status, tax_rate, data_source, upload_time, create_by, create_time) VALUES
(1, 'INV-2024-001', 'ORD-2024-001', 'DEL-2024-001', 'APL-DLR-001', '北京苹果科技有限公司', 135580.00, '2024-01-15', 1, 13.00, 'ERP系统', '2024-01-15 12:00:00', 'admin', NOW()),
(2, 'INV-2024-002', 'ORD-2024-002', 'DEL-2024-002', 'APL-DLR-002', '上海苹果贸易有限公司', 203370.00, '2024-01-16', 1, 13.00, 'ERP系统', '2024-01-16 16:00:00', 'admin', NOW()),
(3, 'INV-2024-003', 'ORD-2024-003', 'DEL-2024-003', 'APL-DLR-001', '北京苹果科技有限公司', 49710.00, '2024-01-17', 1, 13.00, 'ERP系统', '2024-01-17 11:00:00', 'admin', NOW()),
(4, 'INV-2024-004', 'ORD-2024-004', 'DEL-2024-004', 'APL-DLR-003', '深圳苹果电子有限公司', 101690.00, '2024-01-18', 1, 13.00, 'ERP系统', '2024-01-18 18:00:00', 'admin', NOW()),
(5, 'INV-2024-005', 'ORD-2024-005', 'DEL-2024-005', 'APL-DLR-004', '成都苹果数码有限公司', 22590.00, '2024-01-19', 1, 13.00, 'ERP系统', '2024-01-19 13:00:00', 'admin', NOW()),
(6, 'INV-2024-006', 'ORD-2024-007', 'DEL-2024-006', 'APL-DLR-005', '西安苹果科技发展有限公司', 118640.00, '2024-01-21', 1, 13.00, 'ERP系统', '2024-01-21 17:00:00', 'admin', NOW()),
(7, 'INV-2024-007', 'ORD-2024-008', 'DEL-2024-007', 'APL-DLR-001', '北京苹果科技有限公司', 62140.00, '2024-01-22', 1, 13.00, 'ERP系统', '2024-01-22 12:00:00', 'admin', NOW()),
(8, 'INV-2024-008', 'ORD-2024-009', 'DEL-2024-008', 'APL-DLR-003', '深圳苹果电子有限公司', 305060.00, '2024-01-23', 1, 13.00, 'ERP系统', '2024-01-23 16:00:00', 'admin', NOW());
-- =============================================
-- 8. 发票商品明细表数据
-- =============================================
INSERT INTO t_invoice_item (invoice_item_id, invoice_id, invoice_no, order_no, product_code, product_name, invoice_qty, unit_price_no_tax, amount_no_tax, tax_amount, create_by, create_time) VALUES
(1, 1, 'INV-2024-001', 'ORD-2024-001', 'APL-IP15-128G-BK', 'iPhone 15 128GB 黑色', 10, 5308.85, 53088.50, 6901.50, 'admin', NOW()),
(2, 1, 'INV-2024-001', 'ORD-2024-001', 'APL-IP15-256G-BK', 'iPhone 15 256GB 黑色', 10, 6193.81, 61938.10, 8051.90, 'admin', NOW()),
(3, 2, 'INV-2024-002', 'ORD-2024-002', 'APL-IP15-128G-BL', 'iPhone 15 128GB 蓝色', 15, 5308.85, 79632.75, 10352.25, 'admin', NOW()),
(4, 2, 'INV-2024-002', 'ORD-2024-002', 'APL-IP15-256G-BL', 'iPhone 15 256GB 蓝色', 15, 6193.81, 92907.15, 12077.85, 'admin', NOW()),
(5, 3, 'INV-2024-003', 'ORD-2024-003', 'APL-IPAD-AIR-64G-WIFI', 'iPad Air 64GB WiFi', 10, 3892.92, 38929.20, 5060.80, 'admin', NOW()),
(6, 4, 'INV-2024-004', 'ORD-2024-004', 'APL-MAC-AIR-M2-8-256', 'MacBook Air M2芯片 8GB+256GB', 10, 7964.60, 79646.00, 10354.00, 'admin', NOW()),
(7, 5, 'INV-2024-005', 'ORD-2024-005', 'APL-WATCH-SE-44-GPS', 'Apple Watch SE 44mm GPS', 10, 1769.91, 17699.10, 2300.90, 'admin', NOW()),
(8, 6, 'INV-2024-006', 'ORD-2024-007', 'APL-MAC-AIR-M2-8-512', 'MacBook Air M2芯片 8GB+512GB', 10, 9291.15, 92911.50, 12078.50, 'admin', NOW()),
(9, 7, 'INV-2024-007', 'ORD-2024-008', 'APL-IPAD-AIR-256G-WIFI', 'iPad Air 256GB WiFi', 10, 4867.26, 48672.60, 6327.40, 'admin', NOW()),
(10, 8, 'INV-2024-008', 'ORD-2024-009', 'APL-IP15-128G-BK', 'iPhone 15 128GB 黑色', 20, 5308.85, 106177.00, 13803.00, 'admin', NOW()),
(11, 8, 'INV-2024-008', 'ORD-2024-009', 'APL-IP15-256G-BK', 'iPhone 15 256GB 黑色', 20, 6193.81, 123876.20, 16103.80, 'admin', NOW());
-- =============================================
-- 9. 返利台账明细表数据
-- =============================================
INSERT INTO t_rebate_detail (rebate_id, rebate_no, order_no, dealer_code, dealer_name, product_code, rebate_amount, rebate_date, operate_type, calc_flag, create_by, create_time) VALUES
(1, 'REB-2024-001', 'ORD-2024-001', 'APL-DLR-001', '北京苹果科技有限公司', 'APL-IP15-128G-BK', 2999.50, '2024-01-15', 1, 1, 'admin', NOW()),
(2, 'REB-2024-002', 'ORD-2024-001', 'APL-DLR-001', '北京苹果科技有限公司', 'APL-IP15-256G-BK', 3499.50, '2024-01-15', 1, 1, 'admin', NOW()),
(3, 'REB-2024-003', 'ORD-2024-002', 'APL-DLR-002', '上海苹果贸易有限公司', 'APL-IP15-128G-BL', 4499.25, '2024-01-16', 1, 1, 'admin', NOW()),
(4, 'REB-2024-004', 'ORD-2024-002', 'APL-DLR-002', '上海苹果贸易有限公司', 'APL-IP15-256G-BL', 5249.25, '2024-01-16', 1, 1, 'admin', NOW()),
(5, 'REB-2024-005', 'ORD-2024-003', 'APL-DLR-001', '北京苹果科技有限公司', 'APL-IPAD-AIR-64G-WIFI', 2199.50, '2024-01-17', 1, 1, 'admin', NOW()),
(6, 'REB-2024-006', 'ORD-2024-004', 'APL-DLR-003', '深圳苹果电子有限公司', 'APL-MAC-AIR-M2-8-256', 4499.50, '2024-01-18', 1, 1, 'admin', NOW()),
(7, 'REB-2024-007', 'ORD-2024-005', 'APL-DLR-004', '成都苹果数码有限公司', 'APL-WATCH-SE-44-GPS', 999.50, '2024-01-19', 1, 1, 'admin', NOW()),
(8, 'REB-2024-008', 'ORD-2024-007', 'APL-DLR-005', '西安苹果科技发展有限公司', 'APL-MAC-AIR-M2-8-512', 5249.50, '2024-01-21', 1, 1, 'admin', NOW()),
(9, 'REB-2024-009', 'ORD-2024-008', 'APL-DLR-001', '北京苹果科技有限公司', 'APL-IPAD-AIR-256G-WIFI', 2749.50, '2024-01-22', 1, 1, 'admin', NOW()),
(10, 'REB-2024-010', 'ORD-2024-009', 'APL-DLR-003', '深圳苹果电子有限公司', 'APL-IP15-128G-BK', 5999.00, '2024-01-23', 1, 1, 'admin', NOW()),
(11, 'REB-2024-011', 'ORD-2024-009', 'APL-DLR-003', '深圳苹果电子有限公司', 'APL-IP15-256G-BK', 6999.00, '2024-01-23', 1, 1, 'admin', NOW());
-- =============================================
-- 10. 异常工单表数据
-- =============================================
INSERT INTO t_exception_workorder (workorder_id, workorder_no, order_no, dealer_code, dealer_name, exception_type, severity_level, workorder_status, create_time, expect_complete_time, handler_user, exception_desc, handle_suggest, data_source, create_by) VALUES
(1, 'WO-2024-001', 'ORD-2024-006', 'APL-DLR-002', '上海苹果贸易有限公司', 1, 2, 1, '2024-01-20 14:00:00', '2024-01-22 18:00:00', NULL, '订单金额与产品单价不匹配', '请核实订单金额计算是否正确', 'ERP系统', 'admin'),
(2, 'WO-2024-002', 'ORD-2024-010', 'APL-DLR-004', '成都苹果数码有限公司', 2, 1, 2, '2024-01-24 10:00:00', '2024-01-25 18:00:00', '张三', '经销商资质审核未通过', '请重新提交资质材料', 'ERP系统', 'admin'),
(3, 'WO-2024-003', 'ORD-2024-001', 'APL-DLR-001', '北京苹果科技有限公司', 3, 3, 3, '2024-01-15 12:00:00', '2024-01-16 18:00:00', '李四', '返利计算异常', '已重新计算返利金额', 'ERP系统', 'admin'),
(4, 'WO-2024-004', 'ORD-2024-002', 'APL-DLR-002', '上海苹果贸易有限公司', 1, 2, 4, '2024-01-16 16:00:00', '2024-01-17 18:00:00', '王五', '发票信息不完整', '已补充完整发票信息', 'ERP系统', 'admin');
-- =============================================
-- 11. 异常工单处理日志表数据
-- =============================================
INSERT INTO t_exception_workorder_log (log_id, workorder_id, workorder_no, handle_user, handle_time, before_status, after_status, handle_opinion, create_by) VALUES
(1, 2, 'WO-2024-002', '张三', '2024-01-24 14:30:00', 1, 2, '开始处理经销商资质审核问题', 'admin'),
(2, 3, 'WO-2024-003', '李四', '2024-01-15 15:00:00', 1, 2, '开始处理返利计算异常', 'admin'),
(3, 3, 'WO-2024-003', '李四', '2024-01-15 16:30:00', 2, 3, '返利计算异常已解决,重新计算完成', 'admin'),
(4, 4, 'WO-2024-004', '王五', '2024-01-16 17:00:00', 1, 2, '开始处理发票信息不完整问题', 'admin'),
(5, 4, 'WO-2024-004', '王五', '2024-01-16 18:00:00', 2, 3, '发票信息已补充完整', 'admin'),
(6, 4, 'WO-2024-004', '王五', '2024-01-17 09:00:00', 3, 4, '工单已关闭,问题已解决', 'admin');
-- Apple经销商ERP系统对接项目 - 产品管理和经销商管理权限配置脚本
-- 创建时间: 2025-01-27
-- 版本: v1.0
-- 设置数据库和字符集
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 使用数据库
USE apple_erp;
-- =========================================
-- 1. 插入产品管理相关按钮权限
-- =========================================
-- 获取产品管理主菜单ID(menu_id = 8)
SET @product_menu_id = (SELECT menu_id FROM t_sys_menu WHERE menu_name = '产品管理' AND del_flag = '0' LIMIT 1);
-- 产品管理按钮权限
INSERT INTO t_sys_menu (parent_id, menu_name, menu_type, path, icon, sort, status, perms, create_by, create_time, update_by, update_time, del_flag)
VALUES
(@product_menu_id, '产品查询', '2', '', '', 1, '1', 'product:list', 'admin', NOW(), 'admin', NOW(), '0'),
(@product_menu_id, '产品新增', '2', '', '', 2, '1', 'product:add', 'admin', NOW(), 'admin', NOW(), '0'),
(@product_menu_id, '产品修改', '2', '', '', 3, '1', 'product:edit', 'admin', NOW(), 'admin', NOW(), '0'),
(@product_menu_id, '产品删除', '2', '', '', 4, '1', 'product:delete', 'admin', NOW(), 'admin', NOW(), '0'),
(@product_menu_id, '产品详情', '2', '', '', 5, '1', 'product:detail', 'admin', NOW(), 'admin', NOW(), '0'),
(@product_menu_id, '产品批量删除', '2', '', '', 6, '1', 'product:batchDelete', 'admin', NOW(), 'admin', NOW(), '0'),
(@product_menu_id, '产品状态修改', '2', '', '', 7, '1', 'product:updateStatus', 'admin', NOW(), 'admin', NOW(), '0'),
(@product_menu_id, '产品返利标识修改', '2', '', '', 8, '1', 'product:updateRebateFlag', 'admin', NOW(), 'admin', NOW(), '0');
-- =========================================
-- 2. 插入经销商管理相关按钮权限
-- =========================================
-- 获取经销商管理主菜单ID(menu_id = 9)
SET @dealer_menu_id = (SELECT menu_id FROM t_sys_menu WHERE menu_name = '经销商管理' AND del_flag = '0' LIMIT 1);
-- 经销商管理按钮权限
INSERT INTO t_sys_menu (parent_id, menu_name, menu_type, path, icon, sort, status, perms, create_by, create_time, update_by, update_time, del_flag)
VALUES
(@dealer_menu_id, '经销商查询', '2', '', '', 1, '1', 'dealer:list', 'admin', NOW(), 'admin', NOW(), '0'),
(@dealer_menu_id, '经销商新增', '2', '', '', 2, '1', 'dealer:add', 'admin', NOW(), 'admin', NOW(), '0'),
(@dealer_menu_id, '经销商修改', '2', '', '', 3, '1', 'dealer:update', 'admin', NOW(), 'admin', NOW(), '0'),
(@dealer_menu_id, '经销商删除', '2', '', '', 4, '1', 'dealer:delete', 'admin', NOW(), 'admin', NOW(), '0'),
(@dealer_menu_id, '经销商详情', '2', '', '', 5, '1', 'dealer:detail', 'admin', NOW(), 'admin', NOW(), '0'),
(@dealer_menu_id, '经销商批量删除', '2', '', '', 6, '1', 'dealer:batchDelete', 'admin', NOW(), 'admin', NOW(), '0'),
(@dealer_menu_id, '经销商合作状态修改', '2', '', '', 7, '1', 'dealer:updateStatus', 'admin', NOW(), 'admin', NOW(), '0'),
(@dealer_menu_id, '经销商资质审核', '2', '', '', 8, '1', 'dealer:audit', 'admin', NOW(), 'admin', NOW(), '0');
SET FOREIGN_KEY_CHECKS = 1;
SELECT 'Apple经销商ERP系统产品管理和经销商管理权限配置完成!' AS message;
# Apple经销商ERP系统 - 数据库脚本说明
# Apple经销商ERP系统 - 数据库设计文档
## 概述
本数据库设计为Apple经销商ERP系统提供完整的数据存储方案,严格按照数据库设计文档创建系统管理、订单管理、出库查询、发票管理、返利台账、异常工单等核心业务模块。
## 数据库结构
### 1. 系统管理模块
#### 系统用户表(t_sys_user)
- 存储系统用户基本信息
- 包含用户状态和登录信息
- 支持用户权限管理
#### 系统角色表(t_sys_role)
- 存储系统角色信息
- 包含角色状态和权限
- 支持角色权限管理
#### 用户角色关联表(t_sys_user_role)
- 存储用户和角色的关联关系
- 支持多对多关系
- 实现用户权限分配
#### 系统菜单表(t_sys_menu)
- 存储系统菜单信息
- 包含菜单层级和权限标识
- 支持动态菜单管理
#### 角色菜单关联表(t_sys_role_menu)
- 存储角色和菜单的关联关系
- 支持角色权限控制
- 实现菜单权限管理
#### 字典类型表(t_sys_dict_type)
- 存储字典类型信息
- 支持系统配置管理
- 实现数据字典功能
#### 字典项表(t_sys_dict_item)
- 存储字典项信息
- 关联字典类型
- 支持配置项管理
#### 系统日志表(t_sys_log)
- 存储系统操作日志
- 包含用户操作记录
- 支持审计和监控
### 2. 订单管理模块
#### 订单主表(t_order_main)
- 存储订单基本信息
- 包含经销商信息、订单状态、金额等
- 支持返利计算和数据验证
#### 订单商品明细表(t_order_item)
- 存储订单商品明细
- 包含产品信息、数量、价格等
- 支持明细金额计算
### 3. 出库管理模块
#### 出库主表(t_delivery_main)
- 存储出库单基本信息
- 关联订单信息
- 支持多种出库状态
#### 出库商品明细表(t_delivery_item)
- 存储出库商品明细
- 包含出库数量、价格等
- 支持出库金额计算
### 4. 发票管理模块
#### 发票主表(t_invoice_main)
- 存储发票基本信息
- 支持普通发票和专用发票
- 包含税率和税额计算
#### 发票商品明细表(t_invoice_item)
- 存储发票商品明细
- 支持税率计算
- 包含含税金额
### 5. 返利管理模块
#### 返利台账明细表(t_rebate_detail)
- 存储返利明细信息
- 支持新增和扣减操作
- 包含返利计算状态
### 6. 异常工单模块
#### 异常工单表(t_exception_workorder)
- 存储异常工单信息
- 支持多种异常类型
- 包含严重程度和状态管理
#### 异常工单处理日志表(t_exception_workorder_log)
- 存储工单处理日志
- 记录处理过程和结果
- 支持附件管理
### 7. 基础信息模块
#### 经销商信息表(t_dealer_info)
- 存储经销商基本信息
- 包含资质审核和合作状态
- 支持返利金额统计
#### 产品信息表(t_product_info)
- 存储产品基本信息
- 包含价格和销售状态
- 支持返利标识
## 脚本文件说明
### 1. 系统管理表创建脚本
### 系统管理脚本
#### 1. 系统管理表创建脚本
- **文件**: `01_create_system_tables.sql`
- **功能**: 创建系统管理相关的数据库表
- **包含表**:
......@@ -15,7 +132,7 @@
- `t_sys_dict_item` - 字典项表
- `t_sys_log` - 系统日志表
### 2. 系统管理表索引创建脚本
#### 2. 系统管理表索引创建脚本
- **文件**: `02_create_system_indexes.sql`
- **功能**: 为系统管理表创建索引和外键约束
- **包含内容**:
......@@ -24,7 +141,7 @@
- 外键约束
- 唯一约束索引
### 3. 系统基础数据插入脚本
#### 3. 系统基础数据插入脚本
- **文件**: `03_insert_system_data.sql`
- **功能**: 插入系统初始化基础数据
- **包含数据**:
......@@ -35,24 +152,176 @@
- 角色菜单关联数据
- 字典类型和字典项数据
### 4. 系统表结构验证脚本
- **文件**: `04_verify_system_structure.sql`
- **功能**: 验证系统表结构的完整性和数据一致性
- **验证内容**:
- 表结构验证
- 索引结构验证
- 外键约束验证
- 基础数据验证
- 数据完整性检查
### 业务管理脚本
#### 4. 业务表创建脚本
- **文件**: `01_create_business_tables.sql`
- **功能**: 创建业务管理相关的数据库表
- **包含表**:
- `t_order_main` - 订单主表
- `t_order_item` - 订单商品明细表
- `t_delivery_main` - 出库主表
- `t_delivery_item` - 出库商品明细表
- `t_invoice_main` - 发票主表
- `t_invoice_item` - 发票商品明细表
- `t_rebate_detail` - 返利台账明细表
- `t_exception_workorder` - 异常工单表
- `t_exception_workorder_log` - 异常工单处理日志表
- `t_dealer_info` - 经销商信息表
- `t_product_info` - 产品信息表
#### 5. 业务表索引创建脚本
- **文件**: `02_create_business_indexes.sql`
- **功能**: 为业务管理表创建索引
- **包含内容**:
- 主键索引、唯一索引、普通索引
- 复合索引优化查询性能
- 支持高效的数据检索
#### 6. 业务测试数据插入脚本
- **文件**: `03_insert_business_data.sql`
- **功能**: 插入业务测试数据
- **包含数据**:
- 经销商数据(5个经销商)
- 产品数据(15个Apple产品)
- 订单数据(10个订单及明细)
- 出库数据(8个出库单及明细)
- 发票数据(8张发票及明细)
- 返利数据(11条返利记录)
- 异常工单数据(4个异常工单及处理日志)
#### 7. 业务数据库初始化脚本
- **文件**: `00_setup_business_database.sql`
- **功能**: 按顺序执行所有业务数据库脚本
- **执行顺序**:
1. 创建业务表
2. 创建业务索引
3. 插入业务测试数据
## 执行顺序
### 系统管理数据库初始化
请按照以下顺序执行脚本:
1. `01_create_system_tables.sql` - 创建表结构
2. `02_create_system_indexes.sql` - 创建索引和约束
3. `03_insert_system_data.sql` - 插入基础数据
4. `04_verify_system_structure.sql` - 验证结构完整性
1. `01_create_system_tables.sql` - 创建系统表结构
2. `02_create_system_indexes.sql` - 创建系统表索引和约束
3. `03_insert_system_data.sql` - 插入系统基础数据
### 业务管理数据库初始化
请按照以下顺序执行脚本:
1. `01_create_business_tables.sql` - 创建业务表结构
2. `02_create_business_indexes.sql` - 创建业务表索引
3. `03_insert_business_data.sql` - 插入业务测试数据
4. `06_insert_product_dealer_permissions.sql` - 插入产品管理和经销商管理权限
或者直接执行:
- `00_setup_business_database.sql` - 自动执行业务数据库初始化(包含权限配置)
## 权限管理脚本
### 产品管理和经销商管理权限脚本 (06_insert_product_dealer_permissions.sql)
**功能:** 配置产品管理和经销商管理相关菜单和权限
**包含内容:**
- 产品管理按钮权限(基于现有产品管理主菜单)
- 经销商管理按钮权限(基于现有经销商管理主菜单)
- 为ADMIN角色分配产品管理和经销商管理权限
- 权限验证查询
**产品管理权限列表:**
- `product:list` - 产品查询
- `product:add` - 产品新增
- `product:update` - 产品修改
- `product:delete` - 产品删除
- `product:detail` - 产品详情
- `product:batchDelete` - 产品批量删除
- `product:updateStatus` - 产品状态修改
- `product:updateRebateFlag` - 产品返利标识修改
**经销商管理权限列表:**
- `dealer:list` - 经销商查询
- `dealer:add` - 经销商新增
- `dealer:update` - 经销商修改
- `dealer:delete` - 经销商删除
- `dealer:detail` - 经销商详情
- `dealer:batchDelete` - 经销商批量删除
- `dealer:updateStatus` - 经销商合作状态修改
- `dealer:audit` - 经销商资质审核
**说明:**
- 基于`03_insert_system_data.sql`中已存在的产品管理和经销商管理主菜单
- 只添加按钮级别的权限,不重复创建主菜单
- 所有权限都会自动分配给ADMIN角色
## 核心特性
### 1. 完整的业务流程
- 订单 → 出库 → 发票 → 返利
- 异常工单处理流程
- 数据验证和状态管理
### 2. 丰富的测试数据
- 5个经销商(一级和二级)
- 15个Apple产品
- 10个订单及明细
- 8个出库单及明细
- 8张发票及明细
- 11条返利记录
- 4个异常工单及处理日志
### 3. 优化的索引设计
- 主键索引、唯一索引、普通索引
- 复合索引优化查询性能
- 支持高效的数据检索
### 4. 数据一致性
- 业务编号唯一性约束
- 状态字段标准化
- 金额字段精度控制
## 数据关系
### 订单流程
1. 创建订单 → 订单明细
2. 确认订单 → 生成出库单
3. 出库完成 → 生成发票
4. 发票开具 → 计算返利
### 数据关联
- 订单 → 出库单(一对多)
- 出库单 → 发票(一对一)
- 经销商 → 订单(一对多)
- 产品 → 订单明细(一对多)
- 异常工单 → 处理日志(一对多)
## 性能优化
### 索引策略
- 主键使用自增ID
- 业务编号使用唯一索引
- 查询字段建立复合索引
- 状态字段建立普通索引
### 查询优化
- 分页查询优化
- 状态查询优化
- 时间范围查询优化
- 金额范围查询优化
## 扩展性设计
### 表结构扩展
- 预留扩展字段
- 支持自定义字段
- 版本控制支持
### 业务扩展
- 支持多经销商管理
- 支持多种产品类型
- 支持多种支付方式
- 支持多种配送方式
## 默认用户账号
......@@ -64,13 +333,57 @@
| operator | operator123 | 运营人员 | 拥有业务操作权限 |
| auditor | auditor123 | 审核人员 | 拥有审核权限 |
## 测试数据说明
### 经销商数据
- 5个经销商(3个一级,2个二级)
- 涵盖不同地区和合作状态
- 包含完整的资质信息
### 产品数据
- 15个Apple产品
- 涵盖iPhone、iPad、MacBook、Apple Watch
- 包含不同规格和价格
### 订单数据
- 10个订单
- 涵盖不同状态和类型
- 包含完整的订单明细
### 出库数据
- 8个出库单
- 关联对应订单
- 包含出库明细
### 发票数据
- 8张发票
- 支持含税金额计算
- 包含发票明细
### 返利数据
- 11条返利记录
- 支持新增和扣减操作
- 包含返利计算状态
### 异常工单数据
- 4个异常工单
- 涵盖不同异常类型和严重程度
- 包含处理日志
## 注意事项
1. 所有脚本都包含错误处理,可以重复执行
2. 密码已使用BCrypt加密存储
3. 所有表都包含软删除标志(del_flag)
4. 外键约束已正确设置,确保数据完整性
5. 索引已优化,支持高效查询
1. 所有金额字段使用decimal(18,2)类型
2. 时间字段统一使用datetime类型
3. 状态字段使用tinyint类型
4. 字符串字段根据实际需要设置长度
5. 所有表都包含创建和更新信息
6. 重要字段不允许为空
7. 业务编号保持唯一性
8. 不创建外键约束,通过应用层保证数据一致性
9. 所有脚本都包含错误处理,可以重复执行
10. 密码已使用BCrypt加密存储
11. 所有表都包含软删除标志(del_flag)
12. 索引已优化,支持高效查询
## 数据库要求
......@@ -78,3 +391,11 @@
- 字符集: utf8mb4
- 排序规则: utf8mb4_unicode_ci
- 存储引擎: InnoDB
## 使用说明
1. 按照脚本顺序执行数据库初始化
2. 确保MySQL版本8.0或以上
3. 使用utf8mb4字符集
4. 建议使用InnoDB存储引擎
5. 根据实际需要调整测试数据
......
import { request } from '@/utils/request'
import axios from 'axios'
import type { LoginForm, LoginResponse, User } from '@/types'
// 创建axios实例
const api = axios.create({
baseURL: 'http://localhost:8083/api',
timeout: 10000
})
// 请求拦截器
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 => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
// 登录
export function loginApi(data: LoginForm): Promise<LoginResponse> {
return request.post('/api/auth/login', data)
return api.post('/auth/login', data)
}
// 登出
export function logoutApi(): Promise<void> {
return request.post('/api/auth/logout')
return api.post('/auth/logout')
}
// 获取用户信息
export function getUserInfoApi(): Promise<User> {
return request.get('/api/auth/userInfo')
return api.get('/auth/userInfo')
}
// 刷新Token
export function refreshTokenApi(): Promise<{ token: string }> {
return request.post('/api/auth/refresh')
return api.post('/auth/refresh')
}
// 修改密码
......@@ -26,10 +61,10 @@ export function changePasswordApi(data: {
oldPassword: string
newPassword: string
}): Promise<void> {
return request.post('/api/auth/changePassword', data)
return api.post('/auth/changePassword', data)
}
// 获取验证码
export function getCaptchaApi(): Promise<{ captchaId: string; captchaImage: string }> {
return request.get('/api/auth/captcha')
return api.get('/auth/captcha')
}
......
import axios from 'axios'
// 创建axios实例
const api = axios.create({
baseURL: 'http://localhost:8083/api',
timeout: 10000
})
// 请求拦截器
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) => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
// 经销商信息接口
export interface DealerInfo {
dealerId: number
dealerCode: string
dealerName: string
creditCode: string
dealerLevel: number
region: string
contactPerson: string
contactPhone: string
cooperateStartDate: string
cooperateStatus: number
businessLicenseUrl: string
cooperationAgreementUrl: string
qualificationAuditStatus: number
auditOpinion: string
createTime: string
updateTime: string
}
// 经销商查询请求参数
export interface DealerQueryReq {
dealerCode?: string
dealerName?: string
creditCode?: string
dealerLevel?: number
region?: string
contactPerson?: string
contactPhone?: string
cooperateStatus?: number
qualificationAuditStatus?: number
pageNum: number
pageSize: number
}
// 经销商新增请求参数
export interface DealerAddReq {
dealerCode: string
dealerName: string
creditCode: string
dealerLevel: number
region: string
contactPerson?: string
contactPhone?: string
cooperateStartDate: string
cooperateStatus?: number
businessLicenseUrl?: string
cooperationAgreementUrl?: string
qualificationAuditStatus?: number
auditOpinion?: string
}
// 经销商修改请求参数
export interface DealerUpdateReq {
dealerId: number
dealerCode: string
dealerName: string
creditCode: string
dealerLevel: number
region: string
contactPerson?: string
contactPhone?: string
cooperateStartDate: string
cooperateStatus?: number
businessLicenseUrl?: string
cooperationAgreementUrl?: string
qualificationAuditStatus?: number
auditOpinion?: string
}
// 分页数据接口
export interface PageData<T> {
records: T[]
total: number
pageNum: number
pageSize: number
}
// API响应接口
export interface ApiResponse<T = any> {
code: number
message: string
data: T
}
// 经销商管理API
export const dealerApi = {
// 分页查询经销商列表
getDealerList: (params: DealerQueryReq) => {
return api.get<ApiResponse<PageData<DealerInfo>>>('/dealer/list', { params })
},
// 获取经销商详情
getDealerDetail: (dealerId: number) => {
return api.get<ApiResponse<DealerInfo>>(`/dealer/${dealerId}`)
},
// 新增经销商
addDealer: (data: DealerAddReq) => {
return api.post<ApiResponse>('/dealer', data)
},
// 修改经销商
updateDealer: (data: DealerUpdateReq) => {
return api.post<ApiResponse>('/dealer/update', data)
},
// 删除经销商
deleteDealer: (dealerId: number) => {
return api.delete<ApiResponse>(`/dealer/${dealerId}`)
},
// 批量删除经销商
batchDeleteDealer: (dealerIds: number[]) => {
return api.delete<ApiResponse>('/dealer/batch', { data: dealerIds })
},
// 修改经销商合作状态
updateCooperateStatus: (dealerId: number, cooperateStatus: number) => {
return api.post<ApiResponse>(`/dealer/${dealerId}/cooperateStatus/${cooperateStatus}`)
},
// 修改经销商资质审核状态
updateAuditStatus: (dealerId: number, qualificationAuditStatus: number, auditOpinion?: string) => {
return api.post<ApiResponse>(`/dealer/${dealerId}/auditStatus`, null, {
params: { qualificationAuditStatus, auditOpinion }
})
}
}
import axios from 'axios'
// 创建axios实例
const api = axios.create({
baseURL: 'http://localhost:8083/api',
timeout: 10000
})
// 请求拦截器
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 => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
// 产品信息接口
export interface ProductInfo {
productId: number
productCode: string
productName: string
productModel: string
productType: string
storageCapacity: string
color: string
officialPrice: number
saleStatus: number
rebateFlag: number
saleStartDate: string
saleEndDate: string
imageUrl: string
remark: string
createTime: string
updateTime: string
}
// 产品查询请求参数
export interface ProductQueryReq {
productCode?: string
productName?: string
productModel?: string
saleStatus?: number
rebateFlag?: number
pageNum: number
pageSize: number
}
// 产品新增请求参数
export interface ProductAddReq {
productCode: string
productName: string
productModel: string
productType: string
storageCapacity: string
color: string
officialPrice: number
saleStatus: number
rebateFlag: number
saleStartDate: string
saleEndDate: string
imageUrl?: string
remark?: string
}
// 产品修改请求参数
export interface ProductUpdateReq {
productId: number
productCode: string
productName: string
productModel: string
productType: string
storageCapacity: string
color: string
officialPrice: number
saleStatus: number
rebateFlag: number
saleStartDate: string
saleEndDate: string
imageUrl?: string
remark?: string
}
// 分页数据接口
export interface PageData<T> {
records: T[]
total: number
current: number
size: number
pages: number
}
// API响应接口
export interface ApiResponse<T> {
code: number
message: string
data: T
}
// API接口
export const productApi = {
// 获取产品列表
getProductList: (params: ProductQueryReq) => {
return api.get<ApiResponse<PageData<ProductInfo>>>('/product/list', { params })
},
// 获取产品详情
getProductDetail: (productId: number) => {
return api.get<ProductInfo>(`/product/${productId}`)
},
// 新增产品
addProduct: (data: ProductAddReq) => {
return api.post('/product', data)
},
// 修改产品
updateProduct: (data: ProductUpdateReq) => {
return api.post('/product/update', data)
},
// 删除产品
deleteProduct: (productId: number) => {
return api.delete(`/product/${productId}`)
},
// 批量删除产品
batchDeleteProduct: (productIds: number[]) => {
return api.post('/product/batchDelete', productIds)
},
// 修改销售状态
updateSaleStatus: (productId: number, saleStatus: number) => {
return api.post(`/product/${productId}/status`, null, { params: { saleStatus } })
},
// 修改返利标识
updateRebateFlag: (productId: number, rebateFlag: number) => {
return api.post(`/product/${productId}/rebate`, null, { params: { rebateFlag } })
}
}
......@@ -124,6 +124,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { logoutApi } from '../api/auth'
const router = useRouter()
const route = useRoute()
......@@ -140,6 +141,8 @@ const tabContainer = ref<HTMLElement>()
// 菜单项配置
const menuItems = ref([
{ name: '首页', path: '/main/dashboard', icon: '🏠' },
{ name: '产品管理', path: '/main/product', icon: '📦' },
{ name: '经销商管理', path: '/main/dealer', icon: '🏢' },
{
name: '系统设置',
icon: '⚙️',
......@@ -314,14 +317,21 @@ const toggleSidebar = () => {
}
// 退出登录
const handleLogout = () => {
const handleLogout = async () => {
if (confirm('确定要退出登录吗?')) {
// 清除本地存储
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
// 跳转到登录页
router.push('/')
try {
// 调用后端退出登录接口
await logoutApi()
} catch (error) {
console.error('退出登录接口调用失败:', error)
} finally {
// 无论接口调用成功与否,都清除本地存储
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
// 跳转到登录页
router.push('/')
}
}
}
......
......@@ -45,6 +45,15 @@ const staticRoutes: RouteRecordRaw[] = [
}
},
{
path: 'product',
name: 'Product',
component: () => import('@/views/product/index.vue'),
meta: {
title: '产品管理',
requiresAuth: true
}
},
{
path: 'sys/role',
name: 'Role',
component: () => import('@/views/sys/role/index.vue'),
......@@ -108,6 +117,24 @@ const staticRoutes: RouteRecordRaw[] = [
}
},
{
path: 'product',
name: 'Product',
component: () => import('@/views/product/index.vue'),
meta: {
title: '产品管理',
requiresAuth: true
}
},
{
path: 'dealer',
name: 'Dealer',
component: () => import('@/views/dealer/index.vue'),
meta: {
title: '经销商管理',
requiresAuth: true
}
},
{
path: 'settings',
name: 'Settings',
component: () => import('@/views/settings/index.vue'),
......
<template>
<div class="dealer-container">
<!-- 搜索筛选区域 -->
<div class="search-section">
<div class="search-row">
<div class="search-item">
<label>经销商编码:</label>
<input
v-model="searchParams.dealerCode"
type="text"
class="search-input"
placeholder="请输入经销商编码"
/>
</div>
<div class="search-item">
<label>经销商名称:</label>
<input
v-model="searchParams.dealerName"
type="text"
class="search-input"
placeholder="请输入经销商名称"
/>
</div>
<div class="search-item">
<label>经销商等级:</label>
<select v-model="searchParams.dealerLevel" class="search-select">
<option value="">所有</option>
<option value="1">一级经销商</option>
<option value="2">二级经销商</option>
</select>
</div>
<div class="search-item">
<label>合作状态:</label>
<select v-model="searchParams.cooperateStatus" class="search-select">
<option value="">所有</option>
<option value="1">正常合作</option>
<option value="2">暂停合作</option>
<option value="3">终止合作</option>
</select>
</div>
<div class="search-actions">
<button @click="handleSearch" class="search-btn">🔍 搜索</button>
<button @click="handleReset" class="reset-btn">🔄 重置</button>
</div>
</div>
</div>
<!-- 操作按钮区域 -->
<div class="action-section">
<div class="action-buttons">
<button @click="handleAdd" class="action-btn primary">✨ 新增</button>
<button @click="handleBatchDelete" class="action-btn danger">🗑️ 删除</button>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="table-controls">
<button @click="handleTableSearch" class="control-btn" title="搜索">🔍</button>
<button @click="handleTableRefresh" class="control-btn" title="刷新">🔄</button>
<button @click="handleTableExport" class="control-btn" title="导出">📋</button>
<button @click="handleTableViewToggle" class="control-btn" title="视图切换">⊞</button>
</div>
</div>
<div class="table-container">
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner">加载中...</div>
</div>
<table class="data-table">
<thead>
<tr>
<th>
<input
type="checkbox"
class="select-all"
v-model="selectAll"
@change="handleSelectAll"
/>
</th>
<th>经销商编码</th>
<th>经销商名称</th>
<th>统一社会信用代码</th>
<th>经销商等级</th>
<th>所在区域</th>
<th>联系人</th>
<th>联系电话</th>
<th>合作起始日期</th>
<th>合作状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="dealer in dealerList" :key="dealer.dealerId">
<td>
<input
type="checkbox"
:value="dealer.dealerId"
v-model="selectedDealers"
/>
</td>
<td>{{ dealer.dealerCode }}</td>
<td>{{ dealer.dealerName }}</td>
<td>{{ dealer.creditCode }}</td>
<td>{{ getDealerLevelText(dealer.dealerLevel) }}</td>
<td>{{ dealer.region }}</td>
<td>{{ dealer.contactPerson }}</td>
<td>{{ dealer.contactPhone }}</td>
<td>{{ dealer.cooperateStartDate }}</td>
<td>
<span :class="getCooperateStatusClass(dealer.cooperateStatus)">
{{ getCooperateStatusText(dealer.cooperateStatus) }}
</span>
</td>
<td>
<button @click="handleEdit(dealer)" class="table-btn edit">✏️ 编辑</button>
<button @click="handleDelete(dealer)" class="table-btn delete">🗑️ 删除</button>
<button @click="handleStatusChange(dealer)" class="table-btn status">🔄 状态</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页信息 -->
<div class="table-footer">
<div class="pagination-left">
<div class="pagination-info">
共 {{ total || 0 }} 条记录,第 {{ currentPage || 1 }} / {{ totalPages || 1 }} 页
</div>
<div class="page-size-selector">
<label>每页显示:</label>
<select v-model="pagination.pageSize" @change="handlePageSizeChange" class="page-size-select">
<option value="10">10条</option>
<option value="20">20条</option>
<option value="50">50条</option>
<option value="100">100条</option>
</select>
</div>
</div>
<div class="pagination-container">
<button
@click="handlePageChange(currentPage - 1)"
:disabled="currentPage <= 1"
class="pagination-btn prev-btn"
>
上一页
</button>
<div class="pagination-pages">
<button
v-for="page in getPageNumbers()"
:key="page"
@click="handlePageChange(page)"
:class="['page-number', { active: page === currentPage }]"
>
{{ page }}
</button>
</div>
<button
@click="handlePageChange(currentPage + 1)"
:disabled="currentPage >= totalPages"
class="pagination-btn next-btn"
>
下一页
</button>
</div>
</div>
</div>
<!-- 新增/编辑对话框 -->
<div v-if="showDialog" class="dialog-overlay" @click="handleDialogClose">
<div class="dialog-content" @click.stop>
<div class="dialog-header">
<h3>{{ isEdit ? '编辑经销商' : '新增经销商' }}</h3>
<button @click="handleDialogClose" class="dialog-close">×</button>
</div>
<div class="dialog-body">
<form @submit.prevent="handleSubmit" class="form-container">
<div class="form-row">
<div class="form-item">
<label class="required">经销商编码:</label>
<input
v-model="formData.dealerCode"
class="form-input"
placeholder="请输入经销商编码(6-20位大写字母和数字)"
pattern="^[A-Z0-9]{6,20}$"
title="经销商编码应为6-20位大写字母和数字"
required
/>
</div>
<div class="form-item">
<label class="required">经销商名称:</label>
<input
v-model="formData.dealerName"
class="form-input"
placeholder="请输入经销商名称"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">统一社会信用代码:</label>
<input
v-model="formData.creditCode"
class="form-input"
placeholder="请输入统一社会信用代码"
required
/>
</div>
<div class="form-item">
<label class="required">经销商等级:</label>
<select v-model="formData.dealerLevel" class="form-select" required>
<option value="">请选择</option>
<option value="1">一级经销商</option>
<option value="2">二级经销商</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">所在区域:</label>
<input
v-model="formData.region"
class="form-input"
placeholder="请输入所在区域"
required
/>
</div>
<div class="form-item">
<label>联系人:</label>
<input
v-model="formData.contactPerson"
class="form-input"
placeholder="请输入联系人"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>联系电话:</label>
<input
v-model="formData.contactPhone"
class="form-input"
placeholder="请输入联系电话"
/>
</div>
<div class="form-item">
<label class="required">合作起始日期:</label>
<input
v-model="formData.cooperateStartDate"
class="form-input"
type="date"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>合作状态:</label>
<select v-model="formData.cooperateStatus" class="form-select">
<option value="1">正常合作</option>
<option value="2">暂停合作</option>
<option value="3">终止合作</option>
</select>
</div>
<div class="form-item">
<label>营业执照URL:</label>
<input
v-model="formData.businessLicenseUrl"
class="form-input"
placeholder="请输入营业执照URL"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>合作协议URL:</label>
<input
v-model="formData.cooperationAgreementUrl"
class="form-input"
placeholder="请输入合作协议URL"
/>
</div>
<div class="form-item">
<label>审核意见:</label>
<input
v-model="formData.auditOpinion"
class="form-input"
placeholder="请输入审核意见"
/>
</div>
</div>
<div class="form-row">
<div class="form-item full-width">
<label>备注:</label>
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="请输入备注"
rows="3"
></textarea>
</div>
</div>
</form>
</div>
<div class="dialog-footer">
<button @click="handleDialogClose" class="dialog-btn cancel">取消</button>
<button @click="handleSubmit" class="dialog-btn confirm">确定</button>
</div>
</div>
</div>
<!-- 状态修改对话框 -->
<div v-if="showStatusDialog" class="dialog-overlay" @click="handleStatusDialogClose">
<div class="dialog-content" @click.stop>
<div class="dialog-header">
<h3>修改合作状态</h3>
<button @click="handleStatusDialogClose" class="dialog-close">×</button>
</div>
<div class="dialog-body">
<div class="form-item">
<label>经销商名称:</label>
<input v-model="currentDealer.dealerName" class="form-input" readonly />
</div>
<div class="form-item">
<label>当前状态:</label>
<input :value="getCooperateStatusText(currentDealer.cooperateStatus)" class="form-input" readonly />
</div>
<div class="form-item">
<label>新状态:</label>
<select v-model="newCooperateStatus" class="form-select">
<option value="1">正常合作</option>
<option value="2">暂停合作</option>
<option value="3">终止合作</option>
</select>
</div>
</div>
<div class="dialog-footer">
<button @click="handleStatusDialogClose" class="dialog-btn cancel">取消</button>
<button @click="handleStatusSubmit" class="dialog-btn confirm">确定</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { dealerApi, type DealerInfo, type DealerQueryReq, type DealerAddReq, type DealerUpdateReq } from '../../api/dealer'
// 响应式数据
const loading = ref(false)
const dealerList = ref<DealerInfo[]>([])
const selectedDealers = ref<number[]>([])
const showDialog = ref(false)
const isEdit = ref(false)
const showStatusDialog = ref(false)
const currentDealer = ref<DealerInfo>({} as DealerInfo)
const newCooperateStatus = ref<number>(1)
// 搜索参数
const searchParams = reactive<DealerQueryReq>({
dealerCode: '',
dealerName: '',
creditCode: '',
dealerLevel: undefined,
region: '',
contactPerson: '',
contactPhone: '',
cooperateStatus: undefined,
pageNum: 1,
pageSize: 10
})
// 分页信息
const pagination = reactive({
total: 0,
pageNum: 1,
pageSize: 10
})
// 计算属性
const total = computed(() => pagination.total)
const currentPage = computed(() => pagination.pageNum)
const pageSize = computed(() => pagination.pageSize)
const totalPages = computed(() => {
if (pagination.pageSize <= 0) return 1
return Math.ceil(pagination.total / pagination.pageSize) || 1
})
// 表单数据
const formData = reactive<DealerAddReq & { dealerId?: number }>({
dealerCode: '',
dealerName: '',
creditCode: '',
dealerLevel: 1,
region: '',
contactPerson: '',
contactPhone: '',
cooperateStartDate: '',
cooperateStatus: 1,
businessLicenseUrl: '',
cooperationAgreementUrl: '',
qualificationAuditStatus: 1,
auditOpinion: ''
})
// 计算属性
const selectAll = computed({
get: () => selectedDealers.value.length === dealerList.value.length && dealerList.value.length > 0,
set: (value: boolean) => {
if (value) {
selectedDealers.value = dealerList.value.map(dealer => dealer.dealerId)
} else {
selectedDealers.value = []
}
}
})
// 获取经销商等级文本
const getDealerLevelText = (level: number) => {
return level === 1 ? '一级经销商' : '二级经销商'
}
// 获取合作状态文本
const getCooperateStatusText = (status: number) => {
const statusMap: { [key: number]: string } = {
1: '正常合作',
2: '暂停合作',
3: '终止合作'
}
return statusMap[status] || '未知'
}
// 获取合作状态样式
const getCooperateStatusClass = (status: number) => {
const classMap: { [key: number]: string } = {
1: 'status-normal',
2: 'status-warning',
3: 'status-danger'
}
return classMap[status] || ''
}
// 获取分页页码数组
const getPageNumbers = () => {
const pages = []
const totalPagesValue = totalPages.value
if (totalPagesValue <= 0) return []
const start = Math.max(1, currentPage.value - 2)
const end = Math.min(totalPagesValue, start + 4)
for (let i = start; i <= end; i++) {
pages.push(i)
}
return pages
}
// 获取经销商列表
const fetchDealers = async () => {
try {
loading.value = true
const response = await dealerApi.getDealerList(searchParams)
if (response.code === 200) {
dealerList.value = response.data.records
pagination.total = response.data.total
pagination.pageNum = response.data.current
pagination.pageSize = response.data.size
}
} catch (error) {
console.error('获取经销商列表失败:', error)
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
pagination.pageNum = 1
fetchDealers()
}
// 重置
const handleReset = () => {
Object.assign(searchParams, {
dealerCode: '',
dealerName: '',
creditCode: '',
dealerLevel: undefined,
region: '',
contactPerson: '',
contactPhone: '',
cooperateStatus: undefined
})
pagination.pageNum = 1
fetchDealers()
}
// 新增
const handleAdd = () => {
isEdit.value = false
Object.assign(formData, {
dealerCode: '',
dealerName: '',
creditCode: '',
dealerLevel: 1,
region: '',
contactPerson: '',
contactPhone: '',
cooperateStartDate: '',
cooperateStatus: 1,
businessLicenseUrl: '',
cooperationAgreementUrl: ''
})
showDialog.value = true
}
// 编辑
const handleEdit = (dealer: DealerInfo) => {
isEdit.value = true
Object.assign(formData, {
dealerId: dealer.dealerId,
dealerCode: dealer.dealerCode,
dealerName: dealer.dealerName,
creditCode: dealer.creditCode,
dealerLevel: dealer.dealerLevel,
region: dealer.region,
contactPerson: dealer.contactPerson,
contactPhone: dealer.contactPhone,
cooperateStartDate: dealer.cooperateStartDate,
cooperateStatus: dealer.cooperateStatus,
businessLicenseUrl: dealer.businessLicenseUrl,
cooperationAgreementUrl: dealer.cooperationAgreementUrl
})
showDialog.value = true
}
// 删除
const handleDelete = async (dealer: DealerInfo) => {
if (confirm(`确定要删除经销商"${dealer.dealerName}"吗?`)) {
try {
const response = await dealerApi.deleteDealer(dealer.dealerId)
if (response.code === 200) {
alert('删除成功')
fetchDealers()
} else {
alert(response.message || '删除失败')
}
} catch (error: any) {
console.error('删除失败:', error)
// 处理网络错误或服务器错误
if (error.response && error.response.data) {
const errorData = error.response.data
let errorMessage = errorData.message || '删除失败'
// 如果有具体的字段验证错误,显示详细错误信息
if (errorData.data && typeof errorData.data === 'object') {
const fieldErrors = Object.values(errorData.data).filter((err: any) => typeof err === 'string')
if (fieldErrors.length > 0) {
errorMessage = fieldErrors.join('; ')
}
}
alert(errorMessage)
} else {
alert('删除失败')
}
}
}
}
// 批量删除
const handleBatchDelete = async () => {
if (selectedDealers.value.length === 0) {
alert('请选择要删除的经销商')
return
}
if (confirm(`确定要删除选中的 ${selectedDealers.value.length} 个经销商吗?`)) {
try {
const response = await dealerApi.batchDeleteDealer(selectedDealers.value)
if (response.code === 200) {
alert('批量删除成功')
selectedDealers.value = []
fetchDealers()
} else {
alert(response.message || '批量删除失败')
}
} catch (error: any) {
console.error('批量删除失败:', error)
// 处理网络错误或服务器错误
if (error.response && error.response.data) {
const errorData = error.response.data
let errorMessage = errorData.message || '批量删除失败'
// 如果有具体的字段验证错误,显示详细错误信息
if (errorData.data && typeof errorData.data === 'object') {
const fieldErrors = Object.values(errorData.data).filter((err: any) => typeof err === 'string')
if (fieldErrors.length > 0) {
errorMessage = fieldErrors.join('; ')
}
}
alert(errorMessage)
} else {
alert('批量删除失败')
}
}
}
}
// 状态修改
const handleStatusChange = (dealer: DealerInfo) => {
currentDealer.value = dealer
newCooperateStatus.value = dealer.cooperateStatus
showStatusDialog.value = true
}
// 提交表单
const handleSubmit = async () => {
try {
// 前端验证经销商编码格式
const dealerCodePattern = /^[A-Z0-9]{6,20}$/
if (!dealerCodePattern.test(formData.dealerCode)) {
alert('经销商编码格式不正确,应为6-20位大写字母和数字')
return
}
let response
if (isEdit.value) {
response = await dealerApi.updateDealer(formData as DealerUpdateReq)
} else {
response = await dealerApi.addDealer(formData as DealerAddReq)
}
if (response.code === 200) {
alert(isEdit.value ? '修改成功' : '新增成功')
showDialog.value = false
fetchDealers()
} else {
// 处理验证错误信息
let errorMessage = response.message || (isEdit.value ? '修改失败' : '新增失败')
// 如果有具体的字段验证错误,显示详细错误信息
if (response.data && typeof response.data === 'object') {
const fieldErrors = Object.values(response.data).filter(error => typeof error === 'string')
if (fieldErrors.length > 0) {
errorMessage = fieldErrors.join('; ')
}
}
alert(errorMessage)
}
} catch (error: any) {
console.error('提交失败:', error)
// 处理网络错误或服务器错误
if (error.response && error.response.data) {
const errorData = error.response.data
let errorMessage = errorData.message || (isEdit.value ? '修改失败' : '新增失败')
// 如果有具体的字段验证错误,显示详细错误信息
if (errorData.data && typeof errorData.data === 'object') {
const fieldErrors = Object.values(errorData.data).filter((err: any) => typeof err === 'string')
if (fieldErrors.length > 0) {
errorMessage = fieldErrors.join('; ')
}
}
alert(errorMessage)
} else {
alert(isEdit.value ? '修改失败' : '新增失败')
}
}
}
// 状态提交
const handleStatusSubmit = async () => {
try {
const response = await dealerApi.updateCooperateStatus(currentDealer.value.dealerId, newCooperateStatus.value)
if (response.code === 200) {
alert('状态修改成功')
showStatusDialog.value = false
fetchDealers()
} else {
alert(response.message || '状态修改失败')
}
} catch (error: any) {
console.error('状态修改失败:', error)
// 处理网络错误或服务器错误
if (error.response && error.response.data) {
const errorData = error.response.data
let errorMessage = errorData.message || '状态修改失败'
// 如果有具体的字段验证错误,显示详细错误信息
if (errorData.data && typeof errorData.data === 'object') {
const fieldErrors = Object.values(errorData.data).filter((err: any) => typeof err === 'string')
if (fieldErrors.length > 0) {
errorMessage = fieldErrors.join('; ')
}
}
alert(errorMessage)
} else {
alert('状态修改失败')
}
}
}
// 全选
const handleSelectAll = () => {
// 已在计算属性中处理
}
// 分页相关
const handlePageChange = (page: number) => {
const totalPagesValue = totalPages.value
if (page >= 1 && page <= totalPagesValue && totalPagesValue > 0) {
pagination.pageNum = page
searchParams.pageNum = page
fetchDealers()
}
}
const handlePageSizeChange = () => {
pagination.pageSize = pageSize.value
searchParams.pageSize = pageSize.value
pagination.pageNum = 1
searchParams.pageNum = 1
fetchDealers()
}
// 表格工具栏
const handleTableSearch = () => {
handleSearch()
}
const handleTableRefresh = () => {
fetchDealers()
}
const handleTableExport = () => {
alert('导出功能开发中...')
}
const handleTableViewToggle = () => {
alert('视图切换功能开发中...')
}
// 对话框相关
const handleDialogClose = () => {
showDialog.value = false
}
const handleStatusDialogClose = () => {
showStatusDialog.value = false
}
// 初始化
onMounted(() => {
fetchDealers()
})
</script>
<style scoped>
/* 基础样式 */
.dealer-container {
background: #f5f5f5;
min-height: 100vh;
padding: 0;
}
/* 搜索区域 */
.search-section {
background: white;
padding: 16px 20px;
border-bottom: 1px solid #e0e0e0;
}
.search-row {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.search-item {
display: flex;
align-items: center;
gap: 8px;
}
.search-item label {
font-size: 12px;
color: #666;
white-space: nowrap;
min-width: 60px;
}
.search-input, .search-select {
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 3px;
font-size: 11px;
width: 140px;
height: 24px;
}
.search-input:focus, .search-select:focus {
outline: none;
border-color: #409eff;
}
.search-actions {
display: flex;
gap: 6px;
}
.search-btn {
background: #1890ff;
color: white;
border: none;
padding: 4px 8px;
border-radius: 3px;
font-size: 11px;
cursor: pointer;
height: 24px;
display: flex;
align-items: center;
gap: 4px;
}
.reset-btn {
background: #ff7875;
color: white;
border: none;
padding: 4px 8px;
border-radius: 3px;
font-size: 11px;
cursor: pointer;
height: 24px;
display: flex;
align-items: center;
gap: 4px;
}
/* 操作区域 */
.action-section {
background: white;
padding: 12px 16px;
border-bottom: 1px solid #e0e0e0;
}
.action-buttons {
display: flex;
gap: 6px;
}
.action-btn {
padding: 4px 8px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
height: 24px;
display: flex;
align-items: center;
gap: 4px;
}
.action-btn.primary {
background: #52c41a;
color: white;
}
.action-btn.danger {
background: #ff4d4f;
color: white;
}
/* 表格区域 */
.table-section {
background: white;
margin: 16px;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.table-header {
padding: 8px 16px;
background: #fafafa;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: flex-end;
}
.table-controls {
display: flex;
gap: 4px;
}
.control-btn {
width: 24px;
height: 24px;
border: 1px solid #d9d9d9;
background: white;
border-radius: 3px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
}
.control-btn:hover {
background-color: #f5f5f5;
}
.table-container {
position: relative;
overflow-x: auto;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255,255,255,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-spinner {
font-size: 16px;
color: #409eff;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
.data-table th,
.data-table td {
padding: 8px 6px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.data-table th {
background-color: #fafafa;
font-weight: 500;
color: #333;
font-size: 11px;
}
.data-table tbody tr:hover {
background-color: #f5f5f5;
}
.select-all {
margin: 0;
}
.table-btn {
padding: 2px 6px;
margin: 0 1px;
border: none;
border-radius: 2px;
cursor: pointer;
font-size: 10px;
height: 20px;
display: inline-flex;
align-items: center;
gap: 2px;
}
.table-btn.edit {
background-color: #1890ff;
color: white;
}
.table-btn.delete {
background-color: #ff4d4f;
color: white;
}
.table-btn.status {
background-color: #fa8c16;
color: white;
}
.table-btn.audit {
background-color: #52c41a;
color: white;
}
/* 分页 */
.table-footer {
padding: 8px 16px;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
background: #fafafa;
}
.pagination-left {
display: flex;
align-items: center;
gap: 20px;
}
.pagination-info {
font-size: 12px;
color: #666;
}
.page-size-selector {
display: flex;
align-items: center;
gap: 8px;
}
.page-size-selector label {
font-size: 12px;
color: #666;
}
.page-size-select {
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 12px;
background: white;
cursor: pointer;
transition: border-color 0.3s;
}
.page-size-select:hover {
border-color: #1890ff;
}
.page-size-select:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.pagination-container {
display: flex;
align-items: center;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.pagination-btn {
padding: 2px 6px;
border: none;
background: white;
color: #6c757d;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border-right: 1px solid #e9ecef;
height: 20px;
}
.pagination-btn:hover:not(:disabled) {
background: #e9ecef;
color: #495057;
}
.pagination-btn:disabled {
background: #f8f9fa;
color: #adb5bd;
cursor: not-allowed;
}
.pagination-pages {
display: flex;
align-items: center;
}
.page-number {
padding: 2px 6px;
border: none;
background: white;
color: #6c757d;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border-right: 1px solid #e9ecef;
min-width: 28px;
text-align: center;
height: 20px;
}
.page-number:hover {
background: #e9ecef;
color: #495057;
}
.page-number.active {
background: #6c757d;
color: white;
font-weight: 600;
}
.page-number.active:hover {
background: #5a6268;
}
/* 对话框 */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.dialog-content {
background: white;
border-radius: 8px;
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
}
.dialog-header {
padding: 20px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.dialog-header h3 {
margin: 0;
font-size: 18px;
color: #333;
}
.dialog-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.dialog-body {
padding: 20px;
}
.form-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.form-row {
display: flex;
gap: 20px;
}
.form-item {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.form-item.full-width {
flex: 100%;
}
.form-item label {
font-size: 14px;
color: #333;
font-weight: 500;
}
.form-item label.required::after {
content: ' *';
color: #f56c6c;
}
.form-input, .form-select, .form-textarea {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
outline: none;
border-color: #409eff;
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.dialog-footer {
padding: 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.dialog-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.dialog-btn.cancel {
background-color: #909399;
color: white;
}
.dialog-btn.confirm {
background-color: #409eff;
color: white;
}
/* 状态样式 */
.status-normal {
color: #67c23a;
font-weight: 500;
}
.status-warning {
color: #e6a23c;
font-weight: 500;
}
.status-danger {
color: #f56c6c;
font-weight: 500;
}
.status-pending {
color: #909399;
font-weight: 500;
}
.status-success {
color: #67c23a;
font-weight: 500;
}
</style>
\ No newline at end of file
<template>
<div class="product-container">
<!-- 搜索区域 -->
<div class="search-section">
<div class="search-row">
<div class="search-item">
<label>产品编码:</label>
<input
v-model="searchParams.productCode"
class="search-input"
placeholder="请输入产品编码"
/>
</div>
<div class="search-item">
<label>产品名称:</label>
<input
v-model="searchParams.productName"
class="search-input"
placeholder="请输入产品名称"
/>
</div>
<div class="search-item">
<label>产品型号:</label>
<input
v-model="searchParams.productModel"
class="search-input"
placeholder="请输入产品型号"
/>
</div>
<div class="search-item">
<label>销售状态:</label>
<select v-model="searchParams.saleStatus" class="search-select">
<option value="">所有</option>
<option value="1">在售</option>
<option value="0">停售</option>
</select>
</div>
</div>
<div class="search-row">
<div class="search-item">
<label>返利标识:</label>
<select v-model="searchParams.rebateFlag" class="search-select">
<option value="">所有</option>
<option value="1">是</option>
<option value="0">否</option>
</select>
</div>
<div class="search-actions">
<button @click="handleSearch" class="search-btn">🔍 搜索</button>
<button @click="handleReset" class="reset-btn">🔄 重置</button>
</div>
</div>
</div>
<!-- 操作按钮区域 -->
<div class="action-section">
<div class="action-buttons">
<button @click="handleAdd" class="action-btn primary">✨ 新增</button>
<button @click="handleBatchDelete" class="action-btn danger">🗑️ 删除</button>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="table-controls">
<button @click="handleTableSearch" class="control-btn">🔍</button>
<button @click="handleTableRefresh" class="control-btn">🔄</button>
<button @click="handleTableExport" class="control-btn">📋</button>
<button @click="handleTableViewToggle" class="control-btn">⊞</button>
</div>
</div>
<div class="table-container">
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner">加载中...</div>
</div>
<table class="data-table">
<thead>
<tr>
<th>
<input
type="checkbox"
class="select-all"
v-model="selectAll"
@change="handleSelectAll"
/>
</th>
<th>产品ID</th>
<th class="sortable">产品编码 ↕️</th>
<th>产品名称</th>
<th>产品型号</th>
<th>销售状态</th>
<th>返利标识</th>
<th class="sortable">创建时间 ↕️</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="product in productList" :key="product.productId">
<td>
<input
type="checkbox"
class="row-checkbox"
:value="product.productId"
v-model="selectedProducts"
/>
</td>
<td>{{ product.productId }}</td>
<td>{{ product.productCode }}</td>
<td>{{ product.productName }}</td>
<td>{{ product.productModel }}</td>
<td>
<span :class="product.saleStatus === 1 ? 'status-active' : 'status-inactive'">
{{ product.saleStatus === 1 ? '在售' : '停售' }}
</span>
</td>
<td>
<span :class="product.rebateFlag === 1 ? 'status-active' : 'status-inactive'">
{{ product.rebateFlag === 1 ? '是' : '否' }}
</span>
</td>
<td>{{ formatTime(product.createTime) }}</td>
<td class="action-col">
<button @click="handleEdit(product)" class="edit-btn">✏️ 编辑</button>
<button @click="handleDelete(product)" class="delete-btn">🗑️ 删除</button>
<button @click="handleStatusChange(product)" class="status-btn">
{{ product.saleStatus === 1 ? '停售' : '在售' }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页信息 -->
<div class="table-footer">
<div class="pagination-left">
<div class="pagination-info">
共 {{ total }} 条记录,第 {{ currentPage }} / {{ totalPages }} 页
</div>
<div class="page-size-selector">
<label>每页显示:</label>
<select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
<option value="10">10条</option>
<option value="20">20条</option>
<option value="50">50条</option>
<option value="100">100条</option>
</select>
</div>
</div>
<div class="pagination-container">
<button
@click="handlePageChange(currentPage - 1)"
:disabled="currentPage <= 1"
class="pagination-btn prev-btn"
>
上一页
</button>
<div class="pagination-pages">
<button
v-for="page in getPageNumbers()"
:key="page"
@click="handlePageChange(page)"
:class="['page-number', { active: page === currentPage }]"
>
{{ page }}
</button>
</div>
<button
@click="handlePageChange(currentPage + 1)"
:disabled="currentPage >= totalPages"
class="pagination-btn next-btn"
>
下一页
</button>
</div>
</div>
</div>
<!-- 新增/编辑对话框 -->
<div v-if="showDialog" class="dialog-overlay" @click="closeDialog">
<div class="dialog-content" @click.stop>
<div class="dialog-header">
<h3>{{ isEdit ? '编辑产品' : '新增产品' }}</h3>
<button @click="closeDialog" class="close-btn">×</button>
</div>
<div class="dialog-body">
<form @submit.prevent="handleSubmit">
<div class="form-row">
<div class="form-item">
<label class="required">产品编码:</label>
<input
v-model="formData.productCode"
class="form-input"
placeholder="请输入产品编码"
required
/>
</div>
<div class="form-item">
<label class="required">产品名称:</label>
<input
v-model="formData.productName"
class="form-input"
placeholder="请输入产品名称"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">产品型号:</label>
<input
v-model="formData.productModel"
class="form-input"
placeholder="请输入产品型号"
required
/>
</div>
<div class="form-item">
<label class="required">产品类别:</label>
<input
v-model="formData.productType"
class="form-input"
placeholder="请输入产品类别"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">存储容量:</label>
<input
v-model="formData.storageCapacity"
class="form-input"
placeholder="请输入存储容量"
required
/>
</div>
<div class="form-item">
<label class="required">颜色:</label>
<input
v-model="formData.color"
class="form-input"
placeholder="请输入颜色"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">官方指导价:</label>
<input
v-model.number="formData.officialPrice"
class="form-input"
type="number"
placeholder="请输入官方指导价"
required
/>
</div>
<div class="form-item">
<label class="required">销售状态:</label>
<select v-model="formData.saleStatus" class="form-select" required>
<option value="1">在售</option>
<option value="0">停售</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">返利标识:</label>
<select v-model="formData.rebateFlag" class="form-select" required>
<option value="1">是</option>
<option value="0">否</option>
</select>
</div>
<div class="form-item">
<label>图片URL:</label>
<input
v-model="formData.imageUrl"
class="form-input"
placeholder="请输入图片URL"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">销售起始日期:</label>
<input
v-model="formData.saleStartDate"
class="form-input"
type="date"
required
/>
</div>
<div class="form-item">
<label class="required">销售终止日期:</label>
<input
v-model="formData.saleEndDate"
class="form-input"
type="date"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item full-width">
<label>备注:</label>
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="请输入备注"
rows="3"
></textarea>
</div>
</div>
<div class="form-actions">
<button type="button" @click="closeDialog" class="cancel-btn">取消</button>
<button type="submit" class="submit-btn">确定</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { productApi, type ProductInfo, type ProductQueryReq, type ProductAddReq, type ProductUpdateReq } from '../../api/product'
// 响应式数据
const loading = ref(false)
const productList = ref<ProductInfo[]>([])
const selectedProducts = ref<number[]>([])
const showDialog = ref(false)
const isEdit = ref(false)
// 搜索参数
const searchParams = reactive<ProductQueryReq>({
productCode: '',
productName: '',
productModel: '',
saleStatus: undefined,
rebateFlag: undefined,
pageNum: 1,
pageSize: 10
})
// 分页信息
const pagination = reactive({
total: 0,
pageNum: 1,
pageSize: 10
})
// 分页计算属性
const total = computed(() => pagination.total)
const currentPage = computed(() => pagination.pageNum)
const pageSize = computed({
get: () => pagination.pageSize,
set: (value: number) => {
pagination.pageSize = value
}
})
const totalPages = computed(() => Math.ceil(pagination.total / pagination.pageSize))
// 表单数据
const formData = reactive<ProductAddReq & { productId?: number }>({
productCode: '',
productName: '',
productModel: '',
productType: '',
storageCapacity: '',
color: '',
officialPrice: 0,
saleStatus: 1,
rebateFlag: 0,
saleStartDate: '',
saleEndDate: '',
imageUrl: '',
remark: ''
})
// 计算属性
const selectAll = computed({
get: () => selectedProducts.value.length === productList.value.length && productList.value.length > 0,
set: (value: boolean) => {
if (value) {
selectedProducts.value = productList.value.map(product => product.productId)
} else {
selectedProducts.value = []
}
}
})
// 获取页码数组
const getPageNumbers = () => {
const totalPages = Math.ceil(pagination.total / pagination.pageSize)
const currentPage = pagination.pageNum
const maxVisible = 5
const start = Math.max(1, currentPage - Math.floor(maxVisible / 2))
const end = Math.min(totalPages, start + maxVisible - 1)
const pages = []
for (let i = start; i <= end; i++) {
pages.push(i)
}
return pages
}
// 方法
const fetchProducts = async () => {
try {
loading.value = true
const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
const response = await productApi.getProductList(params) as any
if (response.code === 200 && response.data) {
productList.value = response.data.records || []
pagination.total = response.data.total || 0
pagination.pageNum = response.data.current || 1
pagination.pageSize = response.data.size || 10
} else {
productList.value = []
pagination.total = 0
showMessage(response.message || '获取产品列表失败', 'error')
}
} catch (error) {
console.error('获取产品列表失败:', error)
showMessage('获取产品列表失败, 请重试', 'error')
} finally {
loading.value = false
}
}
const handleSearch = () => {
pagination.pageNum = 1
fetchProducts()
}
const handleReset = () => {
Object.assign(searchParams, {
productCode: '',
productName: '',
productModel: '',
saleStatus: undefined,
rebateFlag: undefined
})
pagination.pageNum = 1
fetchProducts()
}
const handleAdd = () => {
isEdit.value = false
Object.assign(formData, {
productCode: '',
productName: '',
productModel: '',
productType: '',
storageCapacity: '',
color: '',
officialPrice: 0,
saleStatus: 1,
rebateFlag: 0,
saleStartDate: '',
saleEndDate: '',
imageUrl: '',
remark: ''
})
showDialog.value = true
}
const handleEdit = (product: ProductInfo) => {
isEdit.value = true
Object.assign(formData, {
productId: product.productId,
productCode: product.productCode,
productName: product.productName,
productModel: product.productModel,
productType: product.productType,
storageCapacity: product.storageCapacity,
color: product.color,
officialPrice: product.officialPrice,
saleStatus: product.saleStatus,
rebateFlag: product.rebateFlag,
saleStartDate: product.saleStartDate,
saleEndDate: product.saleEndDate,
imageUrl: product.imageUrl || '',
remark: product.remark || ''
})
showDialog.value = true
}
const handleDelete = async (product: ProductInfo) => {
if (confirm(`确定要删除产品"${product.productName}"吗?`)) {
try {
await productApi.deleteProduct(product.productId)
showMessage('删除成功', 'success')
fetchProducts()
} catch (error) {
console.error('删除失败:', error)
showMessage('删除失败, 请重试', 'error')
}
}
}
const handleBatchDelete = async () => {
if (selectedProducts.value.length === 0) {
showMessage('请选择要删除的产品', 'warning')
return
}
if (confirm(`确定要删除选中的${selectedProducts.value.length}个产品吗?`)) {
try {
await productApi.batchDeleteProduct(selectedProducts.value)
showMessage('批量删除成功', 'success')
selectedProducts.value = []
fetchProducts()
} catch (error) {
console.error('批量删除失败:', error)
showMessage('批量删除失败, 请重试', 'error')
}
}
}
const handleStatusChange = async (product: ProductInfo) => {
try {
const newStatus = product.saleStatus === 1 ? 0 : 1
await productApi.updateSaleStatus(product.productId, newStatus)
showMessage('状态修改成功', 'success')
fetchProducts()
} catch (error) {
console.error('状态修改失败:', error)
showMessage('状态修改失败, 请重试', 'error')
}
}
const handleSubmit = async () => {
try {
if (isEdit.value) {
await productApi.updateProduct(formData as ProductUpdateReq)
showMessage('修改成功', 'success')
} else {
await productApi.addProduct(formData as ProductAddReq)
showMessage('新增成功', 'success')
}
closeDialog()
fetchProducts()
} catch (error: any) {
console.error('操作失败:', error)
if (error.response?.data?.message) {
showMessage(error.response.data.message, 'error')
} else {
showMessage('操作失败, 请重试', 'error')
}
}
}
const closeDialog = () => {
showDialog.value = false
isEdit.value = false
}
const handleSelectAll = () => {
// 已在计算属性中处理
}
const handleTableSearch = () => {
handleSearch()
}
const handleTableRefresh = () => {
fetchProducts()
}
const handleTableExport = () => {
showMessage('导出功能开发中...', 'info')
}
const handleTableViewToggle = () => {
showMessage('视图切换功能开发中...', 'info')
}
const handlePageChange = (page: number) => {
pagination.pageNum = page
fetchProducts()
}
const handlePageSizeChange = () => {
pagination.pageNum = 1
fetchProducts()
}
const formatTime = (time: string) => {
return time.replace('T', ' ').substring(0, 19)
}
const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') => {
// 这里可以集成消息提示组件
alert(message)
}
// 生命周期
onMounted(() => {
fetchProducts()
})
</script>
<style scoped>
/* 基础样式 */
.product-container {
padding: 0;
background: #f5f5f5;
min-height: 100vh;
}
/* 搜索区域样式 */
.search-section {
background: white;
padding: 16px 20px;
border-bottom: 1px solid #e0e0e0;
}
.search-row {
display: flex;
gap: 20px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 12px;
}
.search-item {
display: flex;
align-items: center;
gap: 8px;
}
.search-item label {
font-size: 12px;
color: #333;
white-space: nowrap;
}
.search-input, .search-select {
padding: 6px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 12px;
width: 120px;
}
.price-range, .date-range {
display: flex;
align-items: center;
gap: 8px;
}
.price-input, .date-input {
padding: 6px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 12px;
width: 120px;
}
.price-separator, .date-separator {
color: #666;
font-weight: bold;
}
.search-input:focus, .search-select:focus, .price-input:focus, .date-input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.search-actions {
display: flex;
gap: 10px;
align-items: center;
}
.search-btn {
background: #1890ff;
color: white;
border: none;
padding: 4px 8px;
border-radius: 3px;
font-size: 11px;
cursor: pointer;
height: 24px;
display: flex;
align-items: center;
gap: 4px;
}
.reset-btn {
background: #ff7875;
color: white;
border: none;
padding: 4px 8px;
border-radius: 3px;
font-size: 11px;
cursor: pointer;
height: 24px;
display: flex;
align-items: center;
gap: 4px;
}
/* 操作区域样式 */
.action-section {
background: white;
padding: 12px 16px;
border-bottom: 1px solid #e0e0e0;
}
.action-buttons {
display: flex;
gap: 6px;
}
.action-btn {
padding: 4px 8px;
border: none;
border-radius: 3px;
font-size: 11px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
height: 24px;
}
.action-btn.primary {
background: #1890ff;
color: white;
}
.action-btn.success {
background: #52c41a;
color: white;
}
.action-btn.danger {
background: #ff4d4f;
color: white;
}
.action-btn.info {
background: #13c2c2;
color: white;
}
.action-btn.warning {
background: #faad14;
color: white;
}
/* 表格区域样式 */
.table-section {
background: white;
margin: 16px;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.table-header {
padding: 8px 16px;
background: #fafafa;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: flex-end;
align-items: center;
}
.table-controls {
display: flex;
gap: 6px;
}
.control-btn {
padding: 4px 8px;
border: 1px solid #ddd;
border-radius: 3px;
background: white;
cursor: pointer;
font-size: 11px;
color: #666;
min-width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.control-btn:hover {
background: #f5f5f5;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 11px;
}
.data-table th,
.data-table td {
padding: 8px 6px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.data-table th {
background-color: #fafafa;
font-weight: 500;
color: #333;
font-size: 11px;
}
.data-table tbody tr:hover {
background-color: #f5f5f5;
}
.sortable {
cursor: pointer;
user-select: none;
}
.sortable:hover {
background: #e6f7ff;
}
.checkbox-col {
width: 40px;
text-align: center;
}
.select-all, .row-checkbox {
width: 14px;
height: 14px;
}
.action-col {
white-space: nowrap;
}
.edit-btn, .delete-btn, .status-btn {
padding: 2px 6px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
margin-right: 4px;
height: 20px;
display: inline-flex;
align-items: center;
gap: 2px;
}
.edit-btn {
background: #1890ff;
color: white;
}
.delete-btn {
background: #ff4d4f;
color: white;
}
.status-btn {
background: #faad14;
color: white;
}
/* 状态样式 */
.status-active {
color: #67c23a;
font-weight: 500;
}
.status-inactive {
color: #f56c6c;
font-weight: 500;
}
/* 分页控制样式 */
.table-footer {
padding: 8px 16px;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination-left {
display: flex;
align-items: center;
gap: 20px;
}
.pagination-info {
font-size: 12px;
color: #666;
}
.page-size-selector {
display: flex;
align-items: center;
gap: 8px;
}
.page-size-selector label {
font-size: 12px;
color: #666;
}
.page-size-select {
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 12px;
background: white;
cursor: pointer;
transition: border-color 0.3s;
}
.page-size-select:hover {
border-color: #1890ff;
}
.page-size-select:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* 新的分页容器样式 - 模拟截图样式 */
.pagination-container {
display: flex;
align-items: center;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.pagination-btn {
padding: 2px 6px;
border: none;
background: white;
color: #6c757d;
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border-right: 1px solid #e9ecef;
height: 20px;
}
.pagination-btn:hover:not(:disabled) {
background: #e9ecef;
color: #495057;
}
.pagination-btn:disabled {
background: #f8f9fa;
color: #adb5bd;
cursor: not-allowed;
}
.pagination-pages {
display: flex;
align-items: center;
}
.page-number {
padding: 2px 6px;
border: none;
background: white;
color: #6c757d;
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border-right: 1px solid #e9ecef;
min-width: 28px;
text-align: center;
height: 20px;
}
.page-number:hover {
background: #e9ecef;
color: #495057;
}
.page-number.active {
background: #6c757d;
color: white;
font-weight: 600;
}
.page-number.active:hover {
background: #5a6268;
}
/* 对话框样式 */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.dialog-content {
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
width: 600px;
max-height: 80vh;
overflow-y: auto;
}
.dialog-header {
padding: 16px 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.dialog-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #999;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.dialog-body {
padding: 20px;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 16px;
}
.form-item {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.form-item.full-width {
flex: 100%;
}
.form-item label {
font-size: 14px;
font-weight: 500;
color: #333;
}
.form-item label.required::after {
content: ' *';
color: #ff4d4f;
}
.form-input, .form-select, .form-textarea {
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s;
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.form-textarea {
resize: vertical;
min-height: 60px;
}
.form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
.cancel-btn, .submit-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.cancel-btn {
background: #f5f5f5;
color: #666;
}
.cancel-btn:hover {
background: #e6e6e6;
}
.submit-btn {
background: #1890ff;
color: white;
}
.submit-btn:hover {
background: #40a9ff;
}
/* 加载状态样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-spinner {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
font-size: 14px;
color: #666;
}
/* 响应式设计 */
@media (max-width: 768px) {
.search-row {
flex-direction: column;
align-items: stretch;
}
.search-item {
width: 100%;
}
.search-input, .search-select, .date-input {
width: 100%;
}
.action-buttons {
flex-wrap: wrap;
}
.dialog-content {
width: 90%;
}
.form-row {
flex-direction: column;
}
}
</style>
<template>
<div class="users-container">
<!-- 搜索筛选区域 -->
<div class="search-section">
<div class="search-row">
<div class="search-item">
<label>登录名称:</label>
<input
v-model="searchForm.username"
type="text"
class="search-input"
placeholder="请输入登录名称"
/>
</div>
<div class="search-item">
<label>用户状态:</label>
<select v-model="searchForm.status" class="search-select">
<option value="">所有</option>
<option value="1">正常</option>
<option value="0">停用</option>
</select>
</div>
<div class="search-item">
<label>创建时间:</label>
<div class="date-range">
<div class="date-picker-container">
<input
v-model="searchForm.startTime"
type="text"
class="date-input"
placeholder="开始时间"
readonly
@click="toggleStartDatePicker"
@mouseenter="showStartDateOptions = true"
@mouseleave="hideStartDateOptions"
/>
<div v-if="showStartDateOptions" class="date-options" @mouseenter="showStartDateOptions = true" @mouseleave="hideStartDateOptions">
<div class="date-option" @click="selectStartDate('today')">今天</div>
<div class="date-option" @click="selectStartDate('yesterday')">昨天</div>
<div class="date-option" @click="selectStartDate('thisWeek')">本周</div>
<div class="date-option" @click="selectStartDate('lastWeek')">上周</div>
<div class="date-option" @click="selectStartDate('thisMonth')">本月</div>
<div class="date-option" @click="selectStartDate('lastMonth')">上月</div>
<div class="date-option" @click="selectStartDate('custom')">自定义</div>
</div>
<!-- 自定义日期选择器 -->
<div v-if="showStartDatePicker" class="custom-date-picker">
<input
type="date"
v-model="searchForm.startTime"
@change="showStartDatePicker = false"
class="date-input"
/>
</div>
</div>
<span class="date-separator">-</span>
<div class="date-picker-container">
<input
v-model="searchForm.endTime"
type="text"
class="date-input"
placeholder="结束时间"
readonly
@click="toggleEndDatePicker"
@mouseenter="showEndDateOptions = true"
@mouseleave="hideEndDateOptions"
/>
<div v-if="showEndDateOptions" class="date-options" @mouseenter="showEndDateOptions = true" @mouseleave="hideEndDateOptions">
<div class="date-option" @click="selectEndDate('today')">今天</div>
<div class="date-option" @click="selectEndDate('yesterday')">昨天</div>
<div class="date-option" @click="selectEndDate('thisWeek')">本周</div>
<div class="date-option" @click="selectEndDate('lastWeek')">上周</div>
<div class="date-option" @click="selectEndDate('thisMonth')">本月</div>
<div class="date-option" @click="selectEndDate('lastMonth')">上月</div>
<div class="date-option" @click="selectEndDate('custom')">自定义</div>
</div>
<!-- 自定义日期选择器 -->
<div v-if="showEndDatePicker" class="custom-date-picker">
<input
type="date"
v-model="searchForm.endTime"
@change="showEndDatePicker = false"
class="date-input"
/>
</div>
</div>
</div>
</div>
<div class="search-actions">
<button @click="handleSearch" class="search-btn">🔍 搜索</button>
<button @click="handleReset" class="reset-btn">🔄 重置</button>
</div>
</div>
</div>
<!-- 操作按钮区域 -->
<div class="action-section">
<div class="action-buttons">
<button @click="handleAdd" class="action-btn primary">✨ 新增</button>
<button @click="handleBatchDelete" class="action-btn danger">🗑️ 删除</button>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="table-controls">
<button @click="handleTableSearch" class="control-btn" title="搜索">🔍</button>
<button @click="handleTableRefresh" class="control-btn" title="刷新">🔄</button>
<button @click="handleTableExport" class="control-btn" title="导出">📋</button>
<button @click="handleTableViewToggle" class="control-btn" title="视图切换">⊞</button>
</div>
</div>
<div class="table-container">
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner">加载中...</div>
</div>
<table class="data-table">
<thead>
<tr>
<th>
<input
type="checkbox"
class="select-all"
v-model="selectAll"
@change="handleSelectAll"
/>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.userId">
<td>
<button @click="handleEdit(user)" class="table-btn edit">✏️ 编辑</button>
<button @click="handleDelete(user)" class="table-btn delete">🗑️ 删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页信息 -->
<div class="table-footer">
<div class="pagination-left">
<div class="pagination-info">
共 {{ total }} 条记录,第 {{ currentPage }} / {{ totalPages }} 页
</div>
<div class="page-size-selector">
<label>每页显示:</label>
<select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
<option value="10">10条</option>
<option value="20">20条</option>
<option value="50">50条</option>
<option value="100">100条</option>
</select>
</div>
</div>
<div class="pagination-container">
<button
@click="handlePageChange(currentPage - 1)"
:disabled="currentPage <= 1"
class="pagination-btn prev-btn"
>
上一页
</button>
<div class="pagination-pages">
<button
v-for="page in getPageNumbers()"
:key="page"
@click="handlePageChange(page)"
:class="['page-number', { active: page === currentPage }]"
>
{{ page }}
</button>
</div>
<button
@click="handlePageChange(currentPage + 1)"
:disabled="currentPage >= totalPages"
class="pagination-btn next-btn"
>
下一页
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
\ No newline at end of file