修改目录
This commit is contained in:
@@ -1,306 +0,0 @@
|
||||
# 员工实体关系添加员工名称字段设计
|
||||
|
||||
## 1. 需求概述
|
||||
|
||||
在员工实体关系列表和详情中添加员工名称字段,通过身份证号(personId)关联员工信息表(ccdi_base_staff)获取姓名。
|
||||
|
||||
**涉及模块:** 员工实体关系 (ccdi_staff_enterprise_relation)
|
||||
|
||||
**展示位置:**
|
||||
- 列表页面 (table 列)
|
||||
- 详情接口返回
|
||||
|
||||
**数据来源:** 通过 personId 关联 ccdi_base_staff 表的 id_card 字段获取 name 字段
|
||||
|
||||
**空值处理:** 当 personId 在员工信息表中不存在时,显示为空
|
||||
|
||||
## 2. 技术方案
|
||||
|
||||
采用 MyBatis 关联查询(JOIN)方式,在查询时动态获取员工姓名,不修改表结构。
|
||||
|
||||
### 2.1 优势
|
||||
|
||||
- ✅ 无需修改数据库表结构
|
||||
- ✅ 数据始终与员工信息表同步
|
||||
- ✅ 实施简单,风险低
|
||||
- ✅ 性能影响可控
|
||||
|
||||
## 3. 数据库层设计
|
||||
|
||||
### 3.1 SQL查询改造
|
||||
|
||||
在 `CcdiStaffEnterpriseRelationMapper.xml` 中修改列表查询和详情查询:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
ser.id,
|
||||
ser.person_id,
|
||||
bs.name AS person_name, -- 通过JOIN获取员工姓名
|
||||
ser.relation_person_post,
|
||||
ser.social_credit_code,
|
||||
ser.enterprise_name,
|
||||
ser.status,
|
||||
ser.remark,
|
||||
ser.data_source,
|
||||
ser.is_employee,
|
||||
ser.is_emp_family,
|
||||
ser.is_customer,
|
||||
ser.is_cust_family,
|
||||
ser.create_time,
|
||||
ser.update_time,
|
||||
ser.created_by,
|
||||
ser.updated_by
|
||||
FROM ccdi_staff_enterprise_relation ser
|
||||
LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card
|
||||
WHERE ser.status = 1
|
||||
```
|
||||
|
||||
### 3.2 关键点
|
||||
|
||||
- 使用 `LEFT JOIN` 确保即使员工信息不存在,关系记录也会返回
|
||||
- 当 `personId` 在 `ccdi_base_staff` 中不存在时,`person_name` 为 NULL
|
||||
- 数据库表结构不需要修改
|
||||
|
||||
### 3.3 索引优化
|
||||
|
||||
确保 `ccdi_base_staff.id_card` 字段有索引:
|
||||
|
||||
```sql
|
||||
-- 检查索引
|
||||
SHOW INDEX FROM ccdi_base_staff WHERE Key_name = 'idx_id_card';
|
||||
|
||||
-- 如果没有索引,创建
|
||||
CREATE INDEX idx_id_card ON ccdi_base_staff(id_card);
|
||||
```
|
||||
|
||||
## 4. 后端代码层设计
|
||||
|
||||
### 4.1 VO层修改
|
||||
|
||||
**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffEnterpriseRelationVO.java`
|
||||
|
||||
```java
|
||||
/** 身份证号 */
|
||||
@Schema(description = "身份证号")
|
||||
private String personId;
|
||||
|
||||
/** 员工姓名 */
|
||||
@Schema(description = "员工姓名")
|
||||
private String personName;
|
||||
|
||||
/** 关联人在企业的职务 */
|
||||
@Schema(description = "关联人在企业的职务")
|
||||
private String relationPersonPost;
|
||||
```
|
||||
|
||||
### 4.2 Mapper接口
|
||||
|
||||
**文件:** `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffEnterpriseRelationMapper.xml`
|
||||
|
||||
修改查询方法,添加 LEFT JOIN:
|
||||
|
||||
```xml
|
||||
<select id="selectRelationList" resultType="CcdiStaffEnterpriseRelationVO">
|
||||
SELECT
|
||||
ser.id,
|
||||
ser.person_id,
|
||||
bs.name AS person_name,
|
||||
ser.relation_person_post,
|
||||
ser.social_credit_code,
|
||||
ser.enterprise_name,
|
||||
ser.status,
|
||||
ser.remark,
|
||||
ser.data_source,
|
||||
ser.is_employee,
|
||||
ser.is_emp_family,
|
||||
ser.is_customer,
|
||||
ser.is_cust_family,
|
||||
ser.create_time,
|
||||
ser.update_time,
|
||||
ser.created_by,
|
||||
ser.updated_by
|
||||
FROM ccdi_staff_enterprise_relation ser
|
||||
LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card
|
||||
<where>
|
||||
<if test="personId != null and personId != ''">
|
||||
AND ser.person_id LIKE CONCAT('%', #{personId}, '%')
|
||||
</if>
|
||||
<if test="enterpriseName != null and enterpriseName != ''">
|
||||
AND ser.enterprise_name LIKE CONCAT('%', #{enterpriseName}, '%')
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND ser.status = #{status}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY ser.create_time DESC
|
||||
</select>
|
||||
```
|
||||
|
||||
同样修改 `selectRelationById` 方法。
|
||||
|
||||
### 4.3 Service层
|
||||
|
||||
`ICcdiStaffEnterpriseRelationService.java` 和实现类无需大改,MyBatis Plus 会自动填充 JOIN 的字段。
|
||||
|
||||
## 5. 前端代码层设计
|
||||
|
||||
### 5.1 列表页面修改
|
||||
|
||||
**文件:** `ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue`
|
||||
|
||||
在表格列定义中添加员工姓名列:
|
||||
|
||||
```vue
|
||||
<el-table-column label="身份证号" align="center" prop="personId" width="180" />
|
||||
<el-table-column label="员工姓名" align="center" prop="personName" width="100" />
|
||||
<el-table-column label="职务" align="center" prop="relationPersonPost" width="120" />
|
||||
```
|
||||
|
||||
**位置建议:** 放在"身份证号"列之后,方便用户对照查看
|
||||
|
||||
### 5.2 API接口
|
||||
|
||||
**文件:** `ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js`
|
||||
|
||||
无需修改,接口会自动返回新增的 `personName` 字段。
|
||||
|
||||
### 5.3 详情页面
|
||||
|
||||
如果存在详情对话框,同样添加员工姓名显示:
|
||||
|
||||
```vue
|
||||
<el-form-item label="身份证号">{{ form.personId }}</el-form-item>
|
||||
<el-form-item label="员工姓名">{{ form.personName }}</el-form-item>
|
||||
<el-form-item label="职务">{{ form.relationPersonPost }}</el-form-item>
|
||||
```
|
||||
|
||||
## 6. 错误处理和边界情况
|
||||
|
||||
### 6.1 数据空值处理
|
||||
|
||||
- **场景**: personId 在 `ccdi_base_staff` 表中不存在
|
||||
- **处理**: `personName` 为 NULL,前端显示为空字符串
|
||||
- **前端展示**: Element UI table 会自动将 null 显示为空
|
||||
|
||||
### 6.2 数据一致性
|
||||
|
||||
- **场景**: 员工信息表的姓名后续被修改
|
||||
- **影响**: 下次查询时自动获取最新姓名,无需同步
|
||||
- **优势**: JOIN 方案天然保证数据一致性
|
||||
|
||||
### 6.3 性能考虑
|
||||
|
||||
- **索引**: 确保 `ccdi_base_staff.id_card` 字段有索引
|
||||
- **查询优化**: LEFT JOIN 对性能影响较小
|
||||
- **分页**: 已有分页机制,单页数据量有限
|
||||
|
||||
### 6.4 特殊字符处理
|
||||
|
||||
- 员工姓名可能包含特殊字符,MyBatis 和 JSON 序列化会自动处理
|
||||
- 无需额外转义逻辑
|
||||
|
||||
## 7. 测试策略
|
||||
|
||||
### 7.1 单元测试
|
||||
|
||||
创建测试用例覆盖以下场景:
|
||||
|
||||
```java
|
||||
// 测试场景1: 员工信息存在
|
||||
assertEquals("张三", result.getPersonName());
|
||||
|
||||
// 测试场景2: 员工信息不存在
|
||||
assertNull(result.getPersonName());
|
||||
|
||||
// 测试场景3: 姓名包含特殊字符
|
||||
assertEquals("张三·李四", result.getPersonName());
|
||||
|
||||
// 测试场景4: 批量数据性能测试
|
||||
List<CcdiStaffEnterpriseRelationVO> list = mapper.selectRelationList(query);
|
||||
assertTrue(list.size() > 0);
|
||||
```
|
||||
|
||||
### 7.2 接口测试
|
||||
|
||||
使用测试脚本验证:
|
||||
- 列表接口返回 `personName` 字段
|
||||
- 详情接口返回 `personName` 字段
|
||||
- 分页查询正常工作
|
||||
- 空值处理正确
|
||||
|
||||
### 7.3 前端测试
|
||||
|
||||
手动验证:
|
||||
- 列表页面正确显示员工姓名
|
||||
- 空值显示为空
|
||||
- 列表排序、筛选功能正常
|
||||
|
||||
### 7.4 数据准备测试
|
||||
|
||||
准备测试数据:
|
||||
- 已有员工的关系记录
|
||||
- 无对应员工的关系记录
|
||||
- 批量数据的性能测试
|
||||
|
||||
## 8. 实施步骤
|
||||
|
||||
### 步骤1: 修改 VO 类
|
||||
- 文件: `CcdiStaffEnterpriseRelationVO.java`
|
||||
- 添加 `personName` 字段及注解
|
||||
|
||||
### 步骤2: 修改 Mapper XML
|
||||
- 文件: `CcdiStaffEnterpriseRelationMapper.xml`
|
||||
- 修改列表查询和详情查询,添加 LEFT JOIN
|
||||
|
||||
### 步骤3: 修改前端列表页
|
||||
- 文件: `ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue`
|
||||
- 在表格中添加员工姓名列
|
||||
|
||||
### 步骤4: 检查数据库索引
|
||||
- 检查 `ccdi_base_staff.id_card` 是否有索引
|
||||
- 如果没有,执行创建索引 SQL
|
||||
|
||||
### 步骤5: 测试验证
|
||||
- 运行后端,测试接口返回
|
||||
- 运行前端,验证页面显示
|
||||
- 生成测试报告
|
||||
|
||||
### 步骤6: 更新文档
|
||||
- 更新 API 文档
|
||||
- 更新数据库设计文档
|
||||
|
||||
## 9. 影响范围
|
||||
|
||||
**修改文件:**
|
||||
- 后端: 2个文件 (VO + Mapper XML)
|
||||
- 前端: 1个文件 (列表页面)
|
||||
- 数据库: 0个表结构修改
|
||||
|
||||
**涉及模块:**
|
||||
- 员工实体关系 (ccdi_staff_enterprise_relation)
|
||||
|
||||
**风险评估:**
|
||||
- 低风险: 仅查询层面的改动,不影响数据写入
|
||||
- 性能影响可控: 通过索引优化
|
||||
- 兼容性好: 新增字段不影响现有功能
|
||||
|
||||
## 10. 后续优化建议
|
||||
|
||||
### 10.1 缓存优化
|
||||
|
||||
如果员工信息表数据量大且变动不频繁,可以考虑:
|
||||
- 使用 Redis 缓存员工信息
|
||||
- 减少数据库查询次数
|
||||
|
||||
### 10.2 搜索增强
|
||||
|
||||
可以支持按员工姓名搜索关系记录:
|
||||
- 在查询条件中添加姓名搜索
|
||||
- 需要修改查询 DTO 和 Mapper XML
|
||||
|
||||
### 10.3 其他模块
|
||||
|
||||
如果其他模块也有类似需求,可以复用此方案:
|
||||
- 员工亲属关系 (ccdi_staff_fmy_relation) - 已有 personName 字段
|
||||
- 员工招聘 (ccdi_staff_recruitment)
|
||||
- 员工调动 (ccdi_staff_transfer)
|
||||
@@ -1,949 +0,0 @@
|
||||
# 员工实体关系添加员工名称字段实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**目标:** 在员工实体关系列表和详情中添加员工名称字段,通过 LEFT JOIN 查询员工信息表获取姓名
|
||||
|
||||
**架构:** 使用 MyBatis 的 LEFT JOIN 在查询层关联 `ccdi_base_staff` 表,通过 `person_id = id_card` 关联获取员工姓名,不修改表结构
|
||||
|
||||
**技术栈:** Spring Boot 3, MyBatis Plus 3.5.10, Vue 2.6, Element UI 2.15
|
||||
|
||||
---
|
||||
|
||||
## 前置条件检查
|
||||
|
||||
### Task 1: 检查数据库索引
|
||||
|
||||
**文件:**
|
||||
- 数据库: `ccdi_base_staff` 表
|
||||
|
||||
**Step 1: 连接数据库并检查索引**
|
||||
|
||||
使用 MCP 连接数据库:
|
||||
```
|
||||
连接配置从 application.yml 读取
|
||||
```
|
||||
|
||||
**Step 2: 执行索引检查 SQL**
|
||||
|
||||
```sql
|
||||
SHOW INDEX FROM ccdi_base_staff WHERE Key_name = 'idx_id_card';
|
||||
```
|
||||
|
||||
预期: 如果索引存在,返回索引信息;如果不存在,返回空结果
|
||||
|
||||
**Step 3: 如果索引不存在,创建索引**
|
||||
|
||||
```sql
|
||||
CREATE INDEX idx_id_card ON ccdi_base_staff(id_card);
|
||||
```
|
||||
|
||||
预期: Query OK, 0 rows affected
|
||||
|
||||
**Step 4: 验证索引创建成功**
|
||||
|
||||
```sql
|
||||
SHOW INDEX FROM ccdi_base_staff WHERE Key_name = 'idx_id_card';
|
||||
```
|
||||
|
||||
预期: 返回新创建的索引信息
|
||||
|
||||
**Step 5: 记录结果**
|
||||
|
||||
如果索引已创建,在实施笔记中记录:
|
||||
```markdown
|
||||
- [x] 数据库索引已创建
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 后端实现
|
||||
|
||||
### Task 2: 修改 VO 类添加员工姓名字段
|
||||
|
||||
**文件:**
|
||||
- Modify: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffEnterpriseRelationVO.java`
|
||||
|
||||
**Step 1: 在 personId 字段后添加 personName 字段**
|
||||
|
||||
在 `CcdiStaffEnterpriseRelationVO.java` 的第 30 行之后添加:
|
||||
|
||||
```java
|
||||
/** 身份证号 */
|
||||
@Schema(description = "身份证号")
|
||||
private String personId;
|
||||
|
||||
/** 员工姓名 */
|
||||
@Schema(description = "员工姓名")
|
||||
private String personName;
|
||||
|
||||
/** 关联人在企业的职务 */
|
||||
@Schema(description = "关联人在企业的职务")
|
||||
private String relationPersonPost;
|
||||
```
|
||||
|
||||
**Step 2: 保存文件**
|
||||
|
||||
**Step 3: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffEnterpriseRelationVO.java
|
||||
git commit -m "feat(staff-enterprise-relation): 添加员工姓名字段到VO"
|
||||
```
|
||||
|
||||
预期: Commit 成功
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 修改 Mapper XML - 列表查询
|
||||
|
||||
**文件:**
|
||||
- Modify: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffEnterpriseRelationMapper.xml`
|
||||
|
||||
**Step 1: 找到列表查询的 SQL**
|
||||
|
||||
查找 `selectRelationList` 或类似的列表查询方法
|
||||
|
||||
**Step 2: 修改 SELECT 子句,添加员工姓名**
|
||||
|
||||
将原来的:
|
||||
```xml
|
||||
SELECT ser.id, ser.person_id, ser.relation_person_post, ...
|
||||
```
|
||||
|
||||
修改为:
|
||||
```xml
|
||||
SELECT
|
||||
ser.id,
|
||||
ser.person_id,
|
||||
bs.name AS person_name,
|
||||
ser.relation_person_post,
|
||||
ser.social_credit_code,
|
||||
ser.enterprise_name,
|
||||
ser.status,
|
||||
ser.remark,
|
||||
ser.data_source,
|
||||
ser.is_employee,
|
||||
ser.is_emp_family,
|
||||
ser.is_customer,
|
||||
ser.is_cust_family,
|
||||
ser.create_time,
|
||||
ser.update_time,
|
||||
ser.created_by,
|
||||
ser.updated_by
|
||||
```
|
||||
|
||||
**Step 3: 修改 FROM 子句,添加 LEFT JOIN**
|
||||
|
||||
将原来的:
|
||||
```xml
|
||||
FROM ccdi_staff_enterprise_relation ser
|
||||
```
|
||||
|
||||
修改为:
|
||||
```xml
|
||||
FROM ccdi_staff_enterprise_relation ser
|
||||
LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card
|
||||
```
|
||||
|
||||
**Step 4: 保存文件**
|
||||
|
||||
**Step 5: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffEnterpriseRelationMapper.xml
|
||||
git commit -m "feat(staff-enterprise-relation): 列表查询添加员工姓名JOIN"
|
||||
```
|
||||
|
||||
预期: Commit 成功
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 修改 Mapper XML - 详情查询
|
||||
|
||||
**文件:**
|
||||
- Modify: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffEnterpriseRelationMapper.xml`
|
||||
|
||||
**Step 1: 找到详情查询的 SQL**
|
||||
|
||||
查找 `selectRelationById` 或类似的详情查询方法
|
||||
|
||||
**Step 2: 应用与列表查询相同的修改**
|
||||
|
||||
1. 在 SELECT 子句中添加 `bs.name AS person_name`
|
||||
2. 在 FROM 子句中添加 `LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card`
|
||||
|
||||
完整的查询应该类似:
|
||||
```xml
|
||||
SELECT
|
||||
ser.id,
|
||||
ser.person_id,
|
||||
bs.name AS person_name,
|
||||
ser.relation_person_post,
|
||||
ser.social_credit_code,
|
||||
ser.enterprise_name,
|
||||
ser.status,
|
||||
ser.remark,
|
||||
ser.data_source,
|
||||
ser.is_employee,
|
||||
ser.is_emp_family,
|
||||
ser.is_customer,
|
||||
ser.is_cust_family,
|
||||
ser.create_time,
|
||||
ser.update_time,
|
||||
ser.created_by,
|
||||
ser.updated_by
|
||||
FROM ccdi_staff_enterprise_relation ser
|
||||
LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card
|
||||
WHERE ser.id = #{id}
|
||||
```
|
||||
|
||||
**Step 3: 保存文件**
|
||||
|
||||
**Step 4: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffEnterpriseRelationMapper.xml
|
||||
git commit -m "feat(staff-enterprise-relation): 详情查询添加员工姓名JOIN"
|
||||
```
|
||||
|
||||
预期: Commit 成功
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 编写接口测试脚本
|
||||
|
||||
**文件:**
|
||||
- Create: `doc/test_staff_enterprise_relation_person_name.bat`
|
||||
|
||||
**Step 1: 创建测试脚本**
|
||||
|
||||
创建测试脚本验证接口返回 `personName` 字段:
|
||||
|
||||
```bash
|
||||
@echo off
|
||||
chcp 65001 > nul
|
||||
echo ========================================
|
||||
echo 员工实体关系员工姓名字段测试
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM 获取 token
|
||||
echo [1/5] 获取登录 token...
|
||||
curl -s -X POST "http://localhost:8080/login/test" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"admin\",\"password\":\"admin123\"}" \
|
||||
> token_response.json
|
||||
|
||||
for /f "tokens=2 delims=:\"" %%a in ('findstr "\"token\"" token_response.json') do set TOKEN=%%a
|
||||
echo Token: %TOKEN%
|
||||
echo.
|
||||
|
||||
REM 测试列表接口
|
||||
echo [2/5] 测试列表接口...
|
||||
curl -s -X GET "http://localhost:8080/ccdi/staffEnterpriseRelation/list?pageNum=1&pageSize=10" \
|
||||
-H "Authorization: Bearer %TOKEN%" \
|
||||
> list_response.json
|
||||
|
||||
echo 检查 personName 字段是否在响应中...
|
||||
findstr /C:"personName" list_response.json > nul
|
||||
if %errorlevel% equ 0 (
|
||||
echo [SUCCESS] 列表接口包含 personName 字段
|
||||
) else (
|
||||
echo [FAIL] 列表接口缺少 personName 字段
|
||||
type list_response.json
|
||||
)
|
||||
echo.
|
||||
|
||||
REM 测试详情接口
|
||||
echo [3/5] 获取第一条记录的 ID...
|
||||
for /f "tokens=2 delims=:\" %%a in ('findstr /C:"\"id\":" list_response.json ^| findstr /N "." ^| findstr "^1:"') do (
|
||||
set FIRST_LINE=%%a
|
||||
)
|
||||
REM 这里需要手动解析,暂时使用固定 ID 进行测试
|
||||
echo 注意: 请手动查看 list_response.json 中的有效 ID
|
||||
echo.
|
||||
|
||||
REM 使用示例 ID 测试详情接口
|
||||
echo [4/5] 测试详情接口 (ID: 1)...
|
||||
curl -s -X GET "http://localhost:8080/ccdi/staffEnterpriseRelation/1" \
|
||||
-H "Authorization: Bearer %TOKEN%" \
|
||||
> detail_response.json
|
||||
|
||||
echo 检查 personName 字段是否在响应中...
|
||||
findstr /C:"personName" detail_response.json > nul
|
||||
if %errorlevel% equ 0 (
|
||||
echo [SUCCESS] 详情接口包含 personName 字段
|
||||
) else (
|
||||
echo [FAIL] 详情接口缺少 personName 字段
|
||||
type detail_response.json
|
||||
)
|
||||
echo.
|
||||
|
||||
REM 查看响应内容
|
||||
echo [5/5] 查看列表响应内容...
|
||||
type list_response.json
|
||||
echo.
|
||||
echo ========================================
|
||||
echo 测试完成
|
||||
echo ========================================
|
||||
|
||||
pause
|
||||
```
|
||||
|
||||
**Step 2: 保存文件**
|
||||
|
||||
**Step 3: 提交测试脚本**
|
||||
|
||||
```bash
|
||||
git add doc/test_staff_enterprise_relation_person_name.bat
|
||||
git commit -m "test(staff-enterprise-relation): 添加员工姓名字段测试脚本"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 后端编译验证
|
||||
|
||||
**文件:**
|
||||
- Build: Maven project
|
||||
|
||||
**Step 1: 清理并编译项目**
|
||||
|
||||
```bash
|
||||
cd ruoyi-admin
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
预期: BUILD SUCCESS
|
||||
|
||||
**Step 2: 检查编译错误**
|
||||
|
||||
如果有编译错误,检查:
|
||||
1. VO 类语法是否正确
|
||||
2. Mapper XML 语法是否正确
|
||||
3. 是否有依赖问题
|
||||
|
||||
**Step 3: 如果编译成功,记录日志**
|
||||
|
||||
在实施笔记中记录:
|
||||
```markdown
|
||||
- [x] 后端编译成功
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端实现
|
||||
|
||||
### Task 7: 修改列表页面添加员工姓名列
|
||||
|
||||
**文件:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue`
|
||||
|
||||
**Step 1: 找到表格列定义部分**
|
||||
|
||||
查找 `<el-table>` 组件中的列定义
|
||||
|
||||
**Step 2: 在身份证号列后添加员工姓名列**
|
||||
|
||||
在 `personId` 列之后添加:
|
||||
|
||||
```vue
|
||||
<el-table-column label="身份证号" align="center" prop="personId" width="180" />
|
||||
<el-table-column label="员工姓名" align="center" prop="personName" width="100" />
|
||||
<el-table-column label="职务" align="center" prop="relationPersonPost" width="120" />
|
||||
```
|
||||
|
||||
**Step 3: 如果有搜索表单,也可以添加员工姓名搜索**
|
||||
|
||||
在搜索表单中添加:
|
||||
|
||||
```vue
|
||||
<el-form-item label="员工姓名" prop="personName">
|
||||
<el-input
|
||||
v-model="queryParams.personName"
|
||||
placeholder="请输入员工姓名"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
```
|
||||
|
||||
注意: 如果添加搜索功能,需要同步修改后端的 Mapper XML 查询条件
|
||||
|
||||
**Step 4: 保存文件**
|
||||
|
||||
**Step 5: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue
|
||||
git commit -m "feat(staff-enterprise-relation): 列表页面添加员工姓名列"
|
||||
```
|
||||
|
||||
预期: Commit 成功
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 前端编译验证
|
||||
|
||||
**文件:**
|
||||
- Build: Vue project
|
||||
|
||||
**Step 1: 进入前端目录**
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
```
|
||||
|
||||
**Step 2: 安装依赖(如果需要)**
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
**Step 3: 开发模式启动**
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
预期: 编译成功,服务启动在端口 80
|
||||
|
||||
**Step 4: 检查编译错误**
|
||||
|
||||
如果有编译错误,检查:
|
||||
1. Vue 组件语法是否正确
|
||||
2. 是否有依赖问题
|
||||
|
||||
**Step 5: 停止开发服务器**
|
||||
|
||||
按 `Ctrl+C` 停止
|
||||
|
||||
**Step 6: 记录日志**
|
||||
|
||||
在实施笔记中记录:
|
||||
```markdown
|
||||
- [x] 前端编译成功
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试阶段
|
||||
|
||||
### Task 9: 后端集成测试
|
||||
|
||||
**文件:**
|
||||
- Test: 使用 MCP 或测试脚本
|
||||
|
||||
**Step 1: 启动后端服务**
|
||||
|
||||
```bash
|
||||
cd ruoyi-admin
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
预期: 服务启动成功,监听端口 8080
|
||||
|
||||
**Step 2: 运行测试脚本**
|
||||
|
||||
```bash
|
||||
doc\test_staff_enterprise_relation_person_name.bat
|
||||
```
|
||||
|
||||
**Step 3: 验证测试结果**
|
||||
|
||||
检查:
|
||||
1. 列表接口是否返回 `personName` 字段
|
||||
2. 详情接口是否返回 `personName` 字段
|
||||
3. 员工信息存在时,姓名是否正确显示
|
||||
4. 员工信息不存在时,字段是否为 null
|
||||
|
||||
**Step 4: 记录测试结果**
|
||||
|
||||
在实施笔记中记录:
|
||||
```markdown
|
||||
- [x] 后端接口测试通过
|
||||
- [ ] personName 字段正确返回
|
||||
- [ ] 员工信息存在时姓名正确
|
||||
- [ ] 员工信息不存在时为 null
|
||||
```
|
||||
|
||||
**Step 5: 停止后端服务**
|
||||
|
||||
按 `Ctrl+C` 停止
|
||||
|
||||
---
|
||||
|
||||
### Task 10: 前端集成测试
|
||||
|
||||
**文件:**
|
||||
- Test: 浏览器手动测试
|
||||
|
||||
**Step 1: 启动后端服务**
|
||||
|
||||
```bash
|
||||
cd ruoyi-admin
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
**Step 2: 启动前端服务**
|
||||
|
||||
新开终端:
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Step 3: 浏览器访问测试**
|
||||
|
||||
访问: `http://localhost`
|
||||
|
||||
**Step 4: 登录系统**
|
||||
|
||||
用户名: `admin`
|
||||
密码: `admin123`
|
||||
|
||||
**Step 5: 导航到员工实体关系页面**
|
||||
|
||||
系统管理 > 员工实体关系
|
||||
|
||||
**Step 6: 验证列表显示**
|
||||
|
||||
检查:
|
||||
1. 列表中是否显示"员工姓名"列
|
||||
2. 员工姓名是否正确显示
|
||||
3. 无员工信息时,是否显示为空
|
||||
|
||||
**Step 7: 测试详情查看**
|
||||
|
||||
点击某条记录的"查看"按钮,验证详情对话框中是否显示员工姓名
|
||||
|
||||
**Step 8: 测试分页和搜索**
|
||||
|
||||
1. 切换分页,验证员工姓名持续显示
|
||||
2. 使用身份证号搜索,验证结果正确
|
||||
3. 如果实现了姓名搜索,测试姓名搜索功能
|
||||
|
||||
**Step 9: 记录测试结果**
|
||||
|
||||
在实施笔记中记录:
|
||||
```markdown
|
||||
- [x] 前端页面测试通过
|
||||
- [ ] 员工姓名列正确显示
|
||||
- [ ] 空值正确显示
|
||||
- [ ] 分页正常
|
||||
- [ ] 搜索功能正常
|
||||
```
|
||||
|
||||
**Step 10: 停止服务**
|
||||
|
||||
按 `Ctrl+C` 停止前后端服务
|
||||
|
||||
---
|
||||
|
||||
### Task 11: 性能测试
|
||||
|
||||
**文件:**
|
||||
- Test: 性能验证
|
||||
|
||||
**Step 1: 准备测试数据**
|
||||
|
||||
确保数据库中有足够多的测试数据(至少 1000 条)
|
||||
|
||||
**Step 2: 启动后端服务**
|
||||
|
||||
```bash
|
||||
cd ruoyi-admin
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
**Step 3: 测试分页查询性能**
|
||||
|
||||
使用测试脚本或浏览器开发者工具,测量:
|
||||
- 第一页查询响应时间
|
||||
- 中间页查询响应时间
|
||||
- 最后一页查询响应时间
|
||||
|
||||
**Step 4: 对比性能数据**
|
||||
|
||||
与修改前的性能对比,验证:
|
||||
- 响应时间增加是否在可接受范围内(< 100ms)
|
||||
- 如果性能下降明显,考虑优化索引
|
||||
|
||||
**Step 5: 记录性能测试结果**
|
||||
|
||||
在实施笔记中记录:
|
||||
```markdown
|
||||
- [x] 性能测试完成
|
||||
- [ ] 平均响应时间: ___ ms
|
||||
- [ ] 性能是否可接受: 是/否
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 12: 边界情况测试
|
||||
|
||||
**文件:**
|
||||
- Test: 边界场景验证
|
||||
|
||||
**测试场景 1: personId 为空**
|
||||
|
||||
**Step 1:** 在数据库中插入一条 `person_id` 为 NULL 的记录
|
||||
|
||||
**Step 2:** 在列表中查看该记录
|
||||
|
||||
预期: 记录正常显示,员工姓名为空
|
||||
|
||||
**测试场景 2: personId 在员工信息表中不存在**
|
||||
|
||||
**Step 1:** 在数据库中插入一条 `person_id` 为不存在身份证号的记录
|
||||
|
||||
**Step 2:** 在列表中查看该记录
|
||||
|
||||
预期: 记录正常显示,员工姓名为空
|
||||
|
||||
**测试场景 3: 员工姓名包含特殊字符**
|
||||
|
||||
**Step 1:** 确保员工信息表中有姓名包含特殊字符的记录(如 "张三·李四")
|
||||
|
||||
**Step 2:** 在列表中查看该记录
|
||||
|
||||
预期: 员工姓名正确显示,特殊字符无乱码
|
||||
|
||||
**测试场景 4: 大量数据查询**
|
||||
|
||||
**Step 1:** 测试查询 100 条记录/页
|
||||
|
||||
**Step 2:** 测试查询 500 条记录/页
|
||||
|
||||
预期: 所有记录的员工姓名都正确显示,无性能问题
|
||||
|
||||
**Step 3: 记录边界测试结果**
|
||||
|
||||
在实施笔记中记录:
|
||||
```markdown
|
||||
- [x] 边界测试完成
|
||||
- [ ] personId 为空: 通过/失败
|
||||
- [ ] personId 不存在: 通过/失败
|
||||
- [ ] 特殊字符: 通过/失败
|
||||
- [ ] 大量数据: 通过/失败
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文档更新
|
||||
|
||||
### Task 13: 更新 API 文档
|
||||
|
||||
**文件:**
|
||||
- Modify: `doc/api-docs/api/` 下的相关文档
|
||||
|
||||
**Step 1: 查找现有的 API 文档**
|
||||
|
||||
找到员工实体关系相关的 API 文档
|
||||
|
||||
**Step 2: 在响应参数中添加 personName 字段**
|
||||
|
||||
在列表接口和详情接口的响应参数中添加:
|
||||
|
||||
```markdown
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| personId | String | 身份证号 |
|
||||
| personName | String | 员工姓名(通过关联查询获取) |
|
||||
| relationPersonPost | String | 关联人在企业的职务 |
|
||||
```
|
||||
|
||||
**Step 3: 添加说明**
|
||||
|
||||
在文档中添加说明:
|
||||
```markdown
|
||||
**注意:**
|
||||
- personName 字段通过 LEFT JOIN ccdi_base_staff 表获取
|
||||
- 如果 personId 在员工信息表中不存在,personName 为 null
|
||||
```
|
||||
|
||||
**Step 4: 保存并提交**
|
||||
|
||||
```bash
|
||||
git add doc/api-docs/
|
||||
git commit -m "docs(staff-enterprise-relation): 更新API文档,添加员工姓名字段说明"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 14: 更新数据库设计文档
|
||||
|
||||
**文件:**
|
||||
- Modify: `doc/database-docs/` 下的相关文档
|
||||
|
||||
**Step 1: 更新视图说明**
|
||||
|
||||
在 `ccdi_staff_enterprise_relation` 表的说明中添加:
|
||||
|
||||
```markdown
|
||||
## 关联查询
|
||||
|
||||
该表在查询时会关联 `ccdi_base_staff` 表获取员工姓名:
|
||||
|
||||
- 关联字段: ccdi_staff_enterprise_relation.person_id = ccdi_base_staff.id_card
|
||||
- 获取字段: ccdi_base_staff.name AS person_name
|
||||
- 关联方式: LEFT JOIN(确保即使员工信息不存在也能返回关系记录)
|
||||
```
|
||||
|
||||
**Step 2: 保存并提交**
|
||||
|
||||
```bash
|
||||
git add doc/database-docs/
|
||||
git commit -m "docs(staff-enterprise-relation): 更新数据库设计文档,添加关联查询说明"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 15: 生成测试报告
|
||||
|
||||
**文件:**
|
||||
- Create: `doc/test-reports/2026-02-11-staff-enterprise-relation-person-name-test-report.md`
|
||||
|
||||
**Step 1: 创建测试报告**
|
||||
|
||||
```markdown
|
||||
# 员工实体关系员工姓名字段测试报告
|
||||
|
||||
**测试日期:** 2026-02-11
|
||||
**测试人员:** [测试人员姓名]
|
||||
**测试环境:** 开发环境
|
||||
|
||||
## 1. 功能测试
|
||||
|
||||
### 1.1 列表接口测试
|
||||
|
||||
| 测试项 | 测试场景 | 预期结果 | 实际结果 | 状态 |
|
||||
|--------|----------|----------|----------|------|
|
||||
| personName 字段返回 | 调用列表接口 | 响应包含 personName 字段 | | PASS/FAIL |
|
||||
| 员工信息存在 | personId 在员工表中存在 | 返回正确员工姓名 | | PASS/FAIL |
|
||||
| 员工信息不存在 | personId 在员工表中不存在 | personName 为 null | | PASS/FAIL |
|
||||
|
||||
### 1.2 详情接口测试
|
||||
|
||||
| 测试项 | 测试场景 | 预期结果 | 实际结果 | 状态 |
|
||||
|--------|----------|----------|----------|------|
|
||||
| personName 字段返回 | 调用详情接口 | 响应包含 personName 字段 | | PASS/FAIL |
|
||||
| 员工信息存在 | personId 在员工表中存在 | 返回正确员工姓名 | | PASS/FAIL |
|
||||
| 员工信息不存在 | personId 在员工表中不存在 | personName 为 null | | PASS/FAIL |
|
||||
|
||||
### 1.3 前端页面测试
|
||||
|
||||
| 测试项 | 测试场景 | 预期结果 | 实际结果 | 状态 |
|
||||
|--------|----------|----------|----------|------|
|
||||
| 员工姓名列显示 | 列表页面 | 显示"员工姓名"列 | | PASS/FAIL |
|
||||
| 空值显示 | 员工信息不存在 | 显示为空 | | PASS/FAIL |
|
||||
| 分页功能 | 切换页面 | 员工姓名持续显示 | | PASS/FAIL |
|
||||
|
||||
## 2. 性能测试
|
||||
|
||||
| 测试项 | 测试场景 | 预期结果 | 实际结果 | 状态 |
|
||||
|--------|----------|----------|----------|------|
|
||||
| 响应时间 | 1000 条数据查询 | < 100ms | ___ ms | PASS/FAIL |
|
||||
| 大数据量 | 100 条/页 | 正常显示 | | PASS/FAIL |
|
||||
|
||||
## 3. 边界测试
|
||||
|
||||
| 测试项 | 测试场景 | 预期结果 | 实际结果 | 状态 |
|
||||
|--------|----------|----------|----------|------|
|
||||
| personId 为空 | person_id = NULL | 正常显示,姓名为空 | | PASS/FAIL |
|
||||
| 特殊字符 | 姓名含特殊字符 | 正确显示无乱码 | | PASS/FAIL |
|
||||
|
||||
## 4. 测试结论
|
||||
|
||||
### 4.1 通过的功能
|
||||
- [ ] 列表接口返回 personName 字段
|
||||
- [ ] 详情接口返回 personName 字段
|
||||
- [ ] 前端正确显示员工姓名
|
||||
- [ ] 空值正确处理
|
||||
- [ ] 性能满足要求
|
||||
|
||||
### 4.2 发现的问题
|
||||
[记录测试中发现的问题]
|
||||
|
||||
### 4.3 建议
|
||||
[记录优化建议]
|
||||
|
||||
### 4.4 总体评价
|
||||
- 通过率: ___%
|
||||
- 风险等级: 高/中/低
|
||||
- 上线建议: 建议/不建议
|
||||
```
|
||||
|
||||
**Step 2: 填写测试结果**
|
||||
|
||||
根据实际测试结果填写报告
|
||||
|
||||
**Step 3: 保存并提交**
|
||||
|
||||
```bash
|
||||
git add doc/test-reports/2026-02-11-staff-enterprise-relation-person-name-test-report.md
|
||||
git commit -m "test(staff-enterprise-relation): 添加员工姓名字段测试报告"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 代码审查
|
||||
|
||||
### Task 16: 自我代码审查
|
||||
|
||||
**文件:**
|
||||
- Review: 所有修改的文件
|
||||
|
||||
**Step 1: 检查 VO 类**
|
||||
|
||||
检查项:
|
||||
- [ ] 字段命名符合规范(驼峰命名)
|
||||
- [ ] 有正确的 Swagger 注解
|
||||
- [ ] 字段类型正确(String)
|
||||
- [ ] 实现了 Serializable 接口
|
||||
|
||||
**Step 2: 检查 Mapper XML**
|
||||
|
||||
检查项:
|
||||
- [ ] SQL 语法正确
|
||||
- [ ] LEFT JOIN 条件正确
|
||||
- [ ] 字段别名正确(person_name)
|
||||
- [ ] WHERE 条件不受影响
|
||||
- [ ] 没有语法错误
|
||||
|
||||
**Step 3: 检查前端代码**
|
||||
|
||||
检查项:
|
||||
- [ ] 列定义位置合理
|
||||
- [ ] prop 名称与后端一致
|
||||
- [ ] 列宽设置合理
|
||||
- [ ] 没有 Vue 语法错误
|
||||
|
||||
**Step 4: 检查测试覆盖**
|
||||
|
||||
检查项:
|
||||
- [ ] 接口测试覆盖列表和详情
|
||||
- [ ] 前端测试覆盖显示和交互
|
||||
- [ ] 边界测试覆盖异常场景
|
||||
- [ ] 性能测试覆盖大数据量
|
||||
|
||||
**Step 5: 使用 code-reviewer 技能**
|
||||
|
||||
如果所有检查通过,调用 code-reviewer 技能进行正式审查:
|
||||
|
||||
```
|
||||
/superpowers:requesting-code-review
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最终提交和合并
|
||||
|
||||
### Task 17: 整合提交
|
||||
|
||||
**文件:**
|
||||
- Git: Feature branch
|
||||
|
||||
**Step 1: 查看所有提交**
|
||||
|
||||
```bash
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
确认所有任务都已提交
|
||||
|
||||
**Step 2: 确保分支是最新的**
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git rebase origin/dev_1
|
||||
```
|
||||
|
||||
**Step 3: 推送到远程**
|
||||
|
||||
```bash
|
||||
git push origin HEAD:feat/staff-enterprise-relation-person-name
|
||||
```
|
||||
|
||||
**Step 4: 创建 Pull Request**
|
||||
|
||||
使用 gh 命令创建 PR:
|
||||
|
||||
```bash
|
||||
gh pr create \
|
||||
--title "feat: 员工实体关系添加员工姓名字段" \
|
||||
--body "## 功能说明
|
||||
在员工实体关系列表和详情中添加员工姓名字段,通过 LEFT JOIN 查询员工信息表获取。
|
||||
|
||||
## 实施方案
|
||||
- 修改 CcdiStaffEnterpriseRelationVO,添加 personName 字段
|
||||
- 修改 Mapper XML,添加 LEFT JOIN ccdi_base_staff
|
||||
- 修改前端列表页,添加员工姓名列
|
||||
- 不修改数据库表结构,通过关联查询获取
|
||||
|
||||
## 测试情况
|
||||
- [x] 单元测试通过
|
||||
- [x] 接口测试通过
|
||||
- [x] 前端测试通过
|
||||
- [x] 边界测试通过
|
||||
- [x] 性能测试通过
|
||||
|
||||
## 相关文档
|
||||
- 设计文档: doc/plans/2026-02-11-staff-enterprise-relation-person-name-design.md
|
||||
- 实施计划: doc/plans/2026-02-11-staff-enterprise-relation-person-name-implementation.md
|
||||
- 测试报告: doc/test-reports/2026-02-11-staff-enterprise-relation-person-name-test-report.md
|
||||
" \
|
||||
--base dev_1
|
||||
```
|
||||
|
||||
**Step 6: 请求代码审查**
|
||||
|
||||
通知团队成员进行代码审查
|
||||
|
||||
---
|
||||
|
||||
## 任务清单总结
|
||||
|
||||
在开始实施前,确认以下任务清单:
|
||||
|
||||
- [x] Task 1: 检查数据库索引
|
||||
- [ ] Task 2: 修改 VO 类
|
||||
- [ ] Task 3: 修改 Mapper XML - 列表查询
|
||||
- [ ] Task 4: 修改 Mapper XML - 详情查询
|
||||
- [ ] Task 5: 编写接口测试脚本
|
||||
- [ ] Task 6: 后端编译验证
|
||||
- [ ] Task 7: 修改列表页面
|
||||
- [ ] Task 8: 前端编译验证
|
||||
- [ ] Task 9: 后端集成测试
|
||||
- [ ] Task 10: 前端集成测试
|
||||
- [ ] Task 11: 性能测试
|
||||
- [ ] Task 12: 边界情况测试
|
||||
- [ ] Task 13: 更新 API 文档
|
||||
- [ ] Task 14: 更新数据库设计文档
|
||||
- [ ] Task 15: 生成测试报告
|
||||
- [ ] Task 16: 自我代码审查
|
||||
- [ ] Task 17: 整合提交和 PR
|
||||
|
||||
**预计总时间:** 2-3 小时
|
||||
|
||||
**技术风险:** 低
|
||||
|
||||
**数据风险:** 无(仅查询改动,不影响数据写入)
|
||||
|
||||
---
|
||||
|
||||
## 实施笔记
|
||||
|
||||
在实施过程中记录遇到的问题和解决方案:
|
||||
|
||||
```markdown
|
||||
## 问题记录
|
||||
|
||||
### 问题 1: [描述问题]
|
||||
**时间:** [时间]
|
||||
**现象:** [问题现象]
|
||||
**原因:** [问题原因]
|
||||
**解决:** [解决方案]
|
||||
|
||||
### 问题 2: ...
|
||||
```
|
||||
@@ -1,428 +0,0 @@
|
||||
# 员工关系导入身份证号校验设计文档
|
||||
|
||||
**日期**: 2026-02-11
|
||||
**状态**: 设计完成
|
||||
**优先级**: 中
|
||||
|
||||
---
|
||||
|
||||
## 1. 需求概述
|
||||
|
||||
### 1.1 背景
|
||||
当前员工实体关系和员工亲属关系的导入功能在导入数据时,没有验证员工身份证号是否在员工信息表中存在。这可能导致导入的数据引用了不存在的员工,造成数据完整性问题。
|
||||
|
||||
### 1.2 目标
|
||||
在员工实体关系和员工亲属关系的导入过程中,添加员工身份证号存在性校验:
|
||||
- 验证员工身份证号是否在 `ccdi_base_staff` 表中存在
|
||||
- 不存在的身份证号记录错误信息并跳过
|
||||
- 继续处理其他有效数据
|
||||
|
||||
### 1.3 约束条件
|
||||
- 仅验证员工身份证号(`person_id`)存在性,不验证关系人身份证号
|
||||
- 不验证员工状态(在职/离职)
|
||||
- 错误信息需要包含Excel行号
|
||||
- 与现有的导入流程保持一致(失败记录保存到Redis)
|
||||
|
||||
### 1.4 优化范围
|
||||
同时优化员工调动导入的身份证号验证逻辑,从2次遍历优化为1次遍历。
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 整体架构
|
||||
|
||||
在两个导入服务中添加**员工身份证号批量预验证阶段**,流程如下:
|
||||
|
||||
```
|
||||
导入流程:
|
||||
1. 批量查询已存在的身份证号(新增)⭐
|
||||
2. 数据处理循环(原有,修改)
|
||||
└─ 循环开始时检查身份证号是否存在(新增)
|
||||
3. 批量插入新数据(原有)
|
||||
4. 保存失败记录到Redis(原有)
|
||||
5. 更新导入状态(原有)
|
||||
```
|
||||
|
||||
### 2.2 新增组件
|
||||
|
||||
#### 2.2.1 依赖注入
|
||||
在三个导入服务中添加:
|
||||
```java
|
||||
@Resource
|
||||
private CcdiBaseStaffMapper baseStaffMapper;
|
||||
```
|
||||
|
||||
#### 2.2.2 核心逻辑
|
||||
- **位置**: 在数据处理循环之前
|
||||
- **功能**: 批量查询所有Excel中出现的身份证号,构建存在性集合
|
||||
- **输入**: Excel数据列表、任务ID
|
||||
- **输出**: Set<String> 存在的身份证号集合
|
||||
|
||||
### 2.3 影响的服务
|
||||
|
||||
| 服务 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 员工实体关系导入 | `CcdiStaffEnterpriseRelationImportServiceImpl.java` | 添加身份证号验证 |
|
||||
| 员工亲属关系导入 | `CcdiStaffFmyRelationImportServiceImpl.java` | 添加身份证号验证 |
|
||||
| 员工调动导入 | `CcdiStaffTransferImportServiceImpl.java` | 优化为1次遍历 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据流设计
|
||||
|
||||
### 3.1 详细流程
|
||||
|
||||
```
|
||||
阶段1: 提取身份证号
|
||||
├─ 从 excelList 提取所有 personId
|
||||
├─ 过滤 null 值和空字符串
|
||||
├─ HashSet 去重
|
||||
└─ 得到 Set<String> allPersonIds
|
||||
|
||||
阶段2: 批量查询
|
||||
├─ 如果 allPersonIds 为空,返回空集合
|
||||
├─ 构建查询: SELECT id_card FROM ccdi_base_staff WHERE id_card IN (...)
|
||||
├─ 执行: baseStaffMapper.selectList(wrapper)
|
||||
├─ 提取结果中的 idCard
|
||||
└─ 得到 Set<String> existingPersonIds
|
||||
|
||||
阶段3: 数据处理循环(原有循环增强)
|
||||
├─ 遍历 excelList(行号 1-based,i为索引)
|
||||
│ ├─ 【新增】首先检查: personId 是否在 existingPersonIds 中?
|
||||
│ │ ├─ 如果不在:
|
||||
│ │ │ ├─ 创建 ImportFailureVO 对象
|
||||
│ │ │ ├─ 错误信息: "第{i+1}行: 身份证号[{personId}]不存在于员工信息表中"
|
||||
│ │ │ ├─ 添加到 failures 列表
|
||||
│ │ │ ├─ 记录验证失败日志
|
||||
│ │ │ └─ continue(跳过后续处理)
|
||||
│ │ └─ 如果在:继续执行原有逻辑
|
||||
│ ├─ 转换并验证数据(原有)
|
||||
│ ├─ 检查重复(原有)
|
||||
│ └─ 添加到 newRecords(原有)
|
||||
```
|
||||
|
||||
### 3.2 错误信息格式
|
||||
|
||||
```java
|
||||
String errorMessage = String.format("第%d行: 身份证号[%s]不存在于员工信息表中",
|
||||
rowNumber, personId);
|
||||
```
|
||||
|
||||
### 3.3 日志记录
|
||||
|
||||
使用 `ImportLogUtils` 记录:
|
||||
- 批量查询开始: `logBatchQueryStart(log, taskId, "员工身份证号", count)`
|
||||
- 批量查询完成: `logBatchQueryComplete(log, taskId, "员工身份证号", count)`
|
||||
- 验证失败: `logValidationError(log, taskId, rowNumber, errorMessage, keyData)`
|
||||
|
||||
---
|
||||
|
||||
## 4. 边界情况处理
|
||||
|
||||
### 4.1 personId 为 null 或空字符串
|
||||
- 在提取阶段过滤掉: `.filter(StringUtils::isNotEmpty)`
|
||||
- 这些记录会在原有的 `validateRelationData` 方法中报错
|
||||
|
||||
### 4.2 Excel 为空或所有 personId 为空
|
||||
- `allPersonIds` 为空集合
|
||||
- 直接返回空集合,跳过批量查询
|
||||
- 所有记录会在后续验证中报错
|
||||
|
||||
### 4.3 所有身份证号都不存在
|
||||
- `existingPersonIds` 为空集合
|
||||
- 所有记录都会在第一次检查时抛出异常
|
||||
- `newRecords` 保持为空
|
||||
- 最终状态: `PARTIAL_SUCCESS`
|
||||
|
||||
### 4.4 Excel 中有重复身份证号
|
||||
- 使用 HashSet 去重,只查询一次
|
||||
- 每行独立检查,如果不存在则各自生成失败记录
|
||||
|
||||
### 4.5 数据库中没有员工记录
|
||||
- `baseStaffMapper.selectList` 返回空列表
|
||||
- 所有 Excel 行都会在检查时失败
|
||||
|
||||
### 4.6 身份证号格式错误
|
||||
- 先检查身份证号是否存在
|
||||
- 如果不存在,直接报错"身份证号不存在"
|
||||
- 如果存在但格式错误,在后续的 `validateRelationData` 中会报错
|
||||
|
||||
---
|
||||
|
||||
## 5. 性能分析
|
||||
|
||||
### 5.1 时间复杂度
|
||||
- 提取身份证号: O(n),n为Excel行数
|
||||
- 数据库查询: O(m),m为不重复身份证号数量
|
||||
- 数据处理循环: O(n)
|
||||
- **总计: O(n)**,线性复杂度
|
||||
|
||||
### 5.2 空间复杂度
|
||||
- `allPersonIds`: 约 20字节 × m
|
||||
- `existingPersonIds`: 约 20字节 × m
|
||||
- **总计: 约 40KB / 1000个不重复身份证号**
|
||||
|
||||
### 5.3 数据库查询
|
||||
- 查询次数: **仅1次**
|
||||
- 查询类型: `SELECT id_card FROM ccdi_base_staff WHERE id_card IN (...)`
|
||||
- 索引: `id_card` 字段需要添加索引
|
||||
|
||||
### 5.4 性能对比
|
||||
|
||||
| 方案 | 数据库查询次数 | 1000行耗时 | 10000行耗时 |
|
||||
|------|---------------|-----------|------------|
|
||||
| 批量预验证(本设计) | 1次 | ~50ms | ~200ms |
|
||||
| 逐条验证 | N次 | ~5000ms | ~50000ms |
|
||||
|
||||
**结论**: 批量预验证方案性能提升约**100倍**。
|
||||
|
||||
---
|
||||
|
||||
## 6. 代码实现
|
||||
|
||||
### 6.1 员工实体关系导入服务
|
||||
|
||||
**文件**: `CcdiStaffEnterpriseRelationImportServiceImpl.java`
|
||||
|
||||
#### 6.1.1 添加依赖注入(第44行后)
|
||||
```java
|
||||
@Resource
|
||||
private CcdiBaseStaffMapper baseStaffMapper;
|
||||
```
|
||||
|
||||
#### 6.1.2 在 importRelationAsync 方法中(第55行后),添加批量查询:
|
||||
```java
|
||||
// 【新增】批量验证员工身份证号是否存在
|
||||
Set<String> excelPersonIds = excelList.stream()
|
||||
.map(CcdiStaffEnterpriseRelationExcel::getPersonId)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> existingPersonIds = new HashSet<>();
|
||||
if (!excelPersonIds.isEmpty()) {
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "员工身份证号", excelPersonIds.size());
|
||||
|
||||
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(CcdiBaseStaff::getIdCard)
|
||||
.in(CcdiBaseStaff::getIdCard, excelPersonIds);
|
||||
|
||||
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper);
|
||||
existingPersonIds = existingStaff.stream()
|
||||
.map(CcdiBaseStaff::getIdCard)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工身份证号", existingPersonIds.size());
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.1.3 在数据处理循环开始处(第71行后),添加检查:
|
||||
```java
|
||||
try {
|
||||
// 【新增】身份证号存在性检查
|
||||
if (!existingPersonIds.contains(excel.getPersonId())) {
|
||||
throw new RuntimeException(String.format(
|
||||
"第%d行: 身份证号[%s]不存在于员工信息表中",
|
||||
i + 1, excel.getPersonId()));
|
||||
}
|
||||
|
||||
// 原有逻辑继续...
|
||||
CcdiStaffEnterpriseRelationAddDTO addDTO = new CcdiStaffEnterpriseRelationAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 员工亲属关系导入服务
|
||||
|
||||
**文件**: `CcdiStaffFmyRelationImportServiceImpl.java`
|
||||
|
||||
相同的修改步骤:
|
||||
1. 添加 `CcdiBaseStaffMapper` 依赖注入
|
||||
2. 在第57行后添加批量查询身份证号逻辑
|
||||
3. 在第96行后(数据处理循环开始处)添加身份证号检查
|
||||
|
||||
### 6.3 员工调动导入服务优化
|
||||
|
||||
**文件**: `CcdiStaffTransferImportServiceImpl.java`
|
||||
|
||||
**优化前**: 2次遍历(预验证 + 主循环)
|
||||
**优化后**: 1次遍历(主循环中检查)
|
||||
|
||||
**修改步骤**:
|
||||
1. 移除 `batchValidateStaffIds` 方法
|
||||
2. 移除 `isRowAlreadyFailed` 方法
|
||||
3. 在主循环开始处添加员工ID存在性检查
|
||||
4. 使用 `existingStaffIds` 替代原有的预验证逻辑
|
||||
|
||||
### 6.4 需要导入的类
|
||||
```java
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.ccdi.mapper.CcdiBaseStaffMapper;
|
||||
import com.ruoyi.ccdi.domain.CcdiBaseStaff;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collectors;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试场景
|
||||
|
||||
### 7.1 功能测试用例
|
||||
|
||||
| 场景 | 输入 | 预期结果 |
|
||||
|------|------|----------|
|
||||
| 正常导入 | 5条有效身份证号 | 全部成功,failures为空 |
|
||||
| 部分无效 | 3条有效 + 2条无效身份证号 | 3条成功,2条失败 |
|
||||
| 全部无效 | 5条全部无效身份证号 | 0条成功,5条失败 |
|
||||
| 身份证号为null | 包含null或空字符串 | 在后续验证中报错 |
|
||||
| 大批量数据 | 1000条记录,全部有效 | 仅1次查询,全部成功 |
|
||||
| 重复身份证号 | 10条记录,3个不同身份证号 | 去重查询,正确验证 |
|
||||
| 混合场景 | 有效、无效、null、重复 | 各自正确处理 |
|
||||
|
||||
### 7.2 员工实体关系导入专项测试
|
||||
|
||||
```
|
||||
测试数据1: 有效身份证号
|
||||
personId: "110101199001011234" (存在于ccdi_base_staff)
|
||||
预期: 导入成功
|
||||
|
||||
测试数据2: 无效身份证号
|
||||
personId: "999999999999999999" (不存在于ccdi_base_staff)
|
||||
预期: 导入失败,错误信息: "第N行: 身份证号[xxx]不存在于员工信息表中"
|
||||
```
|
||||
|
||||
### 7.3 员工亲属关系导入专项测试
|
||||
|
||||
```
|
||||
测试数据1: 有效员工身份证号
|
||||
personId: "110101199001011234" (存在)
|
||||
relationCertNo: "110101199001011235" (可以不存在)
|
||||
预期: 导入成功
|
||||
|
||||
测试数据2: 无效员工身份证号
|
||||
personId: "999999999999999999" (不存在)
|
||||
预期: 导入失败
|
||||
```
|
||||
|
||||
### 7.4 性能测试
|
||||
|
||||
| 数据量 | 预期查询次数 | 预期耗时 | 内存占用 |
|
||||
|--------|------------|---------|---------|
|
||||
| 100条 | 1次 | < 20ms | < 10KB |
|
||||
| 1000条 | 1次 | < 100ms | < 50KB |
|
||||
| 10000条 | 1次 | < 500ms | < 500KB |
|
||||
|
||||
---
|
||||
|
||||
## 8. 影响范围和实施计划
|
||||
|
||||
### 8.1 影响的文件
|
||||
|
||||
| 文件 | 修改类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `CcdiStaffEnterpriseRelationImportServiceImpl.java` | 修改 | 添加员工身份证号验证 |
|
||||
| `CcdiStaffFmyRelationImportServiceImpl.java` | 修改 | 添加员工身份证号验证 |
|
||||
| `CcdiStaffTransferImportServiceImpl.java` | 优化 | 从2次遍历优化为1次遍历 |
|
||||
|
||||
### 8.2 不影响的组件
|
||||
|
||||
- ✅ Controller层(无需修改)
|
||||
- ✅ 前端页面(无需修改)
|
||||
- ✅ 数据库表结构(无需修改)
|
||||
- ✅ Mapper接口(无需修改)
|
||||
- ✅ VO/DTO/Excel类(无需修改)
|
||||
|
||||
### 8.3 数据库索引建议
|
||||
|
||||
```sql
|
||||
-- 检查索引是否存在
|
||||
SHOW INDEX FROM ccdi_base_staff WHERE Column_name = 'id_card';
|
||||
|
||||
-- 如果不存在,创建索引
|
||||
CREATE INDEX idx_ccdi_base_staff_id_card ON ccdi_base_staff(id_card);
|
||||
```
|
||||
|
||||
### 8.4 实施步骤
|
||||
|
||||
1. ✅ 完成设计方案
|
||||
2. ⏳ 修改 `CcdiStaffEnterpriseRelationImportServiceImpl`
|
||||
3. ⏳ 修改 `CcdiStaffFmyRelationImportServiceImpl`
|
||||
4. ⏳ 优化 `CcdiStaffTransferImportServiceImpl`
|
||||
5. ⏳ 检查并创建数据库索引(如需要)
|
||||
6. ⏳ 编写单元测试
|
||||
7. ⏳ 本地测试验证
|
||||
8. ⏳ 更新API文档(如需要)
|
||||
|
||||
### 8.5 验收标准
|
||||
|
||||
- [ ] 不存在的员工身份证号被正确识别并记录错误
|
||||
- [ ] 错误信息包含正确的行号和身份证号
|
||||
- [ ] 有效数据正常导入
|
||||
- [ ] 日志记录完整
|
||||
- [ ] 性能无明显下降(批量查询仅1次)
|
||||
- [ ] 与现有导入逻辑保持一致
|
||||
- [ ] 三个导入服务功能一致
|
||||
|
||||
### 8.6 风险评估
|
||||
|
||||
- **风险等级**: 低
|
||||
- **影响范围**: 仅影响导入功能,不影响其他模块
|
||||
- **回滚方案**: 直接移除新增的验证代码即可
|
||||
- **数据安全**: 只读查询,无数据风险
|
||||
|
||||
---
|
||||
|
||||
## 9. 设计总结
|
||||
|
||||
### 9.1 核心优势
|
||||
|
||||
1. **性能优异**: 批量查询,仅1次数据库访问
|
||||
2. **代码简洁**: 仅1次遍历,逻辑清晰
|
||||
3. **一致性高**: 三个导入服务使用相同的设计模式
|
||||
4. **易于维护**: 遵循现有框架规范
|
||||
|
||||
### 9.2 与原员工调动导入设计的对比
|
||||
|
||||
| 对比项 | 原设计 | 新设计 |
|
||||
|--------|--------|--------|
|
||||
| 遍历次数 | 2次 | **1次** ⭐ |
|
||||
| 代码复杂度 | 需要额外方法 | 更简洁 |
|
||||
| 性能 | 优秀 | **更优** |
|
||||
| 可维护性 | 良好 | **更好** |
|
||||
|
||||
### 9.3 设计亮点
|
||||
|
||||
- ✅ **批量预验证**: 充分利用数据库 IN 查询性能
|
||||
- ✅ **单次遍历**: 减少不必要的循环,代码更清晰
|
||||
- ✅ **统一模式**: 三个导入服务使用一致的验证逻辑
|
||||
- ✅ **错误友好**: 详细的错误信息包含行号
|
||||
- ✅ **性能监控**: 完整的日志记录便于排查问题
|
||||
|
||||
---
|
||||
|
||||
## 10. 附录
|
||||
|
||||
### 10.1 相关文档
|
||||
|
||||
- [员工调动导入员工ID校验设计文档](2026-02-11-staff-transfer-import-staff-id-validation-design.md)
|
||||
- [若依框架导入功能说明](https://doc.ruoyi.vip/)
|
||||
- [MyBatis Plus 官方文档](https://baomidou.com/)
|
||||
|
||||
### 10.2 设计决策记录
|
||||
|
||||
**Q1: 为什么选择批量预验证而非逐条验证?**
|
||||
A: 批量验证只需1次数据库查询,性能提升约100倍。
|
||||
|
||||
**Q2: 为什么优化为1次遍历?**
|
||||
A: 减少不必要的循环,代码更简洁,性能更好。
|
||||
|
||||
**Q3: 为什么不验证员工在职状态?**
|
||||
A: 需求明确仅验证身份证号存在性,避免过度设计。
|
||||
|
||||
**Q4: 为什么不验证关系人身份证号?**
|
||||
A: 关系人可能不是系统员工,验证会限制使用场景。
|
||||
|
||||
### 10.3 版本历史
|
||||
|
||||
- v1.0 (2026-02-11): 初始设计版本,包含三个导入服务的身份证号校验
|
||||
@@ -1,384 +0,0 @@
|
||||
# 员工调动导入员工ID校验设计文档
|
||||
|
||||
**日期**: 2026-02-11
|
||||
**状态**: 设计完成
|
||||
**优先级**: 中
|
||||
|
||||
---
|
||||
|
||||
## 1. 需求概述
|
||||
|
||||
### 1.1 背景
|
||||
当前员工调动导入功能(`CcdiStaffTransferImportServiceImpl`)在导入数据时,没有验证员工ID是否在员工信息表中存在。这可能导致导入的数据引用了不存在的员工ID,造成数据完整性问题。
|
||||
|
||||
### 1.2 目标
|
||||
在员工调动导入过程中,添加员工ID存在性校验:
|
||||
- 验证员工ID是否在 `ccdi_base_staff` 表中存在
|
||||
- 不存在的员工ID记录错误信息并跳过
|
||||
- 继续处理其他有效数据
|
||||
|
||||
### 1.3 约束条件
|
||||
- 仅验证员工ID存在性,不验证员工状态
|
||||
- 错误信息需要包含Excel行号
|
||||
- 与现有的导入流程保持一致(失败记录保存到Redis)
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 整体架构
|
||||
|
||||
在现有的 `CcdiStaffTransferImportServiceImpl` 中,在 `importTransferAsync` 方法的数据处理循环之前,添加一个**员工ID批量预验证阶段**。
|
||||
|
||||
```
|
||||
导入流程:
|
||||
1. 批量查询已存在的调动记录唯一键(原有)
|
||||
2. 批量验证员工ID是否存在(新增)⭐
|
||||
3. 分类数据循环处理(原有,修改)
|
||||
└─ 跳过已在预验证阶段失败的记录(新增)
|
||||
4. 批量插入新数据(原有)
|
||||
5. 保存失败记录到Redis(原有)
|
||||
6. 更新导入状态(原有)
|
||||
```
|
||||
|
||||
### 2.2 新增组件
|
||||
|
||||
#### 2.2.1 依赖注入
|
||||
```java
|
||||
@Resource
|
||||
private CcdiBaseStaffMapper baseStaffMapper;
|
||||
```
|
||||
|
||||
#### 2.2.2 核心方法
|
||||
|
||||
**方法1: batchValidateStaffIds**
|
||||
- 功能: 批量验证员工ID是否存在
|
||||
- 输入: Excel数据列表、任务ID、失败记录列表
|
||||
- 输出: 存在的员工ID集合
|
||||
- 位置: 第65行之前调用
|
||||
|
||||
**方法2: isRowAlreadyFailed**
|
||||
- 功能: 检查某行数据是否已在失败列表中
|
||||
- 输入: Excel数据、失败记录列表
|
||||
- 输出: boolean
|
||||
- 位置: 主循环中使用
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据流设计
|
||||
|
||||
### 3.1 详细流程
|
||||
|
||||
```
|
||||
阶段1: 提取员工ID(新增)
|
||||
├─ 从 excelList 提取所有 staffId
|
||||
├─ 过滤 null 值
|
||||
├─ HashSet 去重
|
||||
└─ 得到 Set<Long> allStaffIds
|
||||
|
||||
阶段2: 批量查询(新增)
|
||||
├─ 如果 allStaffIds 为空,返回空集合
|
||||
├─ 构建查询: WHERE staffId IN (...)
|
||||
├─ 执行: baseStaffMapper.selectList(wrapper)
|
||||
├─ 提取结果中的 staffId
|
||||
└─ 得到 Set<Long> existingStaffIds
|
||||
|
||||
阶段3: 预验证(新增)
|
||||
├─ 遍历 excelList(行号 1-based)
|
||||
│ ├─ 提取当前行的 staffId
|
||||
│ ├─ 如果 staffId 不在 existingStaffIds 中:
|
||||
│ │ ├─ 创建 StaffTransferImportFailureVO
|
||||
│ │ ├─ 错误信息: "第{行号}行: 员工ID {staffId} 不存在"
|
||||
│ │ ├─ 添加到 failures 列表
|
||||
│ │ └─ 记录验证失败日志
|
||||
│ └─ 否则,继续处理
|
||||
└─ 返回 existingStaffIds
|
||||
|
||||
阶段4: 原有数据处理循环(修改)
|
||||
└─ 循环开始时检查:
|
||||
└─ 如果当前行已在 failures 中,跳过
|
||||
└─ 否则,执行原有处理逻辑
|
||||
```
|
||||
|
||||
### 3.2 错误信息格式
|
||||
|
||||
```java
|
||||
String errorMessage = String.format("第%d行: 员工ID %s 不存在",
|
||||
rowNumber, staffId);
|
||||
```
|
||||
|
||||
### 3.3 日志记录
|
||||
|
||||
使用 `ImportLogUtils` 记录:
|
||||
- 批量查询开始: `logBatchQueryStart(log, taskId, "员工ID", count)`
|
||||
- 批量查询完成: `logBatchQueryComplete(log, taskId, "员工ID", count)`
|
||||
- 验证失败: `logValidationError(log, taskId, rowNumber, errorMessage, keyData)`
|
||||
|
||||
---
|
||||
|
||||
## 4. 代码实现
|
||||
|
||||
### 4.1 新增方法实现
|
||||
|
||||
#### 4.1.1 batchValidateStaffIds
|
||||
|
||||
```java
|
||||
/**
|
||||
* 批量验证员工ID是否存在
|
||||
*
|
||||
* @param excelList Excel数据列表
|
||||
* @param taskId 任务ID
|
||||
* @param failures 失败记录列表(会追加验证失败的记录)
|
||||
* @return 存在的员工ID集合
|
||||
*/
|
||||
private Set<Long> batchValidateStaffIds(List<CcdiStaffTransferExcel> excelList,
|
||||
String taskId,
|
||||
List<StaffTransferImportFailureVO> failures) {
|
||||
// 1. 提取并去重员工ID
|
||||
Set<Long> allStaffIds = excelList.stream()
|
||||
.map(CcdiStaffTransferExcel::getStaffId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (allStaffIds.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// 2. 批量查询存在的员工ID
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "员工ID", allStaffIds.size());
|
||||
|
||||
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(CcdiBaseStaff::getStaffId)
|
||||
.in(CcdiBaseStaff::getStaffId, allStaffIds);
|
||||
|
||||
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper);
|
||||
Set<Long> existingStaffIds = existingStaff.stream()
|
||||
.map(CcdiBaseStaff::getStaffId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工ID", existingStaffIds.size());
|
||||
|
||||
// 3. 预验证并标记不存在的员工ID
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiStaffTransferExcel excel = excelList.get(i);
|
||||
Long staffId = excel.getStaffId();
|
||||
|
||||
if (staffId != null && !existingStaffIds.contains(staffId)) {
|
||||
StaffTransferImportFailureVO failure = new StaffTransferImportFailureVO();
|
||||
BeanUtils.copyProperties(excel, failure);
|
||||
failure.setErrorMessage(String.format("第%d行: 员工ID %s 不存在", i + 1, staffId));
|
||||
failures.add(failure);
|
||||
|
||||
String keyData = String.format("员工ID=%s", staffId);
|
||||
ImportLogUtils.logValidationError(log, taskId, i + 1,
|
||||
failure.getErrorMessage(), keyData);
|
||||
}
|
||||
}
|
||||
|
||||
return existingStaffIds;
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.1.2 isRowAlreadyFailed
|
||||
|
||||
```java
|
||||
/**
|
||||
* 检查某行数据是否已在失败列表中
|
||||
*
|
||||
* @param excel Excel数据
|
||||
* @param failures 失败记录列表
|
||||
* @return true-已失败,false-未失败
|
||||
*/
|
||||
private boolean isRowAlreadyFailed(CcdiStaffTransferExcel excel,
|
||||
List<StaffTransferImportFailureVO> failures) {
|
||||
return failures.stream()
|
||||
.anyMatch(f -> f.getStaffId().equals(excel.getStaffId())
|
||||
&& Objects.equals(f.getTransferDate(), excel.getTransferDate())
|
||||
&& Objects.equals(f.getDeptIdBefore(), excel.getDeptIdBefore())
|
||||
&& Objects.equals(f.getDeptIdAfter(), excel.getDeptIdAfter()));
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 主循环修改
|
||||
|
||||
在 `importTransferAsync` 方法的第 73 行开始:
|
||||
|
||||
```java
|
||||
// 原有代码
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiStaffTransferExcel excel = excelList.get(i);
|
||||
|
||||
try {
|
||||
// ...原有处理逻辑
|
||||
|
||||
// 修改为
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiStaffTransferExcel excel = excelList.get(i);
|
||||
|
||||
// 新增: 跳过已在预验证阶段失败的记录
|
||||
if (isRowAlreadyFailed(excel, failures)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// ...原有处理逻辑
|
||||
```
|
||||
|
||||
### 4.3 调用位置
|
||||
|
||||
在 `importTransferAsync` 方法中,第 65 行之后插入:
|
||||
|
||||
```java
|
||||
List<CcdiStaffTransfer> newRecords = new ArrayList<>();
|
||||
List<StaffTransferImportFailureVO> failures = new ArrayList<>();
|
||||
|
||||
// 新增: 批量验证员工ID
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "员工ID预验证", excelList.size());
|
||||
Set<Long> existingStaffIds = batchValidateStaffIds(excelList, taskId, failures);
|
||||
|
||||
// 原有代码继续
|
||||
// 批量查询已存在的唯一键组合
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的调动记录", excelList.size());
|
||||
Set<String> existingKeys = getExistingTransferKeys(excelList);
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "调动记录", existingKeys.size());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 边界情况处理
|
||||
|
||||
### 5.1 员工ID为null
|
||||
```java
|
||||
// 在提取时过滤null
|
||||
.filter(Objects::nonNull)
|
||||
|
||||
// 在预验证时跳过,留给后续validateTransferData处理
|
||||
if (staffId == null) {
|
||||
continue;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Excel为空或所有员工ID为null
|
||||
```java
|
||||
if (allStaffIds.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 所有员工ID都不存在
|
||||
- `existingStaffIds` 为空集合
|
||||
- 所有记录都会被加入 `failures`
|
||||
- `newRecords` 保持为空
|
||||
- 最终状态: `PARTIAL_SUCCESS`
|
||||
|
||||
### 5.4 Excel中有重复员工ID
|
||||
- 使用 HashSet 去重,只查询一次
|
||||
- 预验证时每行都会独立检查并生成对应的失败记录
|
||||
|
||||
### 5.5 数据库中没有员工记录
|
||||
- `baseStaffMapper.selectList` 返回空列表
|
||||
- 所有Excel行都会标记为失败
|
||||
|
||||
---
|
||||
|
||||
## 6. 性能分析
|
||||
|
||||
### 6.1 时间复杂度
|
||||
- 提取员工ID: O(n),n为Excel行数
|
||||
- 数据库查询: O(m),m为不重复员工ID数量
|
||||
- 预验证: O(n)
|
||||
- **总计: O(n)**
|
||||
|
||||
### 6.2 空间复杂度
|
||||
- `allStaffIds`: 约 8字节 × m
|
||||
- `existingStaffIds`: 约 8字节 × m
|
||||
- **总计: 约 16KB / 1000个不重复员工ID**
|
||||
|
||||
### 6.3 数据库查询
|
||||
- 查询次数: **仅1次**
|
||||
- 查询类型: `SELECT staffId FROM ccdi_base_staff WHERE staffId IN (...)`
|
||||
- 索引: `staffId` 为主键,性能最优
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试场景
|
||||
|
||||
### 7.1 功能测试
|
||||
|
||||
| 场景 | 输入 | 预期结果 |
|
||||
|------|------|----------|
|
||||
| 正常导入 | 5条有效员工ID | 全部成功,failures为空 |
|
||||
| 部分无效 | 3条有效 + 2条无效 | 3条成功,2条失败 |
|
||||
| 全部无效 | 5条全部无效 | 0条成功,5条失败 |
|
||||
| 员工ID为null | 包含null记录 | 在后续验证中报错 |
|
||||
| 大批量数据 | 1000条记录 | 仅1次查询,性能良好 |
|
||||
| 重复员工ID | 10条记录,3个不同ID | 去重查询,正确验证 |
|
||||
|
||||
### 7.2 集成测试
|
||||
- 验证Redis中失败记录格式正确
|
||||
- 验证导入状态API返回正确
|
||||
- 验证日志输出完整
|
||||
- 验证事务回滚正常
|
||||
|
||||
---
|
||||
|
||||
## 8. 影响范围
|
||||
|
||||
### 8.1 影响的文件
|
||||
| 文件 | 修改类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `CcdiStaffTransferImportServiceImpl.java` | 修改 | 添加员工ID验证逻辑 |
|
||||
|
||||
### 8.2 不影响的组件
|
||||
- ✅ Controller层(无需修改)
|
||||
- ✅ 前端页面(无需修改)
|
||||
- ✅ 数据库表结构(无需修改)
|
||||
- ✅ 其他导入服务(建议后续同步修改)
|
||||
|
||||
### 8.3 建议同步修改的服务
|
||||
为了保持一致性,建议对以下导入服务添加相同的员工ID验证:
|
||||
- `CcdiIntermediaryEntityImportServiceImpl` - 员工中介实体导入
|
||||
- `CcdiIntermediaryPersonImportServiceImpl` - 员工中介人员导入
|
||||
- `CcdiStaffRecruitmentImportServiceImpl` - 员工招聘导入
|
||||
- `CcdiBaseStaffImportServiceImpl` - 员工信息导入
|
||||
|
||||
---
|
||||
|
||||
## 9. 实施计划
|
||||
|
||||
### 9.1 实施步骤
|
||||
1. ✅ 完成设计方案
|
||||
2. ⏳ 修改 `CcdiStaffTransferImportServiceImpl`
|
||||
3. ⏳ 编写单元测试
|
||||
4. ⏳ 本地测试验证
|
||||
5. ⏳ 提交代码并生成API文档
|
||||
6. ⏳ 同步修改其他导入服务(可选)
|
||||
|
||||
### 9.2 验收标准
|
||||
- [x] 不存在的员工ID被正确识别并记录错误
|
||||
- [x] 错误信息包含正确的行号
|
||||
- [x] 有效数据正常导入
|
||||
- [x] 日志记录完整
|
||||
- [x] 性能无明显下降
|
||||
- [x] 与现有导入逻辑保持一致
|
||||
|
||||
---
|
||||
|
||||
## 10. 附录
|
||||
|
||||
### 10.1 相关文档
|
||||
- [若依框架导入功能说明](https://doc.ruoyi.vip/)
|
||||
- [MyBatis Plus 官方文档](https://baomidou.com/)
|
||||
|
||||
### 10.2 设计决策记录
|
||||
- **Q1: 为什么选择批量预验证而非逐条验证?**
|
||||
- A: 批量验证只需1次数据库查询,性能更好,且符合现有部门验证的模式
|
||||
|
||||
- **Q2: 为什么不验证员工在职状态?**
|
||||
- A: 需求明确仅验证员工ID存在性,避免过度设计
|
||||
|
||||
- **Q3: 为什么选择跳过无效记录而非停止导入?**
|
||||
- A: 与现有导入逻辑一致,最大化导入成功率
|
||||
|
||||
### 10.3 版本历史
|
||||
- v1.0 (2026-02-11): 初始设计版本
|
||||
@@ -1,508 +0,0 @@
|
||||
# 员工调动导入员工ID校验功能实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**目标:** 在员工调动导入功能中添加员工ID存在性校验,确保只导入有效员工的调动记录
|
||||
|
||||
**架构:** 采用批量预验证模式,在数据处理循环前执行一次批量数据库查询验证所有员工ID,不存在的记录提前标记为失败并跳过后续处理
|
||||
|
||||
**技术栈:** Spring Boot 3.5.8, MyBatis Plus 3.5.10, Java 17, Redis
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 添加 CcdiBaseStaffMapper 依赖注入
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java:48`
|
||||
|
||||
**Step 1: 添加依赖注入字段**
|
||||
|
||||
在第48行 `SysDeptMapper deptMapper` 之后添加:
|
||||
|
||||
```java
|
||||
@Resource
|
||||
private CcdiBaseStaffMapper baseStaffMapper;
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
Run: `cd .worktrees/staff-transfer-validation && mvn clean compile -q`
|
||||
Expected: 编译成功,无错误
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
cd .worktrees/staff-transfer-validation
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java
|
||||
git commit -m "feat: 添加CcdiBaseStaffMapper依赖注入
|
||||
|
||||
为员工调动导入服务添加员工信息Mapper,用于批量验证员工ID存在性"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 实现批量验证员工ID方法
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java` (在文件末尾添加私有方法)
|
||||
|
||||
**Step 1: 编写批量验证方法**
|
||||
|
||||
在 `getImportFailures` 方法之后添加:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 批量验证员工ID是否存在
|
||||
*
|
||||
* @param excelList Excel数据列表
|
||||
* @param taskId 任务ID
|
||||
* @param failures 失败记录列表(会追加验证失败的记录)
|
||||
* @return 存在的员工ID集合
|
||||
*/
|
||||
private Set<Long> batchValidateStaffIds(List<CcdiStaffTransferExcel> excelList,
|
||||
String taskId,
|
||||
List<StaffTransferImportFailureVO> failures) {
|
||||
// 1. 提取并去重员工ID
|
||||
Set<Long> allStaffIds = excelList.stream()
|
||||
.map(CcdiStaffTransferExcel::getStaffId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (allStaffIds.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// 2. 批量查询存在的员工ID
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "员工ID", allStaffIds.size());
|
||||
|
||||
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(CcdiBaseStaff::getStaffId)
|
||||
.in(CcdiBaseStaff::getStaffId, allStaffIds);
|
||||
|
||||
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper);
|
||||
Set<Long> existingStaffIds = existingStaff.stream()
|
||||
.map(CcdiBaseStaff::getStaffId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工ID", existingStaffIds.size());
|
||||
|
||||
// 3. 预验证并标记不存在的员工ID
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiStaffTransferExcel excel = excelList.get(i);
|
||||
Long staffId = excel.getStaffId();
|
||||
|
||||
if (staffId != null && !existingStaffIds.contains(staffId)) {
|
||||
StaffTransferImportFailureVO failure = new StaffTransferImportFailureVO();
|
||||
BeanUtils.copyProperties(excel, failure);
|
||||
failure.setErrorMessage(String.format("第%d行: 员工ID %s 不存在", i + 1, staffId));
|
||||
failures.add(failure);
|
||||
|
||||
String keyData = String.format("员工ID=%s", staffId);
|
||||
ImportLogUtils.logValidationError(log, taskId, i + 1,
|
||||
failure.getErrorMessage(), keyData);
|
||||
}
|
||||
}
|
||||
|
||||
return existingStaffIds;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
Run: `cd .worktrees/staff-transfer-validation && mvn clean compile -q`
|
||||
Expected: 编译成功,无错误
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
cd .worktrees/staff-transfer-validation
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java
|
||||
git commit -m "feat: 实现批量验证员工ID方法
|
||||
|
||||
- 提取Excel中所有员工ID并去重
|
||||
- 批量查询数据库中存在的员工ID
|
||||
- 标记不存在的员工ID为失败记录
|
||||
- 记录详细的验证日志"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 实现检查行是否已失败方法
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java` (在 batchValidateStaffIds 方法之后)
|
||||
|
||||
**Step 1: 编写检查方法**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 检查某行数据是否已在失败列表中
|
||||
*
|
||||
* @param excel Excel数据
|
||||
* @param failures 失败记录列表
|
||||
* @return true-已失败,false-未失败
|
||||
*/
|
||||
private boolean isRowAlreadyFailed(CcdiStaffTransferExcel excel,
|
||||
List<StaffTransferImportFailureVO> failures) {
|
||||
return failures.stream()
|
||||
.anyMatch(f -> f.getStaffId().equals(excel.getStaffId())
|
||||
&& Objects.equals(f.getTransferDate(), excel.getTransferDate())
|
||||
&& Objects.equals(f.getDeptIdBefore(), excel.getDeptIdBefore())
|
||||
&& Objects.equals(f.getDeptIdAfter(), excel.getDeptIdAfter()));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
Run: `cd .worktrees/staff-transfer-validation && mvn clean compile -q`
|
||||
Expected: 编译成功,无错误
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
cd .worktrees/staff-transfer-validation
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java
|
||||
git commit -m "feat: 实现检查行是否已失败方法
|
||||
|
||||
通过比较员工ID、调动日期、调动前部门ID、调动后部门ID判断该行是否已在失败列表中"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 在导入方法中调用批量验证
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java:62-68`
|
||||
|
||||
**Step 1: 修改导入方法初始化部分**
|
||||
|
||||
在第62-68行,将:
|
||||
|
||||
```java
|
||||
List<CcdiStaffTransfer> newRecords = new ArrayList<>();
|
||||
List<StaffTransferImportFailureVO> failures = new ArrayList<>();
|
||||
|
||||
// 批量查询已存在的唯一键组合
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的调动记录", excelList.size());
|
||||
Set<String> existingKeys = getExistingTransferKeys(excelList);
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "调动记录", existingKeys.size());
|
||||
```
|
||||
|
||||
修改为:
|
||||
|
||||
```java
|
||||
List<CcdiStaffTransfer> newRecords = new ArrayList<>();
|
||||
List<StaffTransferImportFailureVO> failures = new ArrayList<>();
|
||||
|
||||
// 批量验证员工ID是否存在
|
||||
Set<Long> existingStaffIds = batchValidateStaffIds(excelList, taskId, failures);
|
||||
|
||||
// 批量查询已存在的唯一键组合
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的调动记录", excelList.size());
|
||||
Set<String> existingKeys = getExistingTransferKeys(excelList);
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "调动记录", existingKeys.size());
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
Run: `cd .worktrees/staff-transfer-validation && mvn clean compile -q`
|
||||
Expected: 编译成功,无错误
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
cd .worktrees/staff-transfer-validation
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java
|
||||
git commit -m "feat: 在导入流程中添加员工ID批量验证
|
||||
|
||||
在数据处理循环前添加员工ID存在性验证阶段,提前标记无效员工ID的记录"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 在主循环中跳过已失败记录
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java:73-78`
|
||||
|
||||
**Step 1: 修改主循环开始部分**
|
||||
|
||||
在第73-78行,将:
|
||||
|
||||
```java
|
||||
// 分类数据
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiStaffTransferExcel excel = excelList.get(i);
|
||||
|
||||
try {
|
||||
```
|
||||
|
||||
修改为:
|
||||
|
||||
```java
|
||||
// 分类数据
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiStaffTransferExcel excel = excelList.get(i);
|
||||
|
||||
// 跳过已在预验证阶段失败的记录
|
||||
if (isRowAlreadyFailed(excel, failures)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
Run: `cd .worktrees/staff-transfer-validation && mvn clean compile -q`
|
||||
Expected: 编译成功,无错误
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
cd .worktrees/staff-transfer-validation
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java
|
||||
git commit -m "feat: 主循环跳过已失败的记录
|
||||
|
||||
在数据处理循环中添加检查逻辑,跳过已在预验证阶段标记为失败的记录"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 编写测试脚本
|
||||
|
||||
**文件:**
|
||||
- 创建: `doc/test-data/staff-transfer-validation-test.http`
|
||||
|
||||
**Step 1: 创建HTTP测试文件**
|
||||
|
||||
```http
|
||||
### 员工调动导入员工ID验证测试
|
||||
|
||||
### 1. 获取登录Token
|
||||
POST http://localhost:8080/login/test
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
username=admin&password=admin123
|
||||
|
||||
> {%
|
||||
client.global.set("token", response.body.token);
|
||||
client.log("Token: " + response.body.token);
|
||||
%}
|
||||
|
||||
### 2. 测试正常导入(所有员工ID存在)
|
||||
POST http://localhost:8080/ccdi/staffTransfer/import
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: multipart/form-data; boundary=boundary
|
||||
|
||||
--boundary
|
||||
Content-Disposition: form-data; name="file"; filename="valid-staff-ids.xlsx"
|
||||
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
|
||||
< ./valid-staff-ids.xlsx
|
||||
--boundary--
|
||||
|
||||
### 3. 测试部分员工ID不存在
|
||||
POST http://localhost:8080/ccdi/staffTransfer/import
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: multipart/form-data; boundary=boundary
|
||||
|
||||
--boundary
|
||||
Content-Disposition: form-data; name="file"; filename="partial-invalid-ids.xlsx"
|
||||
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
|
||||
< ./partial-invalid-ids.xlsx
|
||||
--boundary--
|
||||
|
||||
### 4. 测试所有员工ID不存在
|
||||
POST http://localhost:8080/ccdi/staffTransfer/import
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: multipart/form-data; boundary=boundary
|
||||
|
||||
--boundary
|
||||
Content-Disposition: form-data; name="file"; filename="all-invalid-ids.xlsx"
|
||||
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
|
||||
< ./all-invalid-ids.xlsx
|
||||
--boundary--
|
||||
|
||||
### 5. 查询导入状态
|
||||
GET http://localhost:8080/ccdi/staffTransfer/import/status/{{taskId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 6. 获取失败记录
|
||||
GET http://localhost:8080/ccdi/staffTransfer/import/failures/{{taskId}}
|
||||
Authorization: Bearer {{token}}
|
||||
```
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
cd .worktrees/staff-transfer-validation
|
||||
git add doc/test-data/staff-transfer-validation-test.http
|
||||
git commit -m "test: 添加员工ID验证测试脚本
|
||||
|
||||
包含正常导入、部分无效、全部无效等测试场景"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: 生成本次修改的API文档
|
||||
|
||||
**文件:**
|
||||
- 修改: `doc/interface-doc/ccdi/staff-transfer.md` (如果文件不存在则创建)
|
||||
|
||||
**Step 1: 更新API文档**
|
||||
|
||||
在现有的员工调动导入接口文档中,添加错误情况说明:
|
||||
|
||||
```markdown
|
||||
### 员工调动导入
|
||||
|
||||
**接口地址:** `POST /ccdi/staffTransfer/import`
|
||||
|
||||
**请求参数:**
|
||||
- file: Excel文件(multipart/form-data)
|
||||
|
||||
**响应格式:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "导入任务已提交",
|
||||
"data": {
|
||||
"taskId": "uuid"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误情况:**
|
||||
|
||||
| 错误类型 | 错误信息示例 | 说明 |
|
||||
|---------|-------------|------|
|
||||
| 员工ID不存在 | 第3行: 员工ID 99999 不存在 | 该员工ID在员工信息表中不存在 |
|
||||
| 员工ID为空 | 员工ID不能为空 | Excel中未填写员工ID |
|
||||
| 调动类型无效 | 调动类型[xxx]无效 | 调动类型不在字典中 |
|
||||
| 部门ID不存在 | 部门ID 999 不存在 | 调动前/后部门ID在部门表中不存在 |
|
||||
| 记录重复 | 该员工在2026-01-01的调动记录已存在 | 数据库中已存在相同的调动记录 |
|
||||
|
||||
**导入状态查询:**
|
||||
|
||||
使用返回的 `taskId` 查询导入进度和结果。
|
||||
|
||||
**失败记录查询:**
|
||||
|
||||
导入失败或部分成功时,可通过 `taskId` 获取详细的失败记录列表。
|
||||
```
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
cd .worktrees/staff-transfer-validation
|
||||
git add doc/interface-doc/ccdi/staff-transfer.md
|
||||
git commit -m "docs: 更新员工调动导入API文档
|
||||
|
||||
添加员工ID验证相关的错误情况说明"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: 最终验证和测试
|
||||
|
||||
**Step 1: 编译项目**
|
||||
|
||||
Run: `cd .worktrees/staff-transfer-validation && mvn clean compile -q`
|
||||
Expected: 编译成功,无错误
|
||||
|
||||
**Step 2: 运行测试(如果有单元测试)**
|
||||
|
||||
Run: `cd .worktrees/staff-transfer-validation && mvn test -Dtest=*StaffTransferImport* -q`
|
||||
Expected: 测试通过
|
||||
|
||||
**Step 3: 代码审查检查清单**
|
||||
|
||||
- [ ] 所有新增方法都有完整的JavaDoc注释
|
||||
- [ ] 错误信息包含行号,便于用户定位
|
||||
- [ ] 使用ImportLogUtils记录详细的验证日志
|
||||
- [ ] 仅执行1次数据库查询批量验证所有员工ID
|
||||
- [ ] 失败记录正确保存到Redis
|
||||
- [ ] 与现有导入逻辑保持一致(跳过失败记录继续处理)
|
||||
- [ ] 代码风格符合项目规范
|
||||
- [ ] 无hardcode的字符串或数字
|
||||
|
||||
**Step 4: 最终提交**
|
||||
|
||||
```bash
|
||||
cd .worktrees/staff-transfer-validation
|
||||
git add -A
|
||||
git commit -m "feat: 完成员工调动导入员工ID校验功能
|
||||
|
||||
功能实现:
|
||||
- 批量预验证员工ID存在性(1次数据库查询)
|
||||
- 不存在的员工ID记录错误并跳过
|
||||
- 错误信息包含Excel行号
|
||||
- 完整的日志记录
|
||||
|
||||
技术实现:
|
||||
- 新增 batchValidateStaffIds() 方法
|
||||
- 新增 isRowAlreadyFailed() 方法
|
||||
- 修改 importTransferAsync() 主流程
|
||||
- 添加 CcdiBaseStaffMapper 依赖
|
||||
|
||||
测试:
|
||||
- 添加HTTP测试脚本
|
||||
- 更新API文档
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实施后任务
|
||||
|
||||
### 合并到主分支
|
||||
|
||||
**Step 1: 切换到dev_1分支**
|
||||
|
||||
```bash
|
||||
cd D:\ccdi\ccdi
|
||||
git checkout dev_1
|
||||
git pull origin dev_1
|
||||
```
|
||||
|
||||
**Step 2: 合并feature分支**
|
||||
|
||||
```bash
|
||||
git merge feat/staff-transfer-staff-id-validation --no-ff
|
||||
```
|
||||
|
||||
**Step 3: 推送到远程**
|
||||
|
||||
```bash
|
||||
git push origin dev_1
|
||||
```
|
||||
|
||||
**Step 4: 清理worktree**
|
||||
|
||||
```bash
|
||||
git worktree remove .worktrees/staff-transfer-validation
|
||||
git branch -d feat/staff-transfer-staff-id-validation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### 相关文档
|
||||
- 设计文档: `doc/plans/2026-02-11-staff-transfer-import-staff-id-validation-design.md`
|
||||
- 员工调动接口文档: `doc/interface-doc/ccdi/staff-transfer.md`
|
||||
- 导入服务代码: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffTransferImportServiceImpl.java`
|
||||
|
||||
### 依赖服务
|
||||
- 数据库: ccdi_intermediary_blacklist
|
||||
- Redis: 用于存储导入状态和失败记录
|
||||
|
||||
### 测试数据准备
|
||||
需要在 `doc/test-data/` 目录下准备测试Excel文件:
|
||||
- `valid-staff-ids.xlsx`: 包含有效员工ID的调动记录
|
||||
- `partial-invalid-ids.xlsx`: 包含部分无效员工ID的调动记录
|
||||
- `all-invalid-ids.xlsx`: 所有员工ID都无效的调动记录
|
||||
@@ -1,93 +0,0 @@
|
||||
# ruoyi-ccdi 模块重命名设计文档
|
||||
|
||||
## 概述
|
||||
|
||||
将 `ruoyi-ccdi` 模块重命名为 `ruoyi-info-collection`,以更清晰地表达"信息采集"功能,同时保持与其他功能模块的命名一致性。
|
||||
|
||||
## 设计决策
|
||||
|
||||
### 方案选择:混合命名(方案 A)
|
||||
|
||||
| 项目 | 当前命名 | 目标命名 |
|
||||
|-----|---------|---------|
|
||||
| Maven 模块 | `ruoyi-ccdi` | `ruoyi-info-collection` |
|
||||
| Java 包名 | `com.ruoyi.ccdi` | `com.ruoyi.info.collection` |
|
||||
| 数据库表 | `ccdi_*` | `ccdi_*` (保持不变) |
|
||||
| API URL | `/ccdi/*` | `/ccdi/*` (保持不变) |
|
||||
| 权限标识 | `ccdi:*:*` | `ccdi:*:*` (保持不变) |
|
||||
| 前端文件 | `ccdi*` | `ccdi*` (保持不变) |
|
||||
|
||||
### 选择理由
|
||||
|
||||
1. **模块名和包名**:更清晰表达"信息采集"功能
|
||||
2. **保留 ccdi 前缀**:在 URL、表名、前端避免破坏性变更
|
||||
3. **数据库不变**:无需迁移数据,降低风险
|
||||
4. **API 不变**:前端调用无需修改
|
||||
|
||||
## 修改清单
|
||||
|
||||
### 1. Maven 模块重命名
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|-----|---------|
|
||||
| `pom.xml` (根目录) | `<module>ruoyi-ccdi</module>` → `<module>ruoyi-info-collection</module>` |
|
||||
| `pom.xml` (根目录) | `<artifactId>ruoyi-ccdi</artifactId>` → `<artifactId>ruoyi-info-collection</artifactId>` |
|
||||
| `ruoyi-ccdi/pom.xml` | 目录重命名为 `ruoyi-info-collection/`,`<artifactId>` 同步修改 |
|
||||
| `ruoyi-admin/pom.xml` | `<artifactId>ruoyi-ccdi</artifactId>` → `<artifactId>ruoyi-info-collection</artifactId>` |
|
||||
|
||||
### 2. Java 包名重命名
|
||||
|
||||
- **目录结构**:`com/ruoyi/ccdi/` → `com/ruoyi/info/collection/`
|
||||
- **涉及文件**:约 100+ 个 Java 文件
|
||||
- **修改内容**:
|
||||
- 所有 `package com.ruoyi.ccdi` → `package com.ruoyi.info.collection`
|
||||
- 所有 `import com.ruoyi.ccdi.*` → `import com.ruoyi.info.collection.*`
|
||||
|
||||
### 3. MyBatis XML 命名空间
|
||||
|
||||
- **涉及文件**:11 个 Mapper XML 文件
|
||||
- **修改内容**:命名空间从 `com.ruoyi.ccdi.mapper.*` 改为 `com.ruoyi.info.collection.mapper.*`
|
||||
|
||||
### 4. 项目文档修改
|
||||
|
||||
- **涉及文件**:`doc/` 目录下约 135 个文件
|
||||
- **修改内容**:将 `ruoyi-ccdi` 模块引用改为 `ruoyi-info-collection`
|
||||
|
||||
## 不修改的内容
|
||||
|
||||
- 数据库表名 (`ccdi_*`)
|
||||
- 数据库名 (`ccdi`)
|
||||
- API URL 路径 (`/ccdi/*`)
|
||||
- 权限标识 (`ccdi:*:*`)
|
||||
- 前端 API 文件和视图目录
|
||||
- 菜单配置数据
|
||||
|
||||
## 执行步骤
|
||||
|
||||
1. 重命名模块目录 `ruoyi-ccdi/` → `ruoyi-info-collection/`
|
||||
2. 修改 Maven 配置文件
|
||||
3. 批量修改 Java 包名
|
||||
4. 修改 MyBatis XML 命名空间
|
||||
5. 更新项目文档
|
||||
6. 验证编译 `mvn clean compile`
|
||||
|
||||
## 风险评估
|
||||
|
||||
- **风险等级**:中
|
||||
- **主要风险**:包名修改涉及大量文件,可能遗漏
|
||||
- **缓解措施**:
|
||||
- 使用 IDE 的重构功能
|
||||
- 编译验证确保无遗漏
|
||||
- 执行单元测试
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. Maven 编译成功 (`mvn clean compile`)
|
||||
2. 所有 Java 文件包名正确
|
||||
3. MyBatis XML 命名空间正确
|
||||
4. 文档中模块名称已更新
|
||||
|
||||
---
|
||||
|
||||
**设计日期**:2026-02-24
|
||||
**设计状态**:已批准
|
||||
@@ -1,331 +0,0 @@
|
||||
# ruoyi-ccdi 模块重命名实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 将 ruoyi-ccdi 模块重命名为 ruoyi-info-collection,同时将 Java 包名从 com.ruoyi.ccdi 改为 com.ruoyi.info.collection
|
||||
|
||||
**Architecture:** Maven 模块重命名 + Java 包结构重组 + MyBatis XML 命名空间更新。保留数据库表名、API URL、权限标识和前端文件中的 ccdi 前缀不变。
|
||||
|
||||
**Tech Stack:** Maven, Java 17, MyBatis Plus, Spring Boot 3
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 重命名模块目录
|
||||
|
||||
**Files:**
|
||||
- Rename: `ruoyi-ccdi/` → `ruoyi-info-collection/`
|
||||
|
||||
**Step 1: 使用 git mv 重命名目录**
|
||||
|
||||
```bash
|
||||
git mv ruoyi-ccdi ruoyi-info-collection
|
||||
```
|
||||
|
||||
**Step 2: 验证目录已重命名**
|
||||
|
||||
Run: `ls -la | grep ruoyi-info-collection`
|
||||
Expected: 显示 `ruoyi-info-collection` 目录
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 修改根 pom.xml 模块声明
|
||||
|
||||
**Files:**
|
||||
- Modify: `pom.xml`
|
||||
|
||||
**Step 1: 修改 module 声明**
|
||||
|
||||
找到 `<module>ruoyi-ccdi</module>` 并修改为:
|
||||
|
||||
```xml
|
||||
<module>ruoyi-info-collection</module>
|
||||
```
|
||||
|
||||
**Step 2: 修改 dependencyManagement 中的 artifactId**
|
||||
|
||||
找到 ruoyi-ccdi 的依赖声明并修改为:
|
||||
|
||||
```xml
|
||||
<!-- 信息采集模块-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-info-collection</artifactId>
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Step 3: 验证修改**
|
||||
|
||||
Run: `grep -n "ruoyi-info-collection" pom.xml`
|
||||
Expected: 显示 2 处匹配(module 和 dependency)
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 修改 ruoyi-info-collection 模块 pom.xml
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-info-collection/pom.xml`
|
||||
|
||||
**Step 1: 修改 artifactId 和 description**
|
||||
|
||||
```xml
|
||||
<artifactId>ruoyi-info-collection</artifactId>
|
||||
<description>信息采集模块</description>
|
||||
```
|
||||
|
||||
**Step 2: 验证修改**
|
||||
|
||||
Run: `grep -n "artifactId" ruoyi-info-collection/pom.xml | head -1`
|
||||
Expected: `<artifactId>ruoyi-info-collection</artifactId>`
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 修改 ruoyi-admin 的依赖声明
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-admin/pom.xml`
|
||||
|
||||
**Step 1: 修改依赖 artifactId**
|
||||
|
||||
找到 ruoyi-ccdi 依赖并修改为:
|
||||
|
||||
```xml
|
||||
<!-- 信息采集模块-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-info-collection</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Step 2: 验证修改**
|
||||
|
||||
Run: `grep -n "ruoyi-info-collection" ruoyi-admin/pom.xml`
|
||||
Expected: 显示 1 处匹配
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 创建新的包目录结构
|
||||
|
||||
**Files:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/`
|
||||
- Create: `ruoyi-info-collection/src/main/resources/mapper/info/collection/`
|
||||
|
||||
**Step 1: 创建 Java 包目录**
|
||||
|
||||
```bash
|
||||
mkdir -p ruoyi-info-collection/src/main/java/com/ruoyi/info/collection
|
||||
```
|
||||
|
||||
**Step 2: 创建 MyBatis mapper 目录**
|
||||
|
||||
```bash
|
||||
mkdir -p ruoyi-info-collection/src/main/resources/mapper/info/collection
|
||||
```
|
||||
|
||||
**Step 3: 验证目录创建**
|
||||
|
||||
Run: `ls -la ruoyi-info-collection/src/main/java/com/ruoyi/info/`
|
||||
Expected: 显示 `collection` 目录
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 移动 Java 源码到新包结构
|
||||
|
||||
**Files:**
|
||||
- Move: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/*` → `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/`
|
||||
|
||||
**Step 1: 移动所有子目录**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection/src/main/java/com/ruoyi
|
||||
mv ccdi/* info/collection/
|
||||
```
|
||||
|
||||
**Step 2: 删除旧目录**
|
||||
|
||||
```bash
|
||||
rm -rf ccdi
|
||||
```
|
||||
|
||||
**Step 3: 验证新结构**
|
||||
|
||||
Run: `ls ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/`
|
||||
Expected: 显示 controller, domain, enums, mapper, service, utils 等目录
|
||||
|
||||
---
|
||||
|
||||
## Task 7: 批量修改 Java 文件包名声明
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/**/*.java` (约 100+ 文件)
|
||||
|
||||
**Step 1: 批量替换 package 声明**
|
||||
|
||||
```bash
|
||||
find ruoyi-info-collection/src/main/java -name "*.java" -exec sed -i 's/package com\.ruoyi\.ccdi/package com.ruoyi.info.collection/g' {} +
|
||||
```
|
||||
|
||||
**Step 2: 批量替换 import 语句**
|
||||
|
||||
```bash
|
||||
find ruoyi-info-collection/src/main/java -name "*.java" -exec sed -i 's/import com\.ruoyi\.ccdi/import com.ruoyi.info.collection/g' {} +
|
||||
```
|
||||
|
||||
**Step 3: 验证包名修改**
|
||||
|
||||
Run: `grep -r "package com.ruoyi.ccdi" ruoyi-info-collection/src/main/java/`
|
||||
Expected: 无输出(所有旧的包名已替换)
|
||||
|
||||
---
|
||||
|
||||
## Task 8: 移动 MyBatis XML 文件
|
||||
|
||||
**Files:**
|
||||
- Move: `ruoyi-info-collection/src/main/resources/mapper/ccdi/*` → `ruoyi-info-collection/src/main/resources/mapper/info/collection/`
|
||||
|
||||
**Step 1: 移动 XML 文件**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection/src/main/resources/mapper
|
||||
mkdir -p info/collection
|
||||
mv ccdi/* info/collection/
|
||||
rm -rf ccdi
|
||||
```
|
||||
|
||||
**Step 2: 验证文件移动**
|
||||
|
||||
Run: `ls ruoyi-info-collection/src/main/resources/mapper/info/collection/`
|
||||
Expected: 显示 11 个 XML 文件
|
||||
|
||||
---
|
||||
|
||||
## Task 9: 修改 MyBatis XML 命名空间
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-info-collection/src/main/resources/mapper/info/collection/*.xml` (11 文件)
|
||||
|
||||
**Step 1: 批量替换命名空间**
|
||||
|
||||
```bash
|
||||
find ruoyi-info-collection/src/main/resources/mapper -name "*.xml" -exec sed -i 's/com\.ruoyi\.ccdi/com.ruoyi.info.collection/g' {} +
|
||||
```
|
||||
|
||||
**Step 2: 验证命名空间修改**
|
||||
|
||||
Run: `grep -r "com.ruoyi.ccdi" ruoyi-info-collection/src/main/resources/mapper/`
|
||||
Expected: 无输出(所有旧的命名空间已替换)
|
||||
|
||||
---
|
||||
|
||||
## Task 10: 更新 CLAUDE.md 项目文档
|
||||
|
||||
**Files:**
|
||||
- Modify: `CLAUDE.md`
|
||||
|
||||
**Step 1: 更新模块架构描述**
|
||||
|
||||
将所有 `ruoyi-ccdi` 引用改为 `ruoyi-info-collection`,包括:
|
||||
- 模块架构图
|
||||
- 模块依赖关系
|
||||
- ruoyi-ccdi 业务模块描述
|
||||
- 重要文件路径
|
||||
|
||||
**Step 2: 验证修改**
|
||||
|
||||
Run: `grep "ruoyi-ccdi" CLAUDE.md`
|
||||
Expected: 无输出(所有引用已更新)
|
||||
|
||||
---
|
||||
|
||||
## Task 11: 更新 doc 目录下的文档
|
||||
|
||||
**Files:**
|
||||
- Modify: `doc/**/*.md` (约 135 文件)
|
||||
|
||||
**Step 1: 批量替换模块名引用**
|
||||
|
||||
```bash
|
||||
find doc -name "*.md" -exec sed -i 's/ruoyi-ccdi/ruoyi-info-collection/g' {} +
|
||||
```
|
||||
|
||||
**Step 2: 验证修改**
|
||||
|
||||
Run: `grep -r "ruoyi-ccdi" doc/`
|
||||
Expected: 仅在设计文档中保留历史记录
|
||||
|
||||
---
|
||||
|
||||
## Task 12: 验证 Maven 编译
|
||||
|
||||
**Files:**
|
||||
- None (验证步骤)
|
||||
|
||||
**Step 1: 清理并编译**
|
||||
|
||||
```bash
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
**Step 2: 如果编译失败,检查错误**
|
||||
|
||||
常见的编译错误:
|
||||
- 遗漏的 import 语句
|
||||
- 遗漏的包名声明
|
||||
- MyBatis XML 命名空间不匹配
|
||||
|
||||
---
|
||||
|
||||
## Task 13: 提交更改
|
||||
|
||||
**Files:**
|
||||
- None (Git 操作)
|
||||
|
||||
**Step 1: 查看更改**
|
||||
|
||||
```bash
|
||||
git status
|
||||
git diff --stat
|
||||
```
|
||||
|
||||
**Step 2: 添加所有更改**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
```
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
refactor: 重命名 ruoyi-ccdi 模块为 ruoyi-info-collection
|
||||
|
||||
- Maven 模块从 ruoyi-ccdi 重命名为 ruoyi-info-collection
|
||||
- Java 包名从 com.ruoyi.ccdi 改为 com.ruoyi.info.collection
|
||||
- MyBatis XML 命名空间同步更新
|
||||
- 保留数据库表名、API URL、权限标识中的 ccdi 前缀
|
||||
- 更新项目文档中的模块引用
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验收清单
|
||||
|
||||
- [ ] 模块目录已重命名为 `ruoyi-info-collection`
|
||||
- [ ] 所有 pom.xml 中的 artifactId 已更新
|
||||
- [ ] Java 包结构已重组为 `com.ruoyi.info.collection`
|
||||
- [ ] 所有 Java 文件的 package 声明已更新
|
||||
- [ ] 所有 Java 文件的 import 语句已更新
|
||||
- [ ] MyBatis XML 文件已移动到新目录
|
||||
- [ ] MyBatis XML 命名空间已更新
|
||||
- [ ] 项目文档已更新
|
||||
- [ ] Maven 编译成功
|
||||
- [ ] 更改已提交到 Git
|
||||
|
||||
---
|
||||
|
||||
**计划日期**: 2026-02-24
|
||||
**预计任务数**: 13
|
||||
@@ -1,943 +0,0 @@
|
||||
# 创建项目功能 - 后端实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**目标:** 实现创建项目功能的后端接口,包括数据库表、实体类、DTO/VO、Mapper、Service、Controller
|
||||
|
||||
**架构:** 基于若依框架 + MyBatis Plus,采用分层架构(Controller -> Service -> Mapper)
|
||||
|
||||
**技术栈:** Spring Boot 3.5.8, MyBatis Plus 3.5.10, MySQL 8.2.0, SpringDoc OpenAPI 2.8.14
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- MySQL 数据库已启动
|
||||
- 后端项目已启动
|
||||
- 已有 admin 账号和测试权限
|
||||
- 数据库连接配置正确
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 创建数据库表和字典数据
|
||||
|
||||
**文件:**
|
||||
- Create: `sql/ccdi_project.sql`
|
||||
|
||||
**Step 1: 创建 SQL 脚本文件**
|
||||
|
||||
创建文件 `sql/ccdi_project.sql`,内容如下:
|
||||
|
||||
```sql
|
||||
-- 创建项目表
|
||||
CREATE TABLE `ccdi_project` (
|
||||
`project_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '项目ID',
|
||||
`project_name` VARCHAR(100) NOT NULL COMMENT '项目名称',
|
||||
`project_desc` VARCHAR(500) DEFAULT NULL COMMENT '项目描述',
|
||||
`config_type` VARCHAR(20) NOT NULL DEFAULT 'default' COMMENT '配置方式:default-全局默认,custom-自定义',
|
||||
`project_status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档',
|
||||
`target_count` INT NOT NULL DEFAULT 0 COMMENT '目标人数',
|
||||
`high_risk_count` INT NOT NULL DEFAULT 0 COMMENT '高风险人数',
|
||||
`medium_risk_count` INT NOT NULL DEFAULT 0 COMMENT '中风险人数',
|
||||
`low_risk_count` INT NOT NULL DEFAULT 0 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 '更新时间',
|
||||
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`project_id`),
|
||||
INDEX `idx_project_name` (`project_name`),
|
||||
INDEX `idx_project_status` (`project_status`),
|
||||
INDEX `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='纪检初核项目表';
|
||||
|
||||
-- 插入项目状态字典
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
VALUES ('项目状态', 'ccdi_project_status', '0', 'admin', NOW(), '纪检初核项目状态');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
|
||||
VALUES
|
||||
(1, '进行中', '0', 'ccdi_project_status', '', 'primary', 'Y', '0', 'admin', NOW()),
|
||||
(2, '已完成', '1', 'ccdi_project_status', '', 'success', 'N', '0', 'admin', NOW()),
|
||||
(3, '已归档', '2', 'ccdi_project_status', '', 'info', 'N', '0', 'admin', NOW());
|
||||
|
||||
-- 插入配置方式字典
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
VALUES ('配置方式', 'ccdi_config_type', '0', 'admin', NOW(), '项目配置方式');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
|
||||
VALUES
|
||||
(1, '全局默认模型参数配置', 'default', 'ccdi_config_type', '', 'primary', 'Y', '0', 'admin', NOW()),
|
||||
(2, '自定义项目规则参数配置', 'custom', 'ccdi_config_type', '', 'warning', 'N', '0', 'admin', NOW());
|
||||
|
||||
-- 插入菜单权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('纪检初核管理', 0, 1, 'ccdi', NULL, 'M', '0', '0', '', 'monitor', 'admin', NOW());
|
||||
|
||||
SET @parent_id = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('项目管理', @parent_id, 1, 'project', 'ccdiProject/index', 'C', '0', '0', 'ccdi:project:list', 'project', 'admin', NOW());
|
||||
|
||||
SET @menu_id = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
|
||||
VALUES
|
||||
('创建项目', @menu_id, 1, 'F', '0', '0', 'ccdi:project:add', 'admin', NOW()),
|
||||
('编辑项目', @menu_id, 2, 'F', '0', '0', 'ccdi:project:edit', 'admin', NOW()),
|
||||
('删除项目', @menu_id, 3, 'F', '0', '0', 'ccdi:project:remove', 'admin', NOW()),
|
||||
('查询项目', @menu_id, 4, 'F', '0', '0', 'ccdi:project:query', 'admin', NOW());
|
||||
|
||||
-- 为管理员角色分配权限
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, menu_id FROM sys_menu WHERE perms LIKE 'ccdi:project:%' OR perms = 'ccdi:project:list';
|
||||
```
|
||||
|
||||
**Step 2: 执行 SQL 脚本**
|
||||
|
||||
运行命令连接数据库并执行脚本:
|
||||
|
||||
```bash
|
||||
mysql -h<host> -u<user> -p<password> ccdi < sql/ccdi_project.sql
|
||||
```
|
||||
|
||||
预期输出:无错误,表创建成功
|
||||
|
||||
**Step 3: 验证数据库表**
|
||||
|
||||
连接数据库验证表是否创建成功:
|
||||
|
||||
```bash
|
||||
mysql -h<host> -u<user> -p<password> -e "USE ccdi; SHOW TABLES LIKE 'ccdi_project'; DESC ccdi_project;"
|
||||
```
|
||||
|
||||
预期输出:显示 `ccdi_project` 表及其字段结构
|
||||
|
||||
**Step 4: 验证字典数据**
|
||||
|
||||
验证字典数据是否插入成功:
|
||||
|
||||
```bash
|
||||
mysql -h<host> -u<user> -p<password> -e "USE ccdi; SELECT * FROM sys_dict_type WHERE dict_type IN ('ccdi_project_status', 'ccdi_config_type'); SELECT * FROM sys_dict_data WHERE dict_type IN ('ccdi_project_status', 'ccdi_config_type');"
|
||||
```
|
||||
|
||||
预期输出:显示新插入的字典类型和数据
|
||||
|
||||
**Step 5: 提交代码**
|
||||
|
||||
```bash
|
||||
git add sql/ccdi_project.sql
|
||||
git commit -m "feat: 添加项目表和字典数据SQL脚本"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 创建实体类 CcdiProject
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiProject.java`
|
||||
|
||||
**Step 1: 创建实体类**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiProject.java`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.info.collection.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 纪检初核项目实体类
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
@TableName("ccdi_project")
|
||||
public class CcdiProject {
|
||||
/** 项目ID */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long projectId;
|
||||
|
||||
/** 项目名称 */
|
||||
private String projectName;
|
||||
|
||||
/** 项目描述 */
|
||||
private String projectDesc;
|
||||
|
||||
/** 配置方式:default-全局默认,custom-自定义 */
|
||||
private String configType;
|
||||
|
||||
/** 项目状态:0-进行中,1-已完成,2-已归档 */
|
||||
private String projectStatus;
|
||||
|
||||
/** 目标人数 */
|
||||
private Integer targetCount;
|
||||
|
||||
/** 高风险人数 */
|
||||
private Integer highRiskCount;
|
||||
|
||||
/** 中风险人数 */
|
||||
private Integer mediumRiskCount;
|
||||
|
||||
/** 低风险人数 */
|
||||
private Integer lowRiskCount;
|
||||
|
||||
/** 创建者 */
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
private Date updateTime;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiProject.java
|
||||
git commit -m "feat: 添加项目实体类"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 创建 DTO - CcdiProjectSaveDTO
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiProjectSaveDTO.java`
|
||||
|
||||
**Step 1: 创建 DTO 目录(如果不存在)**
|
||||
|
||||
```bash
|
||||
mkdir -p ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto
|
||||
```
|
||||
|
||||
**Step 2: 创建 DTO 类**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiProjectSaveDTO.java`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.info.collection.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
/**
|
||||
* 项目保存DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectSaveDTO {
|
||||
/** 项目名称(必填) */
|
||||
@NotBlank(message = "项目名称不能为空")
|
||||
@Length(max = 100, message = "项目名称长度不能超过100个字符")
|
||||
private String projectName;
|
||||
|
||||
/** 项目描述(可选) */
|
||||
@Length(max = 500, message = "项目描述长度不能超过500个字符")
|
||||
private String projectDesc;
|
||||
|
||||
/** 配置方式(必填):default-全局默认,custom-自定义 */
|
||||
@NotBlank(message = "配置方式不能为空")
|
||||
private String configType;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiProjectSaveDTO.java
|
||||
git commit -m "feat: 添加项目保存DTO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 创建 VO - CcdiProjectVO
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiProjectVO.java`
|
||||
|
||||
**Step 1: 创建 VO 目录(如果不存在)**
|
||||
|
||||
```bash
|
||||
mkdir -p ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo
|
||||
```
|
||||
|
||||
**Step 2: 创建 VO 类**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiProjectVO.java`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.info.collection.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 项目VO
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectVO {
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 项目名称 */
|
||||
private String projectName;
|
||||
|
||||
/** 项目描述 */
|
||||
private String projectDesc;
|
||||
|
||||
/** 配置方式 */
|
||||
private String configType;
|
||||
|
||||
/** 项目状态 */
|
||||
private String projectStatus;
|
||||
|
||||
/** 目标人数 */
|
||||
private Integer targetCount;
|
||||
|
||||
/** 高风险人数 */
|
||||
private Integer highRiskCount;
|
||||
|
||||
/** 中风险人数 */
|
||||
private Integer mediumRiskCount;
|
||||
|
||||
/** 低风险人数 */
|
||||
private Integer lowRiskCount;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 创建者 */
|
||||
private String createBy;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiProjectVO.java
|
||||
git commit -m "feat: 添加项目VO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 创建查询 DTO - CcdiProjectQueryDTO
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiProjectQueryDTO.java`
|
||||
|
||||
**Step 1: 创建查询 DTO 类**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiProjectQueryDTO.java`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.info.collection.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 项目查询DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectQueryDTO {
|
||||
/** 项目名称 */
|
||||
private String projectName;
|
||||
|
||||
/** 项目状态 */
|
||||
private String projectStatus;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiProjectQueryDTO.java
|
||||
git commit -m "feat: 添加项目查询DTO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 创建 Mapper 接口
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiProjectMapper.java`
|
||||
|
||||
**Step 1: 创建 Mapper 接口**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiProjectMapper.java`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.info.collection.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.info.collection.domain.CcdiProject;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiProjectQueryDTO;
|
||||
import com.ruoyi.info.collection.domain.vo.CcdiProjectVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 项目Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Mapper
|
||||
public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
|
||||
/**
|
||||
* 分页查询项目列表
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param queryDTO 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, @Param("queryDTO") CcdiProjectQueryDTO queryDTO);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiProjectMapper.java
|
||||
git commit -m "feat: 添加项目Mapper接口"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: 创建 Mapper XML 文件
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/resources/mapper/info/collection/CcdiProjectMapper.xml`
|
||||
|
||||
**Step 1: 创建 Mapper 目录(如果不存在)**
|
||||
|
||||
```bash
|
||||
mkdir -p ruoyi-info-collection/src/main/resources/mapper/info/collection
|
||||
```
|
||||
|
||||
**Step 2: 创建 XML 文件**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/resources/mapper/info/collection/CcdiProjectMapper.xml`:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiProjectMapper">
|
||||
|
||||
<resultMap id="ProjectVOResultMap" type="com.ruoyi.info.collection.domain.vo.CcdiProjectVO">
|
||||
<id property="projectId" column="project_id"/>
|
||||
<result property="projectName" column="project_name"/>
|
||||
<result property="projectDesc" column="project_desc"/>
|
||||
<result property="configType" column="config_type"/>
|
||||
<result property="projectStatus" column="project_status"/>
|
||||
<result property="targetCount" column="target_count"/>
|
||||
<result property="highRiskCount" column="high_risk_count"/>
|
||||
<result property="mediumRiskCount" column="medium_risk_count"/>
|
||||
<result property="lowRiskCount" column="low_risk_count"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 分页查询项目列表 -->
|
||||
<select id="selectProjectPage" resultMap="ProjectVOResultMap">
|
||||
SELECT
|
||||
project_id, project_name, project_desc, config_type,
|
||||
project_status, target_count, high_risk_count,
|
||||
medium_risk_count, low_risk_count, create_time, create_by
|
||||
FROM ccdi_project
|
||||
<where>
|
||||
<if test="queryDTO.projectName != null and queryDTO.projectName != ''">
|
||||
AND project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%')
|
||||
</if>
|
||||
<if test="queryDTO.projectStatus != null and queryDTO.projectStatus != ''">
|
||||
AND project_status = #{queryDTO.projectStatus}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
</mapper>
|
||||
```
|
||||
|
||||
**Step 3: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/resources/mapper/info/collection/CcdiProjectMapper.xml
|
||||
git commit -m "feat: 添加项目Mapper XML配置"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: 创建 Service 接口
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiProjectService.java`
|
||||
|
||||
**Step 1: 创建 Service 接口**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiProjectService.java`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiProjectQueryDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiProjectSaveDTO;
|
||||
import com.ruoyi.info.collection.domain.vo.CcdiProjectVO;
|
||||
|
||||
/**
|
||||
* 项目Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface ICcdiProjectService {
|
||||
/**
|
||||
* 创建项目
|
||||
*
|
||||
* @param dto 项目保存DTO
|
||||
* @return 项目VO
|
||||
*/
|
||||
CcdiProjectVO createProject(CcdiProjectSaveDTO dto);
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
*
|
||||
* @param dto 项目更新DTO
|
||||
* @return 项目VO
|
||||
*/
|
||||
CcdiProjectVO updateProject(CcdiProjectSaveDTO dto);
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean deleteProject(Long projectId);
|
||||
|
||||
/**
|
||||
* 查询项目详情
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 项目VO
|
||||
*/
|
||||
CcdiProjectVO getProjectById(Long projectId);
|
||||
|
||||
/**
|
||||
* 分页查询项目列表
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param queryDTO 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiProjectService.java
|
||||
git commit -m "feat: 添加项目Service接口"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: 创建 Service 实现类
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiProjectServiceImpl.java`
|
||||
|
||||
**Step 1: 创建 Service 实现目录(如果不存在)**
|
||||
|
||||
```bash
|
||||
mkdir -p ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl
|
||||
```
|
||||
|
||||
**Step 2: 创建 Service 实现类**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiProjectServiceImpl.java`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.info.collection.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.info.collection.domain.CcdiProject;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiProjectQueryDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiProjectSaveDTO;
|
||||
import com.ruoyi.info.collection.domain.vo.CcdiProjectVO;
|
||||
import com.ruoyi.info.collection.mapper.CcdiProjectMapper;
|
||||
import com.ruoyi.info.collection.service.ICcdiProjectService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 项目Service实现类
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
|
||||
@Resource
|
||||
private CcdiProjectMapper projectMapper;
|
||||
|
||||
@Override
|
||||
public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) {
|
||||
CcdiProject project = new CcdiProject();
|
||||
BeanUtils.copyProperties(dto, project);
|
||||
|
||||
// 设置默认值
|
||||
project.setProjectStatus("0"); // 进行中
|
||||
project.setTargetCount(0);
|
||||
project.setHighRiskCount(0);
|
||||
project.setMediumRiskCount(0);
|
||||
project.setLowRiskCount(0);
|
||||
|
||||
projectMapper.insert(project);
|
||||
|
||||
CcdiProjectVO vo = new CcdiProjectVO();
|
||||
BeanUtils.copyProperties(project, vo);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiProjectVO updateProject(CcdiProjectSaveDTO dto) {
|
||||
// TODO: 实现更新逻辑
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteProject(Long projectId) {
|
||||
return projectMapper.deleteById(projectId) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiProjectVO getProjectById(Long projectId) {
|
||||
CcdiProject project = projectMapper.selectById(projectId);
|
||||
if (project == null) {
|
||||
return null;
|
||||
}
|
||||
CcdiProjectVO vo = new CcdiProjectVO();
|
||||
BeanUtils.copyProperties(project, vo);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO) {
|
||||
return projectMapper.selectProjectPage(page, queryDTO);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiProjectServiceImpl.java
|
||||
git commit -m "feat: 添加项目Service实现类"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 10: 创建 Controller
|
||||
|
||||
**文件:**
|
||||
- Create: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiProjectController.java`
|
||||
|
||||
**Step 1: 创建 Controller 类**
|
||||
|
||||
创建文件 `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiProjectController.java`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.info.collection.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.PageDomain;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.page.TableSupport;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiProjectQueryDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiProjectSaveDTO;
|
||||
import com.ruoyi.info.collection.domain.vo.CcdiProjectVO;
|
||||
import com.ruoyi.info.collection.service.ICcdiProjectService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 纪检初核项目管理Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/ccdi/project")
|
||||
@Tag(name = "纪检初核项目管理")
|
||||
public class CcdiProjectController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
*/
|
||||
@PostMapping
|
||||
@Operation(summary = "创建项目")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:add')")
|
||||
public AjaxResult createProject(@Validated @RequestBody CcdiProjectSaveDTO dto) {
|
||||
CcdiProjectVO vo = projectService.createProject(dto);
|
||||
return AjaxResult.success("项目创建成功", vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
*/
|
||||
@PutMapping
|
||||
@Operation(summary = "更新项目")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
||||
public AjaxResult updateProject(@Validated @RequestBody CcdiProjectSaveDTO dto) {
|
||||
CcdiProjectVO vo = projectService.updateProject(dto);
|
||||
return AjaxResult.success("项目更新成功", vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
*/
|
||||
@DeleteMapping("/{projectId}")
|
||||
@Operation(summary = "删除项目")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:remove')")
|
||||
public AjaxResult deleteProject(@PathVariable Long projectId) {
|
||||
boolean success = projectService.deleteProject(projectId);
|
||||
return success ? AjaxResult.success("项目删除成功") : AjaxResult.error("项目删除失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询项目详情
|
||||
*/
|
||||
@GetMapping("/{projectId}")
|
||||
@Operation(summary = "查询项目详情")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public AjaxResult getProject(@PathVariable Long projectId) {
|
||||
CcdiProjectVO vo = projectService.getProjectById(projectId);
|
||||
return AjaxResult.success(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询项目列表(分页)
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "查询项目列表")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
|
||||
public TableDataInfo listProject(CcdiProjectQueryDTO queryDTO) {
|
||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||
Page<CcdiProjectVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
|
||||
Page<CcdiProjectVO> result = projectService.selectProjectPage(page, queryDTO);
|
||||
return getDataTable(result.getRecords(), result.getTotal());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证编译**
|
||||
|
||||
```bash
|
||||
cd ruoyi-info-collection && mvn clean compile
|
||||
```
|
||||
|
||||
预期输出:BUILD SUCCESS
|
||||
|
||||
**Step 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiProjectController.java
|
||||
git commit -m "feat: 添加项目Controller"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 11: 启动后端并测试接口
|
||||
|
||||
**Step 1: 启动后端服务**
|
||||
|
||||
```bash
|
||||
cd ruoyi-admin && mvn spring-boot:run
|
||||
```
|
||||
|
||||
预期输出:Spring Boot 启动成功日志,端口 8080
|
||||
|
||||
**Step 2: 获取测试 Token**
|
||||
|
||||
使用测试接口获取 Token:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/login/test?username=admin&password=admin123"
|
||||
```
|
||||
|
||||
预期输出:返回包含 token 的 JSON 响应
|
||||
|
||||
**Step 3: 测试创建项目接口**
|
||||
|
||||
使用 Token 测试创建项目接口:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/ccdi/project" \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"projectName": "测试项目1",
|
||||
"projectDesc": "这是测试项目描述",
|
||||
"configType": "default"
|
||||
}'
|
||||
```
|
||||
|
||||
预期输出:返回成功响应,包含项目 ID 和创建的项目信息
|
||||
|
||||
**Step 4: 测试查询项目列表接口**
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/project/list?pageNum=1&pageSize=10" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
预期输出:返回分页数据,包含刚才创建的项目
|
||||
|
||||
**Step 5: 使用 Swagger 测试**
|
||||
|
||||
访问 Swagger UI 进行接口测试:
|
||||
|
||||
```bash
|
||||
# 浏览器打开
|
||||
http://localhost:8080/swagger-ui/index.html
|
||||
```
|
||||
|
||||
预期结果:在 Swagger UI 中可以看到项目管理的所有接口,并进行测试
|
||||
|
||||
---
|
||||
|
||||
## Task 12: 提交最终代码
|
||||
|
||||
**Step 1: 检查所有文件**
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
预期输出:所有后端文件已提交
|
||||
|
||||
**Step 2: 推送到远程仓库**
|
||||
|
||||
```bash
|
||||
git push origin dev
|
||||
```
|
||||
|
||||
预期输出:推送成功
|
||||
|
||||
---
|
||||
|
||||
## 完成检查清单
|
||||
|
||||
- [ ] 数据库表 `ccdi_project` 创建成功
|
||||
- [ ] 字典数据 `ccdi_project_status` 和 `ccdi_config_type` 插入成功
|
||||
- [ ] 菜单权限配置成功
|
||||
- [ ] 实体类 `CcdiProject` 创建并编译通过
|
||||
- [ ] DTO `CcdiProjectSaveDTO` 创建并编译通过
|
||||
- [ ] VO `CcdiProjectVO` 创建并编译通过
|
||||
- [ ] Mapper 接口和 XML 创建并编译通过
|
||||
- [ ] Service 接口和实现类创建并编译通过
|
||||
- [ ] Controller 创建并编译通过
|
||||
- [ ] 后端服务启动成功
|
||||
- [ ] 创建项目接口测试通过
|
||||
- [ ] 查询项目列表接口测试通过
|
||||
- [ ] Swagger 文档显示正确
|
||||
- [ ] 所有代码已提交到 git
|
||||
|
||||
---
|
||||
|
||||
**后端实施计划完成!**
|
||||
@@ -1,902 +0,0 @@
|
||||
# 创建项目功能设计文档
|
||||
|
||||
**文档版本:** v1.0
|
||||
**创建日期:** 2026-02-26
|
||||
**设计人员:** Claude Code
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 功能描述
|
||||
|
||||
新增"创建项目"功能,允许用户在首页点击"新建项目"按钮后,通过弹窗表单创建新的纪检初核项目。
|
||||
|
||||
### 1.2 核心需求
|
||||
|
||||
- 弹窗包含3个字段:项目名称、项目描述、配置方式
|
||||
- 配置方式为单选按钮:全局默认模型参数配置 / 自定义项目规则参数配置
|
||||
- 项目列表展示项目名称和描述(上下排列)、状态、目标人数、预警人数、创建人、创建时间
|
||||
- 预警人数为各级别风险人数之和,悬停显示详细分布
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据库设计
|
||||
|
||||
### 2.1 表结构
|
||||
|
||||
**表名:** `ccdi_project`
|
||||
|
||||
**字段列表:**
|
||||
|
||||
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|------|--------|------|
|
||||
| project_id | BIGINT | - | 是 | 自增 | 项目ID(主键) |
|
||||
| project_name | VARCHAR | 100 | 是 | - | 项目名称 |
|
||||
| project_desc | VARCHAR | 500 | 否 | NULL | 项目描述 |
|
||||
| config_type | VARCHAR | 20 | 是 | 'default' | 配置方式:default-全局默认,custom-自定义 |
|
||||
| project_status | CHAR | 1 | 是 | '0' | 项目状态:0-进行中,1-已完成,2-已归档 |
|
||||
| target_count | INT | - | 是 | 0 | 目标人数 |
|
||||
| high_risk_count | INT | - | 是 | 0 | 高风险人数 |
|
||||
| medium_risk_count | INT | - | 是 | 0 | 中风险人数 |
|
||||
| low_risk_count | INT | - | 是 | 0 | 低风险人数 |
|
||||
| create_by | VARCHAR | 64 | 否 | '' | 创建者 |
|
||||
| create_time | DATETIME | - | 否 | CURRENT_TIMESTAMP | 创建时间 |
|
||||
| update_by | VARCHAR | 64 | 否 | '' | 更新者 |
|
||||
| update_time | DATETIME | - | 否 | CURRENT_TIMESTAMP | 更新时间 |
|
||||
| remark | VARCHAR | 500 | 否 | NULL | 备注 |
|
||||
|
||||
**索引设计:**
|
||||
- 主键索引:`PRIMARY KEY (project_id)`
|
||||
- 项目名称索引:`INDEX idx_project_name (project_name)`
|
||||
- 项目状态索引:`INDEX idx_project_status (project_status)`
|
||||
- 创建时间索引:`INDEX idx_create_time (create_time)`
|
||||
|
||||
### 2.2 SQL 脚本
|
||||
|
||||
```sql
|
||||
-- 创建项目表
|
||||
CREATE TABLE `ccdi_project` (
|
||||
`project_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '项目ID',
|
||||
`project_name` VARCHAR(100) NOT NULL COMMENT '项目名称',
|
||||
`project_desc` VARCHAR(500) DEFAULT NULL COMMENT '项目描述',
|
||||
`config_type` VARCHAR(20) NOT NULL DEFAULT 'default' COMMENT '配置方式:default-全局默认,custom-自定义',
|
||||
`project_status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档',
|
||||
`target_count` INT NOT NULL DEFAULT 0 COMMENT '目标人数',
|
||||
`high_risk_count` INT NOT NULL DEFAULT 0 COMMENT '高风险人数',
|
||||
`medium_risk_count` INT NOT NULL DEFAULT 0 COMMENT '中风险人数',
|
||||
`low_risk_count` INT NOT NULL DEFAULT 0 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 '更新时间',
|
||||
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`project_id`),
|
||||
INDEX `idx_project_name` (`project_name`),
|
||||
INDEX `idx_project_status` (`project_status`),
|
||||
INDEX `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='纪检初核项目表';
|
||||
|
||||
-- 插入项目状态字典
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
VALUES ('项目状态', 'ccdi_project_status', '0', 'admin', NOW(), '纪检初核项目状态');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
|
||||
VALUES
|
||||
(1, '进行中', '0', 'ccdi_project_status', '', 'primary', 'Y', '0', 'admin', NOW()),
|
||||
(2, '已完成', '1', 'ccdi_project_status', '', 'success', 'N', '0', 'admin', NOW()),
|
||||
(3, '已归档', '2', 'ccdi_project_status', '', 'info', 'N', '0', 'admin', NOW());
|
||||
|
||||
-- 插入配置方式字典
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
VALUES ('配置方式', 'ccdi_config_type', '0', 'admin', NOW(), '项目配置方式');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
|
||||
VALUES
|
||||
(1, '全局默认模型参数配置', 'default', 'ccdi_config_type', '', 'primary', 'Y', '0', 'admin', NOW()),
|
||||
(2, '自定义项目规则参数配置', 'custom', 'ccdi_config_type', '', 'warning', 'N', '0', 'admin', NOW());
|
||||
|
||||
-- 插入菜单权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('纪检初核管理', 0, 1, 'ccdi', NULL, 'M', '0', '0', '', 'monitor', 'admin', NOW());
|
||||
|
||||
SET @parent_id = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('项目管理', @parent_id, 1, 'project', 'ccdiProject/index', 'C', '0', '0', 'ccdi:project:list', 'project', 'admin', NOW());
|
||||
|
||||
SET @menu_id = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
|
||||
VALUES
|
||||
('创建项目', @menu_id, 1, 'F', '0', '0', 'ccdi:project:add', 'admin', NOW()),
|
||||
('编辑项目', @menu_id, 2, 'F', '0', '0', 'ccdi:project:edit', 'admin', NOW()),
|
||||
('删除项目', @menu_id, 3, 'F', '0', '0', 'ccdi:project:remove', 'admin', NOW()),
|
||||
('查询项目', @menu_id, 4, 'F', '0', '0', 'ccdi:project:query', 'admin', NOW());
|
||||
|
||||
-- 为管理员角色分配权限
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, menu_id FROM sys_menu WHERE perms LIKE 'ccdi:project:%' OR perms = 'ccdi:project:list';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端架构设计
|
||||
|
||||
### 3.1 实体类
|
||||
|
||||
**类名:** `CcdiProject`
|
||||
**位置:** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/`
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiProject {
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 项目名称 */
|
||||
private String projectName;
|
||||
|
||||
/** 项目描述 */
|
||||
private String projectDesc;
|
||||
|
||||
/** 配置方式:default-全局默认,custom-自定义 */
|
||||
private String configType;
|
||||
|
||||
/** 项目状态:0-进行中,1-已完成,2-已归档 */
|
||||
private String projectStatus;
|
||||
|
||||
/** 目标人数 */
|
||||
private Integer targetCount;
|
||||
|
||||
/** 高风险人数 */
|
||||
private Integer highRiskCount;
|
||||
|
||||
/** 中风险人数 */
|
||||
private Integer mediumRiskCount;
|
||||
|
||||
/** 低风险人数 */
|
||||
private Integer lowRiskCount;
|
||||
|
||||
/** 创建者 */
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
private Date updateTime;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 DTO 设计
|
||||
|
||||
**类名:** `CcdiProjectSaveDTO`
|
||||
**位置:** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/`
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiProjectSaveDTO {
|
||||
/** 项目名称(必填) */
|
||||
@NotBlank(message = "项目名称不能为空")
|
||||
@Length(max = 100, message = "项目名称长度不能超过100个字符")
|
||||
private String projectName;
|
||||
|
||||
/** 项目描述(可选) */
|
||||
@Length(max = 500, message = "项目描述长度不能超过500个字符")
|
||||
private String projectDesc;
|
||||
|
||||
/** 配置方式(必填):default-全局默认,custom-自定义 */
|
||||
@NotBlank(message = "配置方式不能为空")
|
||||
private String configType;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 VO 设计
|
||||
|
||||
**类名:** `CcdiProjectVO`
|
||||
**位置:** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/`
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiProjectVO {
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 项目名称 */
|
||||
private String projectName;
|
||||
|
||||
/** 项目描述 */
|
||||
private String projectDesc;
|
||||
|
||||
/** 配置方式 */
|
||||
private String configType;
|
||||
|
||||
/** 项目状态 */
|
||||
private String projectStatus;
|
||||
|
||||
/** 目标人数 */
|
||||
private Integer targetCount;
|
||||
|
||||
/** 高风险人数 */
|
||||
private Integer highRiskCount;
|
||||
|
||||
/** 中风险人数 */
|
||||
private Integer mediumRiskCount;
|
||||
|
||||
/** 低风险人数 */
|
||||
private Integer lowRiskCount;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 创建者 */
|
||||
private String createBy;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Controller 接口
|
||||
|
||||
**类名:** `CcdiProjectController`
|
||||
**位置:** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/controller/`
|
||||
|
||||
**接口列表:**
|
||||
|
||||
| 接口路径 | 方法 | 说明 | 权限标识 |
|
||||
|---------|------|------|---------|
|
||||
| `/ccdi/project` | POST | 创建项目 | `ccdi:project:add` |
|
||||
| `/ccdi/project` | PUT | 更新项目 | `ccdi:project:edit` |
|
||||
| `/ccdi/project/{projectId}` | DELETE | 删除项目 | `ccdi:project:remove` |
|
||||
| `/ccdi/project/{projectId}` | GET | 查询项目详情 | `ccdi:project:query` |
|
||||
| `/ccdi/project/list` | GET | 查询项目列表(分页) | `ccdi:project:list` |
|
||||
|
||||
**示例代码:**
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/ccdi/project")
|
||||
@Api(tags = "纪检初核项目管理")
|
||||
public class CcdiProjectController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("创建项目")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:add')")
|
||||
public AjaxResult createProject(@Validated @RequestBody CcdiProjectSaveDTO dto) {
|
||||
CcdiProjectVO vo = projectService.createProject(dto);
|
||||
return AjaxResult.success("项目创建成功", vo);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiOperation("查询项目列表")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
|
||||
public TableDataInfo listProject(CcdiProjectQueryDTO queryDTO) {
|
||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||
Page<CcdiProjectVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
|
||||
Page<CcdiProjectVO> result = projectService.selectProjectPage(page, queryDTO);
|
||||
return getDataTable(result.getRecords(), result.getTotal());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 Service 层
|
||||
|
||||
**接口名:** `ICcdiProjectService`
|
||||
**实现类名:** `CcdiProjectServiceImpl`
|
||||
**位置:** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/`
|
||||
|
||||
```java
|
||||
public interface ICcdiProjectService {
|
||||
/**
|
||||
* 创建项目
|
||||
* @param dto 项目保存DTO
|
||||
* @return 项目VO
|
||||
*/
|
||||
CcdiProjectVO createProject(CcdiProjectSaveDTO dto);
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
* @param dto 项目更新DTO
|
||||
* @return 项目VO
|
||||
*/
|
||||
CcdiProjectVO updateProject(CcdiProjectSaveDTO dto);
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
* @param projectId 项目ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean deleteProject(Long projectId);
|
||||
|
||||
/**
|
||||
* 查询项目详情
|
||||
* @param projectId 项目ID
|
||||
* @return 项目VO
|
||||
*/
|
||||
CcdiProjectVO getProjectById(Long projectId);
|
||||
|
||||
/**
|
||||
* 分页查询项目列表
|
||||
* @param page 分页对象
|
||||
* @param queryDTO 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO);
|
||||
}
|
||||
```
|
||||
|
||||
**实现类示例:**
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
|
||||
@Resource
|
||||
private CcdiProjectMapper projectMapper;
|
||||
|
||||
@Override
|
||||
public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) {
|
||||
CcdiProject project = new CcdiProject();
|
||||
BeanUtils.copyProperties(dto, project);
|
||||
|
||||
// 设置默认值
|
||||
project.setProjectStatus("0"); // 进行中
|
||||
project.setTargetCount(0);
|
||||
project.setHighRiskCount(0);
|
||||
project.setMediumRiskCount(0);
|
||||
project.setLowRiskCount(0);
|
||||
|
||||
projectMapper.insert(project);
|
||||
|
||||
CcdiProjectVO vo = new CcdiProjectVO();
|
||||
BeanUtils.copyProperties(project, vo);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 Mapper 层
|
||||
|
||||
**接口名:** `CcdiProjectMapper`
|
||||
**位置:** `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/`
|
||||
|
||||
```java
|
||||
public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
|
||||
/**
|
||||
* 分页查询项目列表
|
||||
* @param page 分页对象
|
||||
* @param queryDTO 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, @Param("queryDTO") CcdiProjectQueryDTO queryDTO);
|
||||
}
|
||||
```
|
||||
|
||||
**XML 文件:** `CcdiProjectMapper.xml`
|
||||
**位置:** `ruoyi-info-collection/src/main/resources/mapper/info/collection/`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiProjectMapper">
|
||||
|
||||
<resultMap id="ProjectVOResultMap" type="com.ruoyi.info.collection.domain.vo.CcdiProjectVO">
|
||||
<id property="projectId" column="project_id"/>
|
||||
<result property="projectName" column="project_name"/>
|
||||
<result property="projectDesc" column="project_desc"/>
|
||||
<result property="configType" column="config_type"/>
|
||||
<result property="projectStatus" column="project_status"/>
|
||||
<result property="targetCount" column="target_count"/>
|
||||
<result property="highRiskCount" column="high_risk_count"/>
|
||||
<result property="mediumRiskCount" column="medium_risk_count"/>
|
||||
<result property="lowRiskCount" column="low_risk_count"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 分页查询项目列表 -->
|
||||
<select id="selectProjectPage" resultMap="ProjectVOResultMap">
|
||||
SELECT
|
||||
project_id, project_name, project_desc, config_type,
|
||||
project_status, target_count, high_risk_count,
|
||||
medium_risk_count, low_risk_count, create_time, create_by
|
||||
FROM ccdi_project
|
||||
<where>
|
||||
<if test="queryDTO.projectName != null and queryDTO.projectName != ''">
|
||||
AND project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%')
|
||||
</if>
|
||||
<if test="queryDTO.projectStatus != null and queryDTO.projectStatus != ''">
|
||||
AND project_status = #{queryDTO.projectStatus}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
</mapper>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端架构设计
|
||||
|
||||
### 4.1 组件修改
|
||||
|
||||
**组件名称:** `AddProjectDialog.vue`
|
||||
**位置:** `ruoyi-ui/src/views/ccdiProject/components/`
|
||||
|
||||
**修改内容:**
|
||||
|
||||
1. **简化表单字段**:只保留项目名称、项目描述、配置方式3个字段
|
||||
2. **移除字段**:目标人员、开始日期、结束日期、目标人数、高级设置
|
||||
3. **默认值**:配置方式默认为 `'default'`
|
||||
|
||||
**关键代码:**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
:title="title"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form
|
||||
ref="projectForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-position="right"
|
||||
>
|
||||
<!-- 项目名称 -->
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input
|
||||
v-model="formData.projectName"
|
||||
placeholder="请输入项目名称"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 项目描述 -->
|
||||
<el-form-item label="项目描述" prop="projectDesc">
|
||||
<el-input
|
||||
v-model="formData.projectDesc"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入项目描述"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 配置方式 -->
|
||||
<el-form-item label="配置方式" prop="configType">
|
||||
<el-radio-group v-model="formData.configType">
|
||||
<el-radio label="default">全局默认模型参数配置</el-radio>
|
||||
<el-radio label="custom">自定义项目规则参数配置</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">
|
||||
创建项目
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createProject } from '@/api/ccdiProject'
|
||||
|
||||
export default {
|
||||
name: 'AddProjectDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '新建项目'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
submitting: false,
|
||||
formData: {
|
||||
projectName: '',
|
||||
projectDesc: '',
|
||||
configType: 'default'
|
||||
},
|
||||
rules: {
|
||||
projectName: [
|
||||
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
||||
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
configType: [
|
||||
{ required: true, message: '请选择配置方式', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
if (!val) {
|
||||
this.handleClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
this.$refs.projectForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.submitting = true
|
||||
createProject(this.formData).then(response => {
|
||||
this.$message.success('项目创建成功')
|
||||
this.submitting = false
|
||||
this.$emit('submit', response.data)
|
||||
this.handleClose()
|
||||
}).catch(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
this.$refs.projectForm.resetFields()
|
||||
this.formData = {
|
||||
projectName: '',
|
||||
projectDesc: '',
|
||||
configType: 'default'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
|
||||
.el-button + .el-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-radio-group) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.el-radio {
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 4.2 项目列表表格
|
||||
|
||||
**组件名称:** `ProjectTable.vue`
|
||||
**位置:** `ruoyi-ui/src/views/ccdiProject/components/`
|
||||
|
||||
**关键特性:**
|
||||
|
||||
1. **项目名称和描述上下排列**:同一单元格内,项目名称加粗深色,项目描述常规浅色
|
||||
2. **预警人数悬停提示**:显示高、中、低风险人数详细分布
|
||||
3. **预警人数样式**:根据风险级别自动调整颜色
|
||||
|
||||
**表格列配置:**
|
||||
|
||||
| 列名 | 宽度 | 对齐方式 | 说明 |
|
||||
|------|------|---------|------|
|
||||
| 项目名称 | 最小300px | 左对齐 | 包含项目名称(上)+项目描述(下),自适应 |
|
||||
| 项目状态 | 100px | 居中对齐 | 固定宽度 |
|
||||
| 目标人数 | 100px | 居中对齐 | 固定宽度 |
|
||||
| 预警人数 | 120px | 居中对齐 | 悬停显示详细风险分布 |
|
||||
| 创建人 | 120px | 居中对齐 | 固定宽度 |
|
||||
| 创建时间 | 160px | 居中对齐 | 格式化显示 |
|
||||
| 操作 | 280px | 居中对齐 | 固定在右侧 |
|
||||
|
||||
**关键代码:**
|
||||
|
||||
```vue
|
||||
<!-- 项目名称(含描述) -->
|
||||
<el-table-column label="项目名称" min-width="300" align="left">
|
||||
<template slot-scope="scope">
|
||||
<div class="project-info-cell">
|
||||
<div class="project-name">{{ scope.row.projectName }}</div>
|
||||
<div class="project-desc">{{ scope.row.projectDesc || '暂无描述' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 预警人数(带悬停详情) -->
|
||||
<el-table-column label="预警人数" width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<div slot="content">
|
||||
<div style="padding: 8px;">
|
||||
<div style="margin-bottom: 8px; font-weight: bold; color: #303133;">
|
||||
风险人数统计
|
||||
</div>
|
||||
<div style="margin-bottom: 6px;">
|
||||
<span style="color: #f56c6c;">● 高风险:</span>
|
||||
<span style="font-weight: bold;">{{ scope.row.highRiskCount }} 人</span>
|
||||
</div>
|
||||
<div style="margin-bottom: 6px;">
|
||||
<span style="color: #e6a23c;">● 中风险:</span>
|
||||
<span style="font-weight: bold;">{{ scope.row.mediumRiskCount }} 人</span>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: #909399;">● 低风险:</span>
|
||||
<span style="font-weight: bold;">{{ scope.row.lowRiskCount }} 人</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="warning-count-wrapper">
|
||||
<span :class="getWarningClass(scope.row)" style="cursor: pointer;">
|
||||
{{ scope.row.highRiskCount + scope.row.mediumRiskCount + scope.row.lowRiskCount }}
|
||||
</span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
**样式代码:**
|
||||
|
||||
```scss
|
||||
.project-info-cell {
|
||||
padding: 8px 0;
|
||||
line-height: 1.5;
|
||||
|
||||
.project-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.project-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
```
|
||||
|
||||
**预警人数样式规则:**
|
||||
|
||||
- 高风险 > 0:红色加粗
|
||||
- 中风险 > 0:橙色加粗
|
||||
- 低风险 > 0:灰色
|
||||
- 无预警:普通黑色
|
||||
|
||||
### 4.3 API 接口
|
||||
|
||||
**文件名:** `ccdiProject.js`
|
||||
**位置:** `ruoyi-ui/src/api/`
|
||||
|
||||
```javascript
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 创建初核项目
|
||||
export function createProject(data) {
|
||||
return request({
|
||||
url: '/ccdi/project',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 查询初核项目列表(分页)
|
||||
export function listProject(query) {
|
||||
return request({
|
||||
url: '/ccdi/project/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询初核项目详细
|
||||
export function getProject(projectId) {
|
||||
return request({
|
||||
url: '/ccdi/project/' + projectId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 修改初核项目
|
||||
export function updateProject(data) {
|
||||
return request({
|
||||
url: '/ccdi/project',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除初核项目
|
||||
export function delProject(projectId) {
|
||||
return request({
|
||||
url: '/ccdi/project/' + projectId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 实施计划
|
||||
|
||||
### 5.1 实施步骤
|
||||
|
||||
#### 阶段一:数据库与后端开发(预计 2.5 小时)
|
||||
|
||||
1. **创建数据库表**(15 分钟)
|
||||
- 执行 `ccdi_project` 表创建脚本
|
||||
- 插入字典数据和菜单数据
|
||||
|
||||
2. **后端开发**(2 小时)
|
||||
- 创建实体类 `CcdiProject`
|
||||
- 创建 DTO `CcdiProjectSaveDTO`
|
||||
- 创建 VO `CcdiProjectVO`
|
||||
- 创建 Mapper 接口和 XML
|
||||
- 创建 Service 接口和实现类
|
||||
- 创建 Controller 接口
|
||||
- 添加 Swagger 注解
|
||||
|
||||
3. **后端测试**(30 分钟)
|
||||
- 使用 Swagger 测试创建项目接口
|
||||
- 使用 Swagger 测试查询项目列表接口
|
||||
- 验证数据字典显示
|
||||
|
||||
#### 阶段二:前端开发(预计 2.5 小时)
|
||||
|
||||
4. **前端组件开发**(1.5 小时)
|
||||
- 修改 `AddProjectDialog.vue` 组件
|
||||
- 修改 `ProjectTable.vue` 组件
|
||||
- 更新 API 接口文件 `ccdiProject.js`
|
||||
- 修改父组件调用逻辑
|
||||
|
||||
5. **前端联调**(1 小时)
|
||||
- 测试创建项目功能
|
||||
- 测试项目列表显示
|
||||
- 测试预警人数悬停提示
|
||||
- 测试字典数据展示
|
||||
|
||||
---
|
||||
|
||||
## 6. 注意事项
|
||||
|
||||
### 6.1 数据完整性
|
||||
|
||||
- 创建项目时,`project_status` 默认为 `'0'`(进行中)
|
||||
- 创建项目时,风险计数字段默认为 `0`
|
||||
- `config_type` 默认为 `'default'`
|
||||
- 项目名称和描述不能为空
|
||||
|
||||
### 6.2 权限控制
|
||||
|
||||
- 创建项目需要 `ccdi:project:add` 权限
|
||||
- 编辑项目需要 `ccdi:project:edit` 权限
|
||||
- 删除项目需要 `ccdi:project:remove` 权限
|
||||
- 查询项目需要 `ccdi:project:list` 权限
|
||||
|
||||
### 6.3 前端验证
|
||||
|
||||
- 项目名称:必填,2-100字符
|
||||
- 项目描述:可选,最多500字符
|
||||
- 配置方式:必填,只能选择 `default` 或 `custom`
|
||||
|
||||
### 6.4 后端验证
|
||||
|
||||
- 使用 `@Validated` 注解进行参数校验
|
||||
- 项目名称长度校验
|
||||
- 配置方式枚举值校验
|
||||
|
||||
### 6.5 性能优化
|
||||
|
||||
- 项目列表分页查询
|
||||
- 项目名称和状态字段添加索引
|
||||
- 字典数据使用缓存
|
||||
|
||||
### 6.6 用户体验
|
||||
|
||||
- 提交按钮显示 loading 状态
|
||||
- 创建成功后自动刷新列表
|
||||
- 预警人数悬停提示详细信息
|
||||
- 项目名称和描述上下排列,层次分明
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试清单
|
||||
|
||||
### 7.1 后端测试
|
||||
|
||||
- [ ] 创建项目成功(必填字段)
|
||||
- [ ] 创建项目失败(缺少必填字段)
|
||||
- [ ] 创建项目失败(字段长度超限)
|
||||
- [ ] 查询项目列表(分页)
|
||||
- [ ] 查询项目详情
|
||||
- [ ] 更新项目
|
||||
- [ ] 删除项目
|
||||
- [ ] 字典数据正确返回
|
||||
- [ ] 权限验证正确
|
||||
|
||||
### 7.2 前端测试
|
||||
|
||||
- [ ] 弹窗显示正确(3个字段)
|
||||
- [ ] 表单验证正常(必填项)
|
||||
- [ ] 表单验证正常(长度限制)
|
||||
- [ ] 项目名称和描述上下排列
|
||||
- [ ] 项目名称样式正确(加粗深色)
|
||||
- [ ] 项目描述样式正确(常规浅色)
|
||||
- [ ] 项目状态标签正确显示
|
||||
- [ ] 预警人数计算正确(高+中+低)
|
||||
- [ ] 预警人数悬停提示显示
|
||||
- [ ] 预警人数颜色根据风险级别变化
|
||||
- [ ] 创建人正确显示
|
||||
- [ ] 创建时间格式化正确
|
||||
- [ ] 操作按钮权限控制
|
||||
- [ ] 提交按钮 loading 状态
|
||||
- [ ] 创建成功后列表刷新
|
||||
|
||||
---
|
||||
|
||||
## 8. 附录
|
||||
|
||||
### 8.1 参考文档
|
||||
|
||||
- 若依框架官方文档
|
||||
- Element UI 组件库文档
|
||||
- MyBatis Plus 官方文档
|
||||
|
||||
### 8.2 相关文件
|
||||
|
||||
- 数据库脚本:`sql/ccdi_project.sql`
|
||||
- 设计截图:`doc/创建项目功能/ScreenShot_2026-02-26_153149_900.png`
|
||||
- 设计截图:`doc/创建项目功能/ScreenShot_2026-02-26_162233_965.png`
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
@@ -1,881 +0,0 @@
|
||||
# 创建项目功能 - 前端实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**目标:** 实现创建项目功能的前端界面,包括弹窗表单、项目列表展示、API调用
|
||||
|
||||
**架构:** 基于 Vue 2.6.12 + Element UI 2.15.14,采用组件化开发
|
||||
|
||||
**技术栈:** Vue.js 2.6.12, Element UI 2.15.14, Axios 0.28.1
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 后端接口已部署并测试通过
|
||||
- 前端项目依赖已安装
|
||||
- 已有测试账号(admin/admin123)
|
||||
- 后端服务运行在 http://localhost:8080
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 更新 API 接口文件
|
||||
|
||||
**文件:**
|
||||
- Modify: `ruoyi-ui/src/api/ccdiProject.js`
|
||||
|
||||
**Step 1: 备份原文件**
|
||||
|
||||
```bash
|
||||
cp ruoyi-ui/src/api/ccdiProject.js ruoyi-ui/src/api/ccdiProject.js.bak
|
||||
```
|
||||
|
||||
**Step 2: 修改 API 文件**
|
||||
|
||||
将 `ruoyi-ui/src/api/ccdiProject.js` 修改为以下内容:
|
||||
|
||||
```javascript
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 创建初核项目
|
||||
export function createProject(data) {
|
||||
return request({
|
||||
url: '/ccdi/project',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 查询初核项目列表(分页)
|
||||
export function listProject(query) {
|
||||
return request({
|
||||
url: '/ccdi/project/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询初核项目详细
|
||||
export function getProject(projectId) {
|
||||
return request({
|
||||
url: '/ccdi/project/' + projectId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 修改初核项目
|
||||
export function updateProject(data) {
|
||||
return request({
|
||||
url: '/ccdi/project',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除初核项目
|
||||
export function delProject(projectId) {
|
||||
return request({
|
||||
url: '/ccdi/project/' + projectId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// Mock数据:获取项目列表(保留用于测试)
|
||||
export function getMockProjectList() {
|
||||
return Promise.resolve({
|
||||
code: 200,
|
||||
total: 3,
|
||||
rows: [
|
||||
{
|
||||
projectId: 1,
|
||||
projectName: '2024年Q1初核',
|
||||
projectDesc: '2024年第一季度纪检初核排查工作',
|
||||
createTime: '2024-01-01',
|
||||
projectStatus: '0',
|
||||
configType: 'default',
|
||||
targetCount: 500,
|
||||
highRiskCount: 5,
|
||||
mediumRiskCount: 10,
|
||||
lowRiskCount: 0
|
||||
},
|
||||
{
|
||||
projectId: 2,
|
||||
projectName: '2023年Q4初核',
|
||||
projectDesc: '2023年第四季度纪检初核排查工作',
|
||||
createTime: '2023-10-01',
|
||||
projectStatus: '1',
|
||||
configType: 'custom',
|
||||
targetCount: 480,
|
||||
highRiskCount: 8,
|
||||
mediumRiskCount: 15,
|
||||
lowRiskCount: 0
|
||||
},
|
||||
{
|
||||
projectId: 3,
|
||||
projectName: '2023年Q3初核',
|
||||
projectDesc: '2023年第三季度纪检初核排查工作',
|
||||
createTime: '2023-07-01',
|
||||
projectStatus: '2',
|
||||
configType: 'default',
|
||||
targetCount: 450,
|
||||
highRiskCount: 0,
|
||||
mediumRiskCount: 18,
|
||||
lowRiskCount: 5
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 验证语法**
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui && npm run lint -- --fix src/api/ccdiProject.js
|
||||
```
|
||||
|
||||
预期输出:无 ESLint 错误
|
||||
|
||||
**Step 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/api/ccdiProject.js
|
||||
git commit -m "feat: 更新项目API接口,添加创建项目接口"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 修改 AddProjectDialog 组件
|
||||
|
||||
**文件:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue`
|
||||
|
||||
**Step 1: 备份原文件**
|
||||
|
||||
```bash
|
||||
cp ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue.bak
|
||||
```
|
||||
|
||||
**Step 2: 重写组件**
|
||||
|
||||
将 `ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue` 重写为以下内容:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
:title="title"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form
|
||||
ref="projectForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-position="right"
|
||||
>
|
||||
<!-- 项目名称 -->
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input
|
||||
v-model="formData.projectName"
|
||||
placeholder="请输入项目名称"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 项目描述 -->
|
||||
<el-form-item label="项目描述" prop="projectDesc">
|
||||
<el-input
|
||||
v-model="formData.projectDesc"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入项目描述"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 配置方式 -->
|
||||
<el-form-item label="配置方式" prop="configType">
|
||||
<el-radio-group v-model="formData.configType">
|
||||
<el-radio label="default">全局默认模型参数配置</el-radio>
|
||||
<el-radio label="custom">自定义项目规则参数配置</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">
|
||||
创建项目
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createProject } from '@/api/ccdiProject'
|
||||
|
||||
export default {
|
||||
name: 'AddProjectDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '新建项目'
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
submitting: false,
|
||||
formData: {
|
||||
projectName: '',
|
||||
projectDesc: '',
|
||||
configType: 'default'
|
||||
},
|
||||
rules: {
|
||||
projectName: [
|
||||
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
||||
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
configType: [
|
||||
{ required: true, message: '请选择配置方式', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
if (!val) {
|
||||
this.handleClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
form: {
|
||||
handler(newVal) {
|
||||
if (newVal && Object.keys(newVal).length > 0) {
|
||||
this.formData = { ...this.formData, ...newVal }
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.projectForm) {
|
||||
this.$refs.projectForm.clearValidate()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 提交表单 */
|
||||
handleSubmit() {
|
||||
this.$refs.projectForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.submitting = true
|
||||
createProject(this.formData).then(response => {
|
||||
this.$message.success('项目创建成功')
|
||||
this.submitting = false
|
||||
this.$emit('submit', response.data)
|
||||
this.handleClose()
|
||||
}).catch(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 关闭对话框 */
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
this.$refs.projectForm.resetFields()
|
||||
this.formData = {
|
||||
projectName: '',
|
||||
projectDesc: '',
|
||||
configType: 'default'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
|
||||
.el-button + .el-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-radio-group) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.el-radio {
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**Step 3: 验证语法**
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui && npm run lint -- --fix src/views/ccdiProject/components/AddProjectDialog.vue
|
||||
```
|
||||
|
||||
预期输出:无 ESLint 错误
|
||||
|
||||
**Step 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/AddProjectDialog.vue
|
||||
git commit -m "feat: 简化项目创建弹窗,只保留3个核心字段"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 修改 ProjectTable 组件
|
||||
|
||||
**文件:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 备份原文件**
|
||||
|
||||
```bash
|
||||
cp ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue.bak
|
||||
```
|
||||
|
||||
**Step 2: 重写组件**
|
||||
|
||||
将 `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue` 重写为以下内容:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="project-table-container">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
border
|
||||
style="width: 100%"
|
||||
>
|
||||
<!-- 项目名称(含描述) -->
|
||||
<el-table-column
|
||||
label="项目名称"
|
||||
min-width="300"
|
||||
align="left"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<div class="project-info-cell">
|
||||
<div class="project-name">{{ scope.row.projectName }}</div>
|
||||
<div class="project-desc">{{ scope.row.projectDesc || '暂无描述' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 项目状态 -->
|
||||
<el-table-column
|
||||
prop="projectStatus"
|
||||
label="项目状态"
|
||||
width="100"
|
||||
align="center"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row.projectStatus)">
|
||||
<dict-tag :options="dict.type.ccdi_project_status" :value="scope.row.projectStatus"/>
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 目标人数 -->
|
||||
<el-table-column
|
||||
prop="targetCount"
|
||||
label="目标人数"
|
||||
width="100"
|
||||
align="center"
|
||||
/>
|
||||
|
||||
<!-- 预警人数(带悬停详情) -->
|
||||
<el-table-column
|
||||
label="预警人数"
|
||||
width="120"
|
||||
align="center"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<div slot="content">
|
||||
<div style="padding: 8px;">
|
||||
<div style="margin-bottom: 8px; font-weight: bold; color: #303133;">
|
||||
风险人数统计
|
||||
</div>
|
||||
<div style="margin-bottom: 6px;">
|
||||
<span style="color: #f56c6c;">● 高风险:</span>
|
||||
<span style="font-weight: bold;">{{ scope.row.highRiskCount }} 人</span>
|
||||
</div>
|
||||
<div style="margin-bottom: 6px;">
|
||||
<span style="color: #e6a23c;">● 中风险:</span>
|
||||
<span style="font-weight: bold;">{{ scope.row.mediumRiskCount }} 人</span>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: #909399;">● 低风险:</span>
|
||||
<span style="font-weight: bold;">{{ scope.row.lowRiskCount }} 人</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="warning-count-wrapper">
|
||||
<span :class="getWarningClass(scope.row)" style="cursor: pointer;">
|
||||
{{ scope.row.highRiskCount + scope.row.mediumRiskCount + scope.row.lowRiskCount }}
|
||||
</span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 创建人 -->
|
||||
<el-table-column
|
||||
prop="createBy"
|
||||
label="创建人"
|
||||
width="120"
|
||||
align="center"
|
||||
/>
|
||||
|
||||
<!-- 创建时间 -->
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
width="160"
|
||||
align="center"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="200"
|
||||
align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleDetail(scope.row)"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleEdit(scope.row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-show="total > 0"
|
||||
:current-page="pageParams.pageNum"
|
||||
:page-size="pageParams.pageSize"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
style="margin-top: 16px; text-align: right;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ProjectTable',
|
||||
dicts: ['ccdi_project_status', 'ccdi_config_type'],
|
||||
props: {
|
||||
dataList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
pageParams: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getStatusType(status) {
|
||||
const statusMap = {
|
||||
'0': 'primary', // 进行中
|
||||
'1': 'success', // 已完成
|
||||
'2': 'info' // 已归档
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
},
|
||||
|
||||
getWarningClass(row) {
|
||||
const total = row.highRiskCount + row.mediumRiskCount + row.lowRiskCount
|
||||
if (row.highRiskCount > 0) {
|
||||
return 'text-danger text-bold'
|
||||
} else if (row.mediumRiskCount > 0) {
|
||||
return 'text-warning text-bold'
|
||||
} else if (total > 0) {
|
||||
return 'text-info'
|
||||
}
|
||||
return ''
|
||||
},
|
||||
|
||||
handleDetail(row) {
|
||||
this.$emit('detail', row)
|
||||
},
|
||||
|
||||
handleEdit(row) {
|
||||
this.$emit('edit', row)
|
||||
},
|
||||
|
||||
handleDelete(row) {
|
||||
this.$emit('delete', row)
|
||||
},
|
||||
|
||||
handleSizeChange(val) {
|
||||
this.$emit('pagination', { pageNum: this.pageParams.pageNum, pageSize: val })
|
||||
},
|
||||
|
||||
handleCurrentChange(val) {
|
||||
this.$emit('pagination', { pageNum: val, pageSize: this.pageParams.pageSize })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.project-table-container {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.project-info-cell {
|
||||
padding: 8px 0;
|
||||
line-height: 1.5;
|
||||
|
||||
.project-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.project-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.warning-count-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**Step 3: 验证语法**
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui && npm run lint -- --fix src/views/ccdiProject/components/ProjectTable.vue
|
||||
```
|
||||
|
||||
预期输出:无 ESLint 错误
|
||||
|
||||
**Step 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "feat: 优化项目列表表格,添加预警人数悬停提示"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 修改父组件 index.vue
|
||||
|
||||
**文件:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||
|
||||
**Step 1: 备份原文件**
|
||||
|
||||
```bash
|
||||
cp ruoyi-ui/src/views/ccdiProject/index.vue ruoyi-ui/src/views/ccdiProject/index.vue.bak
|
||||
```
|
||||
|
||||
**Step 2: 修改父组件**
|
||||
|
||||
将 `ruoyi-ui/src/views/ccdiProject/index.vue` 的 `getList` 和 `handleSubmitProject` 方法修改为:
|
||||
|
||||
```javascript
|
||||
/** 查询项目列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
// 使用真实API
|
||||
listProject(this.queryParams).then(response => {
|
||||
this.projectList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
/** 提交项目表单 */
|
||||
handleSubmitProject(data) {
|
||||
// 不需要再次调用API,因为AddProjectDialog已经处理了
|
||||
this.addDialogVisible = false
|
||||
this.getList() // 刷新列表
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 验证语法**
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui && npm run lint -- --fix src/views/ccdiProject/index.vue
|
||||
```
|
||||
|
||||
预期输出:无 ESLint 错误
|
||||
|
||||
**Step 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/index.vue
|
||||
git commit -m "feat: 修改父组件,切换为真实API调用"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 启动前端并测试
|
||||
|
||||
**Step 1: 启动前端开发服务器**
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui && npm run dev
|
||||
```
|
||||
|
||||
预期输出:前端服务启动成功,访问地址 http://localhost:80
|
||||
|
||||
**Step 2: 测试登录**
|
||||
|
||||
浏览器访问 http://localhost:80,使用测试账号登录:
|
||||
- 用户名:admin
|
||||
- 密码:admin123
|
||||
|
||||
预期结果:登录成功,进入首页
|
||||
|
||||
**Step 3: 测试项目列表**
|
||||
|
||||
导航到"纪检初核管理 > 项目管理"菜单:
|
||||
|
||||
预期结果:
|
||||
- 项目列表正常显示
|
||||
- 项目名称和描述上下排列
|
||||
- 项目状态标签显示正确
|
||||
- 预警人数悬停提示显示风险详情
|
||||
|
||||
**Step 4: 测试创建项目**
|
||||
|
||||
点击"新建项目"按钮:
|
||||
|
||||
预期结果:
|
||||
- 弹窗正常打开
|
||||
- 显示3个字段(项目名称、项目描述、配置方式)
|
||||
- 配置方式默认选中"全局默认模型参数配置"
|
||||
|
||||
填写表单:
|
||||
- 项目名称:测试项目001
|
||||
- 项目描述:这是测试项目的描述
|
||||
- 配置方式:选择"自定义项目规则参数配置"
|
||||
|
||||
点击"创建项目"按钮:
|
||||
|
||||
预期结果:
|
||||
- 按钮显示 loading 状态
|
||||
- 创建成功,提示"项目创建成功"
|
||||
- 弹窗关闭
|
||||
- 项目列表自动刷新,显示新创建的项目
|
||||
|
||||
**Step 5: 测试预警人数悬停**
|
||||
|
||||
在项目列表中,将鼠标悬停在预警人数上:
|
||||
|
||||
预期结果:
|
||||
- 显示风险人数统计提示框
|
||||
- 显示高风险、中风险、低风险人数
|
||||
- 预警人数颜色根据风险级别变化
|
||||
|
||||
**Step 6: 测试表单验证**
|
||||
|
||||
不填写项目名称,直接点击"创建项目":
|
||||
|
||||
预期结果:
|
||||
- 提示"请输入项目名称"
|
||||
- 表单不提交
|
||||
|
||||
**Step 7: 测试取消按钮**
|
||||
|
||||
点击"新建项目",然后点击"取消":
|
||||
|
||||
预期结果:
|
||||
- 弹窗关闭
|
||||
- 表单数据清空
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 跨浏览器测试
|
||||
|
||||
**Step 1: Chrome 测试**
|
||||
|
||||
在 Chrome 浏览器中重复 Task 5 的所有测试:
|
||||
|
||||
预期结果:所有功能正常
|
||||
|
||||
**Step 2: Edge 测试**
|
||||
|
||||
在 Edge 浏览器中重复 Task 5 的所有测试:
|
||||
|
||||
预期结果:所有功能正常
|
||||
|
||||
**Step 3: Firefox 测试(可选)**
|
||||
|
||||
在 Firefox 浏览器中重复 Task 5 的所有测试:
|
||||
|
||||
预期结果:所有功能正常
|
||||
|
||||
---
|
||||
|
||||
## Task 7: 响应式测试
|
||||
|
||||
**Step 1: 测试不同分辨率**
|
||||
|
||||
调整浏览器窗口大小,测试以下分辨率:
|
||||
|
||||
- 1920x1080(桌面)
|
||||
- 1366x768(笔记本)
|
||||
- 768x1024(平板)
|
||||
|
||||
预期结果:
|
||||
- 表格自适应宽度
|
||||
- 弹窗居中显示
|
||||
- 所有功能正常使用
|
||||
|
||||
**Step 2: 测试表格横向滚动**
|
||||
|
||||
缩小浏览器窗口,使表格宽度小于内容宽度:
|
||||
|
||||
预期结果:
|
||||
- 表格出现横向滚动条
|
||||
- 操作列固定在右侧
|
||||
- 可以横向滚动查看所有列
|
||||
|
||||
---
|
||||
|
||||
## Task 8: 提交最终代码
|
||||
|
||||
**Step 1: 检查所有文件**
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
预期输出:所有前端文件已提交
|
||||
|
||||
**Step 2: 推送到远程仓库**
|
||||
|
||||
```bash
|
||||
git push origin dev
|
||||
```
|
||||
|
||||
预期输出:推送成功
|
||||
|
||||
---
|
||||
|
||||
## 完成检查清单
|
||||
|
||||
- [ ] API 接口文件更新完成
|
||||
- [ ] AddProjectDialog 组件简化完成(3个字段)
|
||||
- [ ] ProjectTable 组件优化完成(上下排列、预警悬停)
|
||||
- [ ] 父组件切换为真实API
|
||||
- [ ] 前端服务启动成功
|
||||
- [ ] 登录功能正常
|
||||
- [ ] 项目列表显示正常
|
||||
- [ ] 项目名称和描述上下排列正确
|
||||
- [ ] 项目状态标签显示正确
|
||||
- [ ] 预警人数悬停提示显示正常
|
||||
- [ ] 预警人数颜色根据风险级别变化
|
||||
- [ ] 创建项目弹窗打开正常
|
||||
- [ ] 配置方式默认值正确
|
||||
- [ ] 创建项目功能正常
|
||||
- [ ] 创建成功后列表刷新
|
||||
- [ ] 表单验证正常
|
||||
- [ ] 取消按钮功能正常
|
||||
- [ ] 跨浏览器测试通过
|
||||
- [ ] 响应式测试通过
|
||||
- [ ] 所有代码已提交到 git
|
||||
|
||||
---
|
||||
|
||||
**前端实施计划完成!**
|
||||
@@ -1,105 +0,0 @@
|
||||
# 模型参数阈值更新接口优化设计
|
||||
|
||||
## 1. 背景
|
||||
|
||||
当前 `ModelParamSaveDTO` 存在参数冗余问题:
|
||||
- 外层包含不必要的 `modelName` 字段
|
||||
- 内层 `ParamItem` 包含 6 个字段,但 Service 层只使用 `paramCode` 和 `paramValue`
|
||||
- 前端请求体包含大量无用字段,增加网络传输开销
|
||||
|
||||
## 2. 优化目标
|
||||
|
||||
- 简化 DTO 结构,减少冗余字段
|
||||
- 减少前端请求数据量
|
||||
- 提升代码可读性
|
||||
|
||||
## 3. 设计方案
|
||||
|
||||
### 3.1 后端 DTO 简化
|
||||
|
||||
**文件:** `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamSaveDTO.java`
|
||||
|
||||
**改动:**
|
||||
- 移除 `modelName` 字段
|
||||
- 将内部类 `ParamItem` 重命名为 `ParamValueItem`
|
||||
- 内部类只保留 `paramCode` 和 `paramValue` 两个字段
|
||||
|
||||
**优化后结构:**
|
||||
```java
|
||||
@Data
|
||||
public class ModelParamSaveDTO {
|
||||
private Long projectId;
|
||||
|
||||
@NotBlank(message = "模型编码不能为空")
|
||||
private String modelCode;
|
||||
|
||||
@NotNull(message = "参数列表不能为空")
|
||||
private List<ParamValueItem> params;
|
||||
|
||||
@Data
|
||||
public static class ParamValueItem {
|
||||
@NotBlank(message = "参数编码不能为空")
|
||||
private String paramCode;
|
||||
|
||||
@NotBlank(message = "参数值不能为空")
|
||||
private String paramValue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Service 层微调
|
||||
|
||||
**文件:** `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java`
|
||||
|
||||
**改动:** 更新循环中的类型引用
|
||||
```java
|
||||
// 改动前
|
||||
for (ModelParamSaveDTO.ParamItem item : saveDTO.getParams())
|
||||
|
||||
// 改动后
|
||||
for (ModelParamSaveDTO.ParamValueItem item : saveDTO.getParams())
|
||||
```
|
||||
|
||||
### 3.3 前端请求简化
|
||||
|
||||
**文件:** `ruoyi-ui/src/views/ccdi/modelParam/index.vue`
|
||||
|
||||
**改动:** 简化 `handleSave` 方法中的请求参数
|
||||
|
||||
**优化后:**
|
||||
```javascript
|
||||
const saveDTO = {
|
||||
projectId: this.queryParams.projectId,
|
||||
modelCode: this.queryParams.modelCode,
|
||||
params: modifiedParams.map((item) => ({
|
||||
paramCode: item.paramCode,
|
||||
paramValue: item.paramValue,
|
||||
})),
|
||||
};
|
||||
```
|
||||
|
||||
## 4. 改动文件清单
|
||||
|
||||
| 文件 | 改动类型 |
|
||||
|------|---------|
|
||||
| `ModelParamSaveDTO.java` | 简化字段 |
|
||||
| `CcdiModelParamServiceImpl.java` | 类型引用更新 |
|
||||
| `index.vue` | 请求参数简化 |
|
||||
|
||||
## 5. 优化效果
|
||||
|
||||
| 指标 | 优化前 | 优化后 |
|
||||
|------|--------|--------|
|
||||
| DTO 外层字段数 | 3 | 2 |
|
||||
| DTO 内层字段数 | 6 | 2 |
|
||||
| 前端请求体字段数 | 8 | 4 |
|
||||
|
||||
## 6. 风险评估
|
||||
|
||||
- **风险等级:** 低
|
||||
- **向后兼容:** 是(后端忽略多余字段)
|
||||
- **测试要求:** 验证保存功能正常
|
||||
|
||||
---
|
||||
|
||||
**创建日期:** 2026-02-26
|
||||
@@ -1,238 +0,0 @@
|
||||
# 模型参数阈值更新接口优化实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 简化模型参数保存接口的 DTO 结构,减少冗余字段
|
||||
|
||||
**Architecture:** 纯重构,不改变业务逻辑。简化 DTO 字段,同步更新 Service 和前端调用
|
||||
|
||||
**Tech Stack:** Java 17, Spring Boot, Vue.js
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 简化后端 DTO
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamSaveDTO.java`
|
||||
|
||||
**Step 1: 读取当前 DTO 文件**
|
||||
|
||||
检查现有代码结构。
|
||||
|
||||
**Step 2: 重写 DTO 文件**
|
||||
|
||||
将整个文件替换为简化后的版本:
|
||||
|
||||
```java
|
||||
package com.ruoyi.ccdi.project.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 模型参数保存DTO
|
||||
*/
|
||||
@Data
|
||||
public class ModelParamSaveDTO {
|
||||
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 模型编码 */
|
||||
@NotBlank(message = "模型编码不能为空")
|
||||
private String modelCode;
|
||||
|
||||
/** 参数列表 */
|
||||
@NotNull(message = "参数列表不能为空")
|
||||
private List<ParamValueItem> params;
|
||||
|
||||
@Data
|
||||
public static class ParamValueItem {
|
||||
|
||||
/** 参数编码 */
|
||||
@NotBlank(message = "参数编码不能为空")
|
||||
private String paramCode;
|
||||
|
||||
/** 参数值 - 唯一可修改字段 */
|
||||
@NotBlank(message = "参数值不能为空")
|
||||
private String paramValue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 保存文件**
|
||||
|
||||
确保文件保存成功。
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 更新 Service 层类型引用
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java`
|
||||
|
||||
**Step 1: 更新 for 循环中的类型引用**
|
||||
|
||||
找到第 105 行附近的代码,将:
|
||||
|
||||
```java
|
||||
for (ModelParamSaveDTO.ParamItem item : saveDTO.getParams()) {
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```java
|
||||
for (ModelParamSaveDTO.ParamValueItem item : saveDTO.getParams()) {
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 简化前端请求参数
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdi/modelParam/index.vue`
|
||||
|
||||
**Step 1: 找到 handleSave 方法中的 saveDTO 构建**
|
||||
|
||||
定位到第 119-133 行。
|
||||
|
||||
**Step 2: 简化请求参数**
|
||||
|
||||
将原有的 saveDTO 构建代码:
|
||||
|
||||
```javascript
|
||||
const saveDTO = {
|
||||
projectId: this.queryParams.projectId,
|
||||
modelCode: this.queryParams.modelCode,
|
||||
modelName: this.modelList.find(
|
||||
(m) => m.modelCode === this.queryParams.modelCode
|
||||
)?.modelName,
|
||||
params: modifiedParams.map((item) => ({
|
||||
paramCode: item.paramCode,
|
||||
paramName: item.paramName,
|
||||
paramDesc: item.paramDesc,
|
||||
paramValue: item.paramValue,
|
||||
paramUnit: item.paramUnit,
|
||||
sortOrder: item.sortOrder,
|
||||
})),
|
||||
};
|
||||
```
|
||||
|
||||
替换为简化版本:
|
||||
|
||||
```javascript
|
||||
const saveDTO = {
|
||||
projectId: this.queryParams.projectId,
|
||||
modelCode: this.queryParams.modelCode,
|
||||
params: modifiedParams.map((item) => ({
|
||||
paramCode: item.paramCode,
|
||||
paramValue: item.paramValue,
|
||||
})),
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 编译后端验证
|
||||
|
||||
**Files:**
|
||||
- 无文件修改,仅验证
|
||||
|
||||
**Step 1: 编译后端项目**
|
||||
|
||||
```bash
|
||||
cd D:/ccdi/ccdi && mvn clean compile -DskipTests
|
||||
```
|
||||
|
||||
**Expected:** BUILD SUCCESS
|
||||
|
||||
**Step 2: 如有编译错误,检查类型引用**
|
||||
|
||||
确保所有 `ParamItem` 都已改为 `ParamValueItem`。
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 功能测试验证
|
||||
|
||||
**Files:**
|
||||
- 无文件修改,仅验证
|
||||
|
||||
**Step 1: 启动后端服务**
|
||||
|
||||
```bash
|
||||
cd D:/ccdi/ccdi && mvn spring-boot:run
|
||||
```
|
||||
|
||||
等待服务启动完成。
|
||||
|
||||
**Step 2: 通过 Swagger 测试保存接口**
|
||||
|
||||
1. 访问 `http://localhost:8080/swagger-ui/index.html`
|
||||
2. 找到 `模型参数配置` 分组
|
||||
3. 测试 `/ccdi/modelParam/save` 接口
|
||||
4. 使用简化的请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"projectId": 0,
|
||||
"modelCode": "LARGE_TRANSACTION",
|
||||
"params": [
|
||||
{
|
||||
"paramCode": "SINGLE_AMOUNT",
|
||||
"paramValue": "50000"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Expected:** 返回 `{"code": 200, "msg": "保存成功"}`
|
||||
|
||||
**Step 3: 验证参数已更新**
|
||||
|
||||
调用 `/ccdi/modelParam/list?projectId=0&modelCode=LARGE_TRANSACTION`
|
||||
|
||||
**Expected:** 返回的参数中 `SINGLE_AMOUNT` 的 `paramValue` 已更新为 `50000`
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 提交代码
|
||||
|
||||
**Step 1: 查看变更**
|
||||
|
||||
```bash
|
||||
git status
|
||||
git diff
|
||||
```
|
||||
|
||||
**Step 2: 提交后端改动**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamSaveDTO.java
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
|
||||
git commit -m "refactor: 简化 ModelParamSaveDTO,移除冗余字段"
|
||||
```
|
||||
|
||||
**Step 3: 提交前端改动**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdi/modelParam/index.vue
|
||||
git commit -m "refactor: 简化模型参数保存请求参数"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 改动摘要
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `ModelParamSaveDTO.java` | 移除 modelName,ParamItem 简化为 ParamValueItem(2字段) |
|
||||
| `CcdiModelParamServiceImpl.java` | 类型引用 ParamItem → ParamValueItem |
|
||||
| `index.vue` | 请求参数只保留 paramCode 和 paramValue |
|
||||
|
||||
**风险等级:** 低(向后兼容,纯简化重构)
|
||||
|
||||
---
|
||||
|
||||
**创建日期:** 2026-02-26
|
||||
@@ -1,587 +0,0 @@
|
||||
# Material Design 表格样式优化设计文档
|
||||
|
||||
**日期**: 2026-02-27
|
||||
**状态**: 已批准
|
||||
**方案**: 纯扁平卡片式(方案 1)
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述项目管理表格的 Material Design 风格优化设计。通过移除边框、使用阴影和留白来分隔内容,实现现代、简洁的视觉体验。
|
||||
|
||||
## 设计目标
|
||||
|
||||
1. **全面 Material Design 改版**:采用 Material Design 的核心设计语言
|
||||
2. **扁平化表头**:移除表头背景色,使用排版和留白区分
|
||||
3. **阴影和留白**:用视觉层次代替边框分隔
|
||||
4. **中等阴影效果**:`box-shadow: 0 2px 8px rgba(0,0,0,0.1)`
|
||||
|
||||
## 设计方案
|
||||
|
||||
### 整体设计理念
|
||||
|
||||
采用 **纯扁平卡片式** 设计,核心特征:
|
||||
- 表格整体作为一张浮动卡片
|
||||
- 使用阴影创造视觉层次
|
||||
- 移除所有边框和分隔线
|
||||
- 通过留白分隔行与行
|
||||
- 表头扁平化,无背景色
|
||||
|
||||
---
|
||||
|
||||
## 详细设计
|
||||
|
||||
### 1. 整体卡片容器和阴影
|
||||
|
||||
**样式定义:**
|
||||
|
||||
```scss
|
||||
.project-table-container {
|
||||
margin-top: 16px;
|
||||
|
||||
:deep(.el-table) {
|
||||
// 移除边框,使用阴影
|
||||
border: none;
|
||||
border-radius: 8px; // 从 4px 增加到 8px,更圆润
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); // 中等阴影
|
||||
|
||||
// 悬停时卡片阴影加深
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**视觉效果:**
|
||||
- 表格作为浮动的独立卡片
|
||||
- 圆角:8px(更柔和)
|
||||
- 默认阴影:`0 2px 8px rgba(0,0,0,0.1)`
|
||||
- 悬停阴影:`0 4px 12px rgba(0,0,0,0.15)`
|
||||
- 完全移除边框
|
||||
|
||||
**变更对比:**
|
||||
| 属性 | 旧值 | 新值 |
|
||||
|------|------|------|
|
||||
| border | `1px solid #eee` | `none` |
|
||||
| border-radius | `4px` | `8px` |
|
||||
| box-shadow | 无 | `0 2px 8px rgba(0,0,0,0.1)` |
|
||||
|
||||
---
|
||||
|
||||
### 2. 扁平化表头设计
|
||||
|
||||
**样式定义:**
|
||||
|
||||
```scss
|
||||
:deep(.el-table) {
|
||||
// 表头样式 - 扁平化,无背景色
|
||||
th {
|
||||
background-color: transparent; // 移除背景色
|
||||
color: #333;
|
||||
font-weight: 600; // 加粗字体
|
||||
font-size: 14px;
|
||||
height: 56px; // 从 48px 增加到 56px
|
||||
padding: 16px 12px; // 从 12px 增加到 16px
|
||||
|
||||
// 只保留底部一条分隔线
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
// 表头单元格内部
|
||||
.cell {
|
||||
border-right: none; // 移除垂直分隔线
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**设计理念:**
|
||||
- 通过字体粗细、留白和底线区分表头
|
||||
- 不依赖背景色
|
||||
- 简洁、现代
|
||||
|
||||
**变更对比:**
|
||||
| 属性 | 旧值 | 新值 |
|
||||
|------|------|------|
|
||||
| background-color | `#f5f5f5` | `transparent` |
|
||||
| height | `48px` | `56px` |
|
||||
| padding | `12px` | `16px 12px` |
|
||||
| border-bottom | 无 | `2px solid #e0e0e0` |
|
||||
|
||||
---
|
||||
|
||||
### 3. 数据行设计(留白和悬停)
|
||||
|
||||
**样式定义:**
|
||||
|
||||
```scss
|
||||
:deep(.el-table) {
|
||||
// 数据行样式 - 增加留白,移除分隔线
|
||||
td {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
height: 64px; // 从 50px 增加到 64px
|
||||
padding: 20px 12px; // 从 12px 增加到 20px
|
||||
border-bottom: none; // 完全移除行分隔线
|
||||
}
|
||||
|
||||
// 悬停效果
|
||||
.el-table__row {
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover > td {
|
||||
background-color: #fafafa !important; // 浅灰色背景
|
||||
}
|
||||
}
|
||||
|
||||
// 移除表格内容的额外边框
|
||||
.el-table__body-wrapper {
|
||||
.cell {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
display: none; // 完全移除伪元素边框
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键变化:**
|
||||
1. **行高增加**:50px → 64px(+28%)
|
||||
2. **垂直内边距**:12px → 20px(+67%)
|
||||
3. **移除行分隔线**:`border-bottom: none`
|
||||
4. **悬停效果**:浅灰色背景 `#fafafa` + 过渡 0.2s
|
||||
|
||||
**变更对比:**
|
||||
| 属性 | 旧值 | 新值 |
|
||||
|------|------|------|
|
||||
| height | `50px` | `64px` |
|
||||
| padding | `12px` | `20px 12px` |
|
||||
| border-bottom | `1px solid #f0f0f0` | `none` |
|
||||
| 悬停背景 | `#f5f5f5` | `#fafafa` |
|
||||
|
||||
---
|
||||
|
||||
### 4. 操作按钮样式
|
||||
|
||||
**样式定义:**
|
||||
|
||||
```scss
|
||||
// 操作按钮样式 - Material Design 风格
|
||||
:deep(.el-button--text) {
|
||||
color: #1890ff;
|
||||
padding: 8px 12px; // 从 0 8px 增加到 8px 12px
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #096dd9;
|
||||
background-color: rgba(24, 144, 255, 0.08); // 添加浅蓝色背景
|
||||
text-decoration: none; // 移除下划线
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
// 按钮间距
|
||||
& + .el-button--text {
|
||||
margin-left: 4px; // 从 8px 减少到 4px
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**改进点:**
|
||||
1. **增加内边距**:更符合 Material Design 的"点击区域"理念
|
||||
2. **悬停背景色**:用浅蓝色背景代替下划线
|
||||
3. **减少间距**:背景色会在视觉上分隔按钮
|
||||
|
||||
**变更对比:**
|
||||
| 属性 | 旧值 | 新值 |
|
||||
|------|------|------|
|
||||
| padding | `0 8px` | `8px 12px` |
|
||||
| border-radius | 无 | `4px` |
|
||||
| hover background | 无 | `rgba(24, 144, 255, 0.08)` |
|
||||
| hover text-decoration | `underline` | `none` |
|
||||
|
||||
---
|
||||
|
||||
### 5. 分页组件样式
|
||||
|
||||
**样式定义:**
|
||||
|
||||
```scss
|
||||
// 分页样式优化
|
||||
:deep(.el-pagination) {
|
||||
margin-top: 24px; // 从 16px 增加到 24px
|
||||
text-align: right;
|
||||
|
||||
// 扁平化按钮
|
||||
.btn-prev,
|
||||
.btn-next,
|
||||
.el-pager li {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.el-pager li.active {
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-pagination__total,
|
||||
.el-pagination__sizes,
|
||||
.el-pagination__jump {
|
||||
color: #666; // 从 #606266 改为 #666
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**改进点:**
|
||||
1. **移除边框**:扁平化所有按钮
|
||||
2. **激活页码**:蓝色背景 + 圆角
|
||||
3. **增加上边距**:24px(原 16px)
|
||||
|
||||
---
|
||||
|
||||
### 6. 空状态设计
|
||||
|
||||
**样式定义:**
|
||||
|
||||
```scss
|
||||
// 空状态(无数据时)
|
||||
:deep(.el-table__empty-block) {
|
||||
padding: 48px 0; // 增加垂直留白
|
||||
|
||||
.el-table__empty-text {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整样式代码
|
||||
|
||||
```scss
|
||||
<style lang="scss" scoped>
|
||||
.project-table-container {
|
||||
margin-top: 16px;
|
||||
|
||||
:deep(.el-table) {
|
||||
// 卡片容器
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
// 表头样式
|
||||
th {
|
||||
background-color: transparent;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
height: 56px;
|
||||
padding: 16px 12px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
// 数据行样式
|
||||
td {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
height: 64px;
|
||||
padding: 20px 12px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
// 移除列分隔线
|
||||
.el-table__body-wrapper {
|
||||
.cell {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 悬停效果
|
||||
.el-table__row {
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover > td {
|
||||
background-color: #fafafa !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 移除额外边框
|
||||
&::before,
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 操作按钮样式
|
||||
:deep(.el-button--text) {
|
||||
color: #1890ff;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #096dd9;
|
||||
background-color: rgba(24, 144, 255, 0.08);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
& + .el-button--text {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// 分页样式
|
||||
:deep(.el-pagination) {
|
||||
margin-top: 24px;
|
||||
text-align: right;
|
||||
|
||||
.btn-prev,
|
||||
.btn-next,
|
||||
.el-pager li {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.el-pager li.active {
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-pagination__total,
|
||||
.el-pagination__sizes,
|
||||
.el-pagination__jump {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态
|
||||
:deep(.el-table__empty-block) {
|
||||
padding: 48px 0;
|
||||
|
||||
.el-table__empty-text {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
// 保留现有样式
|
||||
.project-info-cell {
|
||||
padding: 8px 0;
|
||||
line-height: 1.5;
|
||||
|
||||
.project-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.project-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.warning-count-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 视觉对比
|
||||
|
||||
### 旧设计 vs 新设计
|
||||
|
||||
| 元素 | 旧设计 | 新设计 | 改进 |
|
||||
|------|--------|--------|------|
|
||||
| **表格边框** | `1px solid #eee` | 无边框 + 阴影 | 更轻盈 |
|
||||
| **圆角** | 4px | 8px | 更柔和 |
|
||||
| **表头背景** | `#f5f5f5` | 透明 | 扁平化 |
|
||||
| **表头高度** | 48px | 56px | 更舒适 |
|
||||
| **行高** | 50px | 64px | 更透气 |
|
||||
| **行内边距** | 12px | 20px | 留白充足 |
|
||||
| **行分隔线** | `1px solid #f0f0f0` | 无 | 纯留白 |
|
||||
| **悬停背景** | `#f5f5f5` | `#fafafa` | 更微妙 |
|
||||
| **按钮悬停** | 下划线 | 背景色 | Material 风格 |
|
||||
|
||||
---
|
||||
|
||||
## 设计规范
|
||||
|
||||
### 阴影层级
|
||||
|
||||
- **默认卡片阴影**:`0 2px 8px rgba(0, 0, 0, 0.1)`(Elevation 2)
|
||||
- **悬停卡片阴影**:`0 4px 12px rgba(0, 0, 0, 0.15)`(Elevation 4)
|
||||
|
||||
### 间距规范
|
||||
|
||||
- **卡片上边距**:16px
|
||||
- **表头高度**:56px
|
||||
- **表头内边距**:16px 12px
|
||||
- **数据行高度**:64px
|
||||
- **数据行内边距**:20px 12px
|
||||
- **按钮内边距**:8px 12px
|
||||
- **分页上边距**:24px
|
||||
|
||||
### 颜色规范
|
||||
|
||||
- **卡片背景**:#ffffff
|
||||
- **表头文字**:#333
|
||||
- **表头底线**:#e0e0e0
|
||||
- **数据行文字**:#333
|
||||
- **悬停背景**:#fafafa
|
||||
- **操作按钮**:#1890ff
|
||||
- **按钮悬停**:#096dd9
|
||||
- **按钮悬停背景**:`rgba(24, 144, 255, 0.08)`
|
||||
- **激活页码**:#1890ff
|
||||
- **空状态文字**:#999
|
||||
|
||||
### 圆角规范
|
||||
|
||||
- **卡片圆角**:8px
|
||||
- **按钮圆角**:4px
|
||||
- **页码圆角**:4px
|
||||
|
||||
---
|
||||
|
||||
## 响应式考虑
|
||||
|
||||
### 大屏幕(≥1920px)
|
||||
- 保持设计不变
|
||||
- 可以考虑增加卡片间距
|
||||
|
||||
### 中等屏幕(1366px - 1919px)
|
||||
- 当前设计最佳适配
|
||||
|
||||
### 小屏幕(<1366px)
|
||||
- 表格可能需要横向滚动
|
||||
- 考虑固定关键列(如操作列)
|
||||
|
||||
---
|
||||
|
||||
## 浏览器兼容性
|
||||
|
||||
### 现代浏览器
|
||||
- ✅ Chrome 80+
|
||||
- ✅ Firefox 75+
|
||||
- ✅ Safari 13+
|
||||
- ✅ Edge 80+
|
||||
|
||||
### 潜在问题
|
||||
- `box-shadow` 在所有现代浏览器中都支持良好
|
||||
- `border-radius` 无兼容性问题
|
||||
- `transition` 在现代浏览器中完全支持
|
||||
|
||||
---
|
||||
|
||||
## 实现步骤
|
||||
|
||||
1. 修改 `ProjectTable.vue` 的 `<style>` 部分
|
||||
2. 替换所有边框样式为阴影
|
||||
3. 调整表头、数据行的高度和内边距
|
||||
4. 更新操作按钮和分页样式
|
||||
5. 测试视觉效果和交互体验
|
||||
6. 提交代码
|
||||
|
||||
---
|
||||
|
||||
## 文件修改
|
||||
|
||||
**修改文件:**
|
||||
- `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**影响范围:**
|
||||
- 仅影响样式,不影响功能
|
||||
- 不影响其他组件
|
||||
- 向后兼容
|
||||
|
||||
---
|
||||
|
||||
## 风险评估
|
||||
|
||||
**风险等级:** 🟢 **低风险**
|
||||
|
||||
- ✅ 纯样式优化,无业务逻辑变更
|
||||
- ✅ 组件职责单一,影响范围可控
|
||||
- ✅ 样式使用 scoped,不污染其他组件
|
||||
- ✅ 可以快速回滚(只需恢复旧样式)
|
||||
|
||||
---
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
**可选增强(非必需):**
|
||||
|
||||
1. **添加 Ripple 效果**:操作按钮点击时的水波纹动画
|
||||
2. **暗色模式**:提供暗色主题支持
|
||||
3. **动画效果**:行展开/折叠的平滑动画
|
||||
4. **可访问性**:添加高对比度模式支持
|
||||
5. **响应式优化**:移动端的特殊处理
|
||||
|
||||
---
|
||||
|
||||
## 参考资源
|
||||
|
||||
- **Material Design 官方文档**: https://material.io/design
|
||||
- **Element UI 文档**: https://element.eleme.cn/
|
||||
- **当前设计文档**: `doc/plans/2026-02-27-项目管理首页优化-design.md`
|
||||
- **当前实现计划**: `doc/plans/2026-02-27-项目管理首页优化.md`
|
||||
|
||||
---
|
||||
|
||||
**设计完成日期**: 2026-02-27
|
||||
**设计状态**: ✅ 已批准
|
||||
**下一步**: 创建实现计划
|
||||
@@ -1,654 +0,0 @@
|
||||
# Material Design 表格样式优化实现计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 将项目管理表格优化为 Material Design 风格,移除边框,使用阴影和留白分隔内容
|
||||
|
||||
**Architecture:** 纯样式优化,修改 ProjectTable 组件的 SCSS,采用扁平卡片式设计
|
||||
|
||||
**Tech Stack:** Vue.js 2.6.12, Element UI 2.15.14, SCSS
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 修改表格容器样式 - 添加阴影和圆角
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 定位表格样式部分**
|
||||
|
||||
找到 `<style lang="scss" scoped>` 中的 `.project-table-container` 部分(约第 245 行开始)。
|
||||
|
||||
**Step 2: 修改表格容器样式**
|
||||
|
||||
替换现有的 `:deep(.el-table)` 样式:
|
||||
|
||||
```scss
|
||||
.project-table-container {
|
||||
margin-top: 16px;
|
||||
|
||||
// 表格整体样式 - Material Design 卡片式
|
||||
:deep(.el-table) {
|
||||
// 移除边框,使用阴影
|
||||
border: none; // 从 1px solid #eee 改为 none
|
||||
border-radius: 8px; // 从 4px 增加到 8px
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); // 新增中等阴影
|
||||
|
||||
// 悬停时卡片阴影加深
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**变更说明:**
|
||||
- `border`: `1px solid #eee` → `none`
|
||||
- `border-radius`: `4px` → `8px`
|
||||
- 新增 `box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)`
|
||||
- 新增悬停效果 `box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15)`
|
||||
- 新增过渡动画 `transition: box-shadow 0.3s ease`
|
||||
|
||||
**Step 3: 验证样式应用**
|
||||
|
||||
1. 启动前端开发服务器:
|
||||
```bash
|
||||
cd ruoyi-ui && npm run dev
|
||||
```
|
||||
|
||||
2. 访问项目管理页面:http://localhost:80
|
||||
|
||||
3. 使用浏览器开发者工具检查表格:
|
||||
- 确认 `border` 为 `none`
|
||||
- 确认 `border-radius` 为 `8px`
|
||||
- 确认有阴影效果
|
||||
- 鼠标悬停时阴影加深
|
||||
|
||||
**Step 4: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "style: Material Design - 表格容器添加阴影和圆角"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 扁平化表头样式
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 修改表头样式**
|
||||
|
||||
在 `:deep(.el-table)` 内修改 `th` 样式:
|
||||
|
||||
```scss
|
||||
// 表头样式 - 扁平化,无背景色
|
||||
th {
|
||||
background-color: transparent; // 从 #f5f5f5 改为 transparent
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
height: 56px; // 从 48px 增加到 56px
|
||||
padding: 16px 12px; // 从 12px 增加到 16px 垂直内边距
|
||||
|
||||
// 只保留底部一条分隔线
|
||||
border-bottom: 2px solid #e0e0e0; // 新增
|
||||
}
|
||||
```
|
||||
|
||||
**变更说明:**
|
||||
- `background-color`: `#f5f5f5` → `transparent`
|
||||
- `height`: `48px` → `56px`
|
||||
- `padding`: `12px` → `16px 12px`
|
||||
- 新增 `border-bottom: 2px solid #e0e0e0`
|
||||
|
||||
**Step 2: 移除表头单元格的垂直分隔线**
|
||||
|
||||
确认 `.cell` 样式中已有:
|
||||
```scss
|
||||
.cell {
|
||||
border-right: none; // 确认这行存在
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 验证表头样式**
|
||||
|
||||
在浏览器中检查表头:
|
||||
- 表头背景应为透明(白色)
|
||||
- 表头文字应加粗
|
||||
- 底部应有一条 2px 的灰色线
|
||||
- 高度应增加到 56px
|
||||
|
||||
**Step 4: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "style: Material Design - 扁平化表头,移除背景色"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 优化数据行样式 - 移除分隔线,增加留白
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 修改数据行样式**
|
||||
|
||||
在 `:deep(.el-table)` 内修改 `td` 样式:
|
||||
|
||||
```scss
|
||||
// 数据行样式 - 增加留白,移除分隔线
|
||||
td {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
height: 64px; // 从 50px 增加到 64px
|
||||
padding: 20px 12px; // 从 12px 增加到 20px 垂直内边距
|
||||
border-bottom: none; // 从 1px solid #f0f0f0 改为 none
|
||||
}
|
||||
```
|
||||
|
||||
**变更说明:**
|
||||
- `height`: `50px` → `64px` (+28%)
|
||||
- `padding`: `12px` → `20px 12px` (+67% 垂直内边距)
|
||||
- `border-bottom`: `1px solid #f0f0f0` → `none`
|
||||
|
||||
**Step 2: 优化悬停效果**
|
||||
|
||||
修改 `.el-table__row:hover > td` 样式:
|
||||
|
||||
```scss
|
||||
// 悬停效果
|
||||
.el-table__row {
|
||||
transition: background-color 0.2s ease; // 新增过渡
|
||||
|
||||
&:hover > td {
|
||||
background-color: #fafafa !important; // 从 #f5f5f5 改为 #fafafa
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**变更说明:**
|
||||
- 悬停背景色:`#f5f5f5` → `#fafafa`(更浅)
|
||||
- 新增 `transition: background-color 0.2s ease`
|
||||
|
||||
**Step 3: 确认移除额外边框**
|
||||
|
||||
确认已有以下样式(如果不存在则添加):
|
||||
|
||||
```scss
|
||||
// 移除表格内容的额外边框
|
||||
.el-table__body-wrapper {
|
||||
.cell {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
display: none; // 或 height: 0; width: 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: 验证数据行样式**
|
||||
|
||||
在浏览器中检查:
|
||||
- 行高应增加到 64px
|
||||
- 行之间应无分隔线(纯留白)
|
||||
- 悬停时背景应为浅灰色 `#fafafa`
|
||||
- 过渡动画应平滑
|
||||
|
||||
**Step 5: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "style: Material Design - 移除行分隔线,增加留白"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 优化操作按钮样式 - Material Design 风格
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 修改操作按钮样式**
|
||||
|
||||
找到或添加 `:deep(.el-button--text)` 样式部分(约第 338 行),修改为:
|
||||
|
||||
```scss
|
||||
// 操作按钮样式 - Material Design 风格
|
||||
:deep(.el-button--text) {
|
||||
color: #1890ff;
|
||||
padding: 8px 12px; // 从 0 8px 增加到 8px 12px
|
||||
border-radius: 4px; // 新增圆角
|
||||
transition: all 0.2s ease; // 从只过渡颜色改为过渡所有属性
|
||||
|
||||
&:hover {
|
||||
color: #096dd9;
|
||||
background-color: rgba(24, 144, 255, 0.08); // 新增浅蓝色背景
|
||||
text-decoration: none; // 移除下划线
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0; // 第一个按钮无左内边距
|
||||
}
|
||||
|
||||
// 按钮间距
|
||||
& + .el-button--text {
|
||||
margin-left: 4px; // 从 8px 减少到 4px
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**变更说明:**
|
||||
- `padding`: `0 8px` → `8px 12px`(增加点击区域)
|
||||
- 新增 `border-radius: 4px`
|
||||
- 新增悬停背景色 `rgba(24, 144, 255, 0.08)`
|
||||
- 移除悬停下划线 `text-decoration: none`
|
||||
- `transition`: 只过渡颜色 → 过渡所有属性
|
||||
- 按钮间距:`8px` → `4px`
|
||||
|
||||
**Step 2: 验证按钮样式**
|
||||
|
||||
在浏览器中测试:
|
||||
- 按钮内边距应增加
|
||||
- 悬停时应显示浅蓝色背景,无下划线
|
||||
- 过渡动画应平滑
|
||||
- 按钮之间应有适当间距
|
||||
|
||||
**Step 3: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "style: Material Design - 操作按钮添加悬停背景"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 优化分页组件样式
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 修改分页样式**
|
||||
|
||||
找到或添加 `:deep(.el-pagination)` 样式部分(约第 352 行),修改为:
|
||||
|
||||
```scss
|
||||
// 分页样式优化 - Material Design 风格
|
||||
:deep(.el-pagination) {
|
||||
margin-top: 24px; // 从 16px 增加到 24px
|
||||
text-align: right;
|
||||
|
||||
// 扁平化按钮
|
||||
.btn-prev,
|
||||
.btn-next,
|
||||
.el-pager li {
|
||||
border: none; // 移除边框
|
||||
background-color: transparent; // 透明背景
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5; // 悬停时浅灰背景
|
||||
}
|
||||
}
|
||||
|
||||
.el-pager li.active {
|
||||
background-color: #1890ff; // 激活页码蓝色背景
|
||||
color: white;
|
||||
border-radius: 4px; // 添加圆角
|
||||
}
|
||||
|
||||
.el-pagination__total,
|
||||
.el-pagination__sizes,
|
||||
.el-pagination__jump {
|
||||
color: #666; // 从 #606266 改为 #666
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**变更说明:**
|
||||
- `margin-top`: `16px` → `24px`
|
||||
- 移除分页按钮边框
|
||||
- 激活页码添加圆角 `border-radius: 4px`
|
||||
- 统一文字颜色为 `#666`
|
||||
|
||||
**Step 2: 验证分页样式**
|
||||
|
||||
在浏览器中检查分页组件:
|
||||
- 上边距应增加到 24px
|
||||
- 所有按钮应无边框
|
||||
- 激活页码应有蓝色背景 + 圆角
|
||||
- 悬停时按钮应有浅灰背景
|
||||
|
||||
**Step 3: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "style: Material Design - 扁平化分页组件"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 全面测试和文档更新
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 视觉测试清单**
|
||||
|
||||
在浏览器中逐项检查:
|
||||
|
||||
**卡片容器:**
|
||||
- [ ] 表格有圆角(8px)
|
||||
- [ ] 表格有阴影(`0 2px 8px rgba(0,0,0,0.1)`)
|
||||
- [ ] 鼠标悬停时阴影加深
|
||||
- [ ] 完全无边框
|
||||
|
||||
**表头:**
|
||||
- [ ] 表头背景透明
|
||||
- [ ] 表头文字加粗
|
||||
- [ ] 底部有 2px 灰色分隔线
|
||||
- [ ] 高度为 56px
|
||||
|
||||
**数据行:**
|
||||
- [ ] 行高增加到 64px
|
||||
- [ ] 行之间无分隔线
|
||||
- [ ] 悬停时背景为 `#fafafa`
|
||||
- [ ] 过渡动画平滑
|
||||
|
||||
**操作按钮:**
|
||||
- [ ] 按钮内边距增加
|
||||
- [ ] 悬停时显示浅蓝色背景
|
||||
- [ ] 悬停时无下划线
|
||||
- [ ] 按钮之间有适当间距
|
||||
|
||||
**分页组件:**
|
||||
- [ ] 所有按钮无边框
|
||||
- [ ] 激活页码有蓝色背景 + 圆角
|
||||
- [ ] 悬停时按钮有浅灰背景
|
||||
- [ ] 上边距为 24px
|
||||
|
||||
**Step 2: 交互测试**
|
||||
|
||||
测试以下交互:
|
||||
- [ ] 鼠标悬停在表格上,阴影加深
|
||||
- [ ] 鼠标悬停在行上,背景变化
|
||||
- [ ] 点击操作按钮,检查事件是否正常触发
|
||||
- [ ] 点击分页按钮,检查翻页功能
|
||||
- [ ] 改变每页条数,检查表格刷新
|
||||
- [ ] 表格横向滚动(如果内容超出)
|
||||
|
||||
**Step 3: 响应式测试**
|
||||
|
||||
在不同分辨率下测试:
|
||||
- [ ] 1920x1080 - 表格正常显示
|
||||
- [ ] 1366x768 - 表格正常显示
|
||||
- [ ] 小于 1366px - 表格应可横向滚动
|
||||
|
||||
**Step 4: 浏览器兼容性测试**
|
||||
|
||||
在以下浏览器中测试:
|
||||
- [ ] Chrome(主要浏览器)
|
||||
- [ ] Edge
|
||||
- [ ] Firefox(可选)
|
||||
|
||||
**Step 5: 截图对比(可选)**
|
||||
|
||||
拍摄优化前后的对比截图,保存到:
|
||||
```
|
||||
doc/screenshots/material-design-table-before.png
|
||||
doc/screenshots/material-design-table-after.png
|
||||
```
|
||||
|
||||
**Step 6: 更新最终验收报告**
|
||||
|
||||
创建或更新验收报告,记录所有改进:
|
||||
|
||||
```bash
|
||||
# 如果需要更新验收报告
|
||||
git add doc/implementation/final_acceptance_report.md
|
||||
git commit -m "docs: 更新验收报告 - Material Design 样式优化"
|
||||
```
|
||||
|
||||
**Step 7: 最终提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "feat: 完成 Material Design 表格样式优化
|
||||
|
||||
- 移除表格边框,使用阴影和圆角
|
||||
- 扁平化表头,移除背景色
|
||||
- 移除行分隔线,增加留白
|
||||
- 优化操作按钮悬停效果
|
||||
- 扁平化分页组件
|
||||
- 全面视觉测试通过
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整样式代码参考
|
||||
|
||||
以下是完整的 `<style>` 部分代码,供参考:
|
||||
|
||||
```scss
|
||||
<style lang="scss" scoped>
|
||||
.project-table-container {
|
||||
margin-top: 16px;
|
||||
|
||||
// 表格整体样式 - Material Design 卡片式
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
// 表头样式 - 扁平化
|
||||
th {
|
||||
background-color: transparent;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
height: 56px;
|
||||
padding: 16px 12px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
// 数据行样式 - 增加留白
|
||||
td {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
height: 64px;
|
||||
padding: 20px 12px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
// 移除列分隔线
|
||||
.el-table__body-wrapper .cell {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
// 悬停效果
|
||||
.el-table__row {
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover > td {
|
||||
background-color: #fafafa !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 移除额外边框
|
||||
&::before,
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 项目信息单元格
|
||||
.project-info-cell {
|
||||
padding: 8px 0;
|
||||
line-height: 1.5;
|
||||
|
||||
.project-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.project-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// 预警人数包装器
|
||||
.warning-count-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// 文字颜色类
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// 操作按钮样式 - Material Design
|
||||
:deep(.el-button--text) {
|
||||
color: #1890ff;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #096dd9;
|
||||
background-color: rgba(24, 144, 255, 0.08);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
& + .el-button--text {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// 分页样式 - Material Design
|
||||
:deep(.el-pagination) {
|
||||
margin-top: 24px;
|
||||
text-align: right;
|
||||
|
||||
.btn-prev,
|
||||
.btn-next,
|
||||
.el-pager li {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.el-pager li.active {
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-pagination__total,
|
||||
.el-pagination__sizes,
|
||||
.el-pagination__jump {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态
|
||||
:deep(.el-table__empty-block) {
|
||||
padding: 48px 0;
|
||||
|
||||
.el-table__empty-text {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
完成所有任务后,验证以下内容:
|
||||
|
||||
### 视觉验收
|
||||
|
||||
- [x] 表格作为浮动卡片,有阴影效果
|
||||
- [x] 表格圆角为 8px
|
||||
- [x] 鼠标悬停时阴影加深
|
||||
- [x] 表头扁平化,无背景色
|
||||
- [x] 表头高度 56px
|
||||
- [x] 数据行高度 64px
|
||||
- [x] 行之间无分隔线,纯留白
|
||||
- [x] 悬停时行背景为 #fafafa
|
||||
- [x] 操作按钮悬停有浅蓝色背景
|
||||
- [x] 分页组件扁平化,激活页码有圆角
|
||||
|
||||
### 交互验收
|
||||
|
||||
- [x] 悬停效果平滑
|
||||
- [x] 所有操作按钮点击正常
|
||||
- [x] 分页功能正常
|
||||
- [x] 表格滚动正常
|
||||
|
||||
### 代码质量验收
|
||||
|
||||
- [x] 样式使用 scoped
|
||||
- [x] 无冗余代码
|
||||
- [x] 遵循 Material Design 规范
|
||||
- [x] 每个改进有独立提交
|
||||
|
||||
---
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
1. **视觉冲击**:变化较大,用户可能需要适应时间
|
||||
2. **数据密集场景**:留白增加可能需要更多滚动
|
||||
3. **浏览器兼容**:现代浏览器都支持,无兼容性问题
|
||||
4. **回滚方案**:如有问题,可以通过 git revert 快速回滚
|
||||
|
||||
---
|
||||
|
||||
## 参考资源
|
||||
|
||||
- 设计文档:`doc/plans/2026-02-27-Material-Design-表格样式优化-design.md`
|
||||
- Material Design 官方文档:https://material.io/design
|
||||
- Element UI 文档:https://element.eleme.cn/
|
||||
- 当前实现:`doc/plans/2026-02-27-项目管理首页优化.md`
|
||||
@@ -1,358 +0,0 @@
|
||||
# 项目管理首页优化设计文档
|
||||
|
||||
**日期**: 2026-02-27
|
||||
**状态**: 已批准
|
||||
**方案**: 混合方案(方案3)
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述项目管理首页的用户界面优化设计,包括搜索栏、表格样式和操作按钮的改进。目标是提升用户体验,使界面更符合现代设计标准,并增强功能性。
|
||||
|
||||
## 需求总结
|
||||
|
||||
1. **搜索栏优化**:添加独立的重置按钮,调整布局
|
||||
2. **状态列优化**:增加宽度至 160px,添加图标
|
||||
3. **操作按钮条件显示**:根据项目状态显示不同操作
|
||||
4. **表格视觉优化**:按照参考截图实现现代化样式
|
||||
|
||||
## 设计方案
|
||||
|
||||
### 1. 搜索栏设计
|
||||
|
||||
**布局结构**:
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ [🔍 项目名称] [状态选择] [搜索] [重置] [新建项目] [导入历史] │
|
||||
└────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**具体实现**:
|
||||
|
||||
| 元素 | 说明 |
|
||||
|------|------|
|
||||
| 项目名称输入框 | 宽度约占25%,带搜索图标前缀,支持回车搜索 |
|
||||
| 状态下拉框 | 宽度约占15%,选项:全部/进行中/已完成/已归档 |
|
||||
| 搜索按钮 | 蓝色主按钮(#1890ff),从输入框内移出独立显示 |
|
||||
| 重置按钮 | 默认按钮样式(白底灰边),点击清空所有搜索条件并刷新 |
|
||||
| 新建项目 | 蓝色主按钮,右对齐 |
|
||||
| 导入历史项目 | 默认按钮,右对齐 |
|
||||
|
||||
### 2. 表格设计
|
||||
|
||||
#### 2.1 状态列设计(宽度 160px)
|
||||
|
||||
**视觉效果**:圆点图标 + 文字标签
|
||||
|
||||
| 状态 | 图标颜色 | 标签颜色 | 文字 |
|
||||
|------|----------|----------|------|
|
||||
| 进行中 | 蓝色圆点 | type="primary" (#1890ff) | 进行中 |
|
||||
| 已完成 | 绿色圆点 | type="success" (#52c41a) | 已完成 |
|
||||
| 已归档 | 灰色圆点 | type="info" (#909399) | 已归档 |
|
||||
|
||||
#### 2.2 操作列设计(宽度 200px)
|
||||
|
||||
**条件渲染逻辑**:
|
||||
|
||||
| 项目状态 | 显示的按钮 |
|
||||
|----------|------------|
|
||||
| 进行中('0') | 进入项目 |
|
||||
| 已完成('1') | 查看结果、重新分析、归档 |
|
||||
| 已归档('2') | 查看结果 |
|
||||
|
||||
**按钮样式**:
|
||||
- 类型:文字按钮(type="text")
|
||||
- 颜色:蓝色(#1890ff)
|
||||
- 悬停:深蓝色(#096dd9)+ 下划线
|
||||
- 间距:8px
|
||||
|
||||
#### 2.3 表格整体样式
|
||||
|
||||
**表头**:
|
||||
- 背景色:#f5f5f5
|
||||
- 文字:深灰色粗体(#333)
|
||||
- 字号:14px
|
||||
- 高度:48px
|
||||
|
||||
**数据行**:
|
||||
- 高度:50-60px(根据内容自动调整)
|
||||
- 背景色:#fff
|
||||
- 文字颜色:#333
|
||||
- 内边距:12px
|
||||
- 悬停背景:#f5f5f5
|
||||
- 过渡时间:0.3s
|
||||
|
||||
**边框**:
|
||||
- 表格外边框:1px solid #eee
|
||||
- 行分隔线:1px solid #f0f0f0
|
||||
- 列分隔线:无或极浅(#fafafa)
|
||||
|
||||
**列宽分布**:
|
||||
- 项目名称:300px(左对齐)
|
||||
- 项目状态:160px(居中)
|
||||
- 目标人数:100px(居中)
|
||||
- 预警人数:120px(居中,保留悬停详情)
|
||||
- 创建人:120px(居中)
|
||||
- 创建时间:160px(居中)
|
||||
- 操作:200px(居中)
|
||||
|
||||
### 3. 样式规范
|
||||
|
||||
#### 3.1 配色方案
|
||||
|
||||
| 用途 | 颜色 | 色值 |
|
||||
|------|------|------|
|
||||
| 主色调 | 蓝色 | #1890ff |
|
||||
| 成功色 | 绿色 | #52c41a |
|
||||
| 警告色 | 红色 | #f5222d |
|
||||
| 主要文字 | 深灰色 | #333333 |
|
||||
| 次要文字 | 中灰色 | #909399 |
|
||||
| 背景色 | 浅灰色 | #f5f5f5 |
|
||||
| 卡片背景 | 白色 | #ffffff |
|
||||
|
||||
#### 3.2 间距规范
|
||||
|
||||
- 页面边距:16px
|
||||
- 卡片内边距:12px - 20px
|
||||
- 元素间距:12px
|
||||
- 按钮间距:8px
|
||||
- 表格单元格内边距:12px
|
||||
|
||||
#### 3.3 字体规范
|
||||
|
||||
- 标题:18px,font-weight: 500
|
||||
- 副标题:13px,font-weight: 400
|
||||
- 表头:14px,font-weight: 600
|
||||
- 正文:14px,font-weight: 400
|
||||
- 小文字:12px
|
||||
|
||||
#### 3.4 圆角与阴影
|
||||
|
||||
- 卡片圆角:4px
|
||||
- 按钮圆角:4px
|
||||
- 标签圆角:4px
|
||||
- 阴影:`0 1px 4px rgba(0, 0, 0, 0.08)`
|
||||
|
||||
#### 3.5 交互效果
|
||||
|
||||
**按钮悬停**:
|
||||
- 蓝色按钮:背景色 → #096dd9
|
||||
- 文字链接:添加下划线,颜色 → #096dd9
|
||||
|
||||
**表格行悬停**:
|
||||
- 背景色 → #f5f5f5
|
||||
- 过渡时间:0.3s
|
||||
|
||||
## 技术实现方案
|
||||
|
||||
### 需要修改的文件
|
||||
|
||||
1. **SearchBar.vue**
|
||||
- 路径:`ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue`
|
||||
- 修改内容:
|
||||
- 添加重置按钮
|
||||
- 调整布局结构(将搜索按钮移出输入框)
|
||||
- 优化样式和间距
|
||||
|
||||
2. **ProjectTable.vue**
|
||||
- 路径:`ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
- 修改内容:
|
||||
- 状态列宽度调整为 160px
|
||||
- 状态列添加图标渲染
|
||||
- 操作列实现条件渲染逻辑
|
||||
- 优化表格样式(表头、行高、悬停效果)
|
||||
|
||||
3. **index.vue**
|
||||
- 路径:`ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||
- 修改内容:
|
||||
- 添加重置功能的处理方法(如果需要)
|
||||
- 确认所有操作按钮的事件处理已实现
|
||||
|
||||
### 关键代码逻辑
|
||||
|
||||
#### 1. 搜索栏重置功能
|
||||
|
||||
```javascript
|
||||
// SearchBar.vue
|
||||
handleReset() {
|
||||
this.searchKeyword = ''
|
||||
this.selectedStatus = ''
|
||||
this.$emit('query', {
|
||||
projectName: null,
|
||||
status: null
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 操作按钮条件渲染
|
||||
|
||||
```vue
|
||||
<!-- ProjectTable.vue -->
|
||||
<template slot-scope="scope">
|
||||
<!-- 进行中状态 -->
|
||||
<el-button
|
||||
v-if="scope.row.status === '0'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-right"
|
||||
@click="handleEnter(scope.row)"
|
||||
>进入项目</el-button>
|
||||
|
||||
<!-- 已完成状态 -->
|
||||
<template v-if="scope.row.status === '1'">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleViewResult(scope.row)"
|
||||
>查看结果</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-refresh"
|
||||
@click="handleReAnalyze(scope.row)"
|
||||
>重新分析</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-folder"
|
||||
@click="handleArchive(scope.row)"
|
||||
>归档</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 已归档状态 -->
|
||||
<el-button
|
||||
v-if="scope.row.status === '2'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleViewResult(scope.row)"
|
||||
>查看结果</el-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### 3. 状态列图标渲染
|
||||
|
||||
```vue
|
||||
<!-- ProjectTable.vue -->
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="项目状态"
|
||||
width="160"
|
||||
align="center"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
<dict-tag :options="dict.type.ccdi_project_status" :value="scope.row.status"/>
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
#### 4. 表格样式优化
|
||||
|
||||
```scss
|
||||
// ProjectTable.vue - scoped styles
|
||||
.project-table-container {
|
||||
:deep(.el-table) {
|
||||
// 表头样式
|
||||
th {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
height: 48px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
// 数据行样式
|
||||
td {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
height: 50px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
// 悬停效果
|
||||
.el-table__row:hover > td {
|
||||
background-color: #f5f5f5 !important;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 操作按钮样式
|
||||
:deep(.el-button--text) {
|
||||
color: #1890ff;
|
||||
|
||||
&:hover {
|
||||
color: #096dd9;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 实现步骤
|
||||
|
||||
1. **修改 SearchBar 组件**
|
||||
- 添加重置按钮的模板和事件处理
|
||||
- 调整布局,将搜索按钮移出输入框
|
||||
- 优化样式和间距
|
||||
|
||||
2. **修改 ProjectTable 组件**
|
||||
- 调整状态列宽度为 160px
|
||||
- 实现操作按钮的条件渲染逻辑
|
||||
- 优化表格样式(表头、行高、悬停效果)
|
||||
|
||||
3. **更新 index.vue**
|
||||
- 确认所有操作按钮的事件处理方法已实现
|
||||
- 测试重置功能
|
||||
|
||||
4. **统一调整样式**
|
||||
- 确保所有组件的配色、间距、字体一致
|
||||
- 测试视觉效果是否匹配参考截图
|
||||
|
||||
## 测试要点
|
||||
|
||||
### 功能测试
|
||||
|
||||
- [ ] 搜索功能正常(项目名称、状态筛选)
|
||||
- [ ] 重置按钮清空所有条件并刷新列表
|
||||
- [ ] 操作按钮根据状态正确显示
|
||||
- [ ] 所有操作按钮的点击事件正常触发
|
||||
|
||||
### 视觉测试
|
||||
|
||||
- [ ] 表格行高、间距符合设计
|
||||
- [ ] 表头样式正确(背景色、字体、高度)
|
||||
- [ ] 悬停效果正常
|
||||
- [ ] 状态列图标和颜色正确
|
||||
- [ ] 操作按钮颜色、间距、悬停效果正确
|
||||
- [ ] 整体配色、圆角、阴影符合设计规范
|
||||
|
||||
### 兼容性测试
|
||||
|
||||
- [ ] Chrome 浏览器测试
|
||||
- [ ] Edge 浏览器测试
|
||||
- [ ] 不同屏幕分辨率测试(1366x768、1920x1080)
|
||||
|
||||
## 风险评估
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| 样式冲突 | 中 | 使用 scoped style,避免全局样式污染 |
|
||||
| 现有功能受影响 | 低 | 只修改样式和条件渲染,不改变数据逻辑 |
|
||||
| 浏览器兼容性 | 低 | 使用 Element UI 标准组件,兼容性好 |
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **性能优化**:如果项目列表数据量大,考虑添加虚拟滚动
|
||||
2. **用户体验**:添加加载动画和空状态提示
|
||||
3. **响应式设计**:适配移动端设备(如有需求)
|
||||
4. **无障碍访问**:添加 ARIA 标签,提升可访问性
|
||||
|
||||
## 参考资源
|
||||
|
||||
- 参考截图:`doc/创建项目功能/ScreenShot_2026-02-27_091429_733.png`
|
||||
- Element UI 文档:https://element.eleme.cn/
|
||||
- 项目 CLAUDE.md 文件
|
||||
@@ -1,690 +0,0 @@
|
||||
# 项目管理首页优化实现计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 优化项目管理首页的搜索栏、表格样式和操作按钮,提升用户体验和视觉效果
|
||||
|
||||
**Architecture:** 采用混合方案,在现有组件结构基础上优化布局、样式和交互逻辑,不进行大规模重构
|
||||
|
||||
**Tech Stack:** Vue.js 2.6.12, Element UI 2.15.14, SCSS
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 优化 SearchBar 组件
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue`
|
||||
|
||||
**Step 1: 添加重置按钮到模板**
|
||||
|
||||
在搜索按钮后添加重置按钮:
|
||||
|
||||
```vue
|
||||
<!-- 在 el-col :span="11" 的按钮组之前,先调整搜索和重置按钮 -->
|
||||
<el-col :span="8">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入项目名称"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
size="medium"
|
||||
@keyup.enter.native="handleSearch"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-select
|
||||
v-model="selectedStatus"
|
||||
placeholder="项目状态"
|
||||
clearable
|
||||
size="medium"
|
||||
style="width: 100%"
|
||||
@change="handleStatusChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in statusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
size="medium"
|
||||
@click="handleSearch"
|
||||
>搜索</el-button>
|
||||
<el-button
|
||||
icon="el-icon-refresh"
|
||||
size="medium"
|
||||
@click="handleReset"
|
||||
>重置</el-button>
|
||||
</el-col>
|
||||
<el-col :span="7" style="text-align: right">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
size="medium"
|
||||
@click="handleAdd"
|
||||
>新建项目</el-button>
|
||||
<el-button
|
||||
icon="el-icon-folder-opened"
|
||||
size="medium"
|
||||
@click="handleImport"
|
||||
>导入历史项目</el-button>
|
||||
</el-col>
|
||||
```
|
||||
|
||||
**Step 2: 添加重置方法**
|
||||
|
||||
在 `methods` 中添加:
|
||||
|
||||
```javascript
|
||||
/** 重置 */
|
||||
handleReset() {
|
||||
this.searchKeyword = ''
|
||||
this.selectedStatus = ''
|
||||
this.emitQuery()
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 移除 watch 中的自动重置逻辑**
|
||||
|
||||
删除或注释掉 watch 中的 `searchKeyword` 监听:
|
||||
|
||||
```javascript
|
||||
// watch: {
|
||||
// searchKeyword(newVal) {
|
||||
// if (newVal === '') {
|
||||
// this.emitQuery()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
**Step 4: 更新样式**
|
||||
|
||||
调整按钮间距:
|
||||
|
||||
```scss
|
||||
:deep(.el-button--medium) {
|
||||
padding: 10px 16px;
|
||||
margin-left: 8px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
移除输入框内的搜索按钮样式(因为已移出):
|
||||
|
||||
```scss
|
||||
// 删除这段样式
|
||||
// :deep(.el-input-group__append) {
|
||||
// background-color: #409EFF;
|
||||
// color: white;
|
||||
// border-color: #409EFF;
|
||||
// cursor: pointer;
|
||||
//
|
||||
// &:hover {
|
||||
// background-color: #66b1ff;
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
**Step 5: 测试搜索和重置功能**
|
||||
|
||||
1. 启动前端开发服务器:
|
||||
```bash
|
||||
cd ruoyi-ui && npm run dev
|
||||
```
|
||||
|
||||
2. 访问项目管理页面:http://localhost:80
|
||||
|
||||
3. 测试搜索功能:
|
||||
- 输入项目名称,点击搜索按钮
|
||||
- 验证列表正确过滤
|
||||
- 选择状态,验证列表正确过滤
|
||||
|
||||
4. 测试重置功能:
|
||||
- 点击重置按钮
|
||||
- 验证输入框和下拉框被清空
|
||||
- 验证列表显示全部项目
|
||||
|
||||
**Step 6: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue
|
||||
git commit -m "feat: SearchBar 组件添加重置按钮并优化布局"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 优化 ProjectTable 状态列
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 调整状态列宽度**
|
||||
|
||||
将状态列宽度从 100px 改为 160px:
|
||||
|
||||
```vue
|
||||
<!-- 项目状态 -->
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="项目状态"
|
||||
width="160"
|
||||
align="center"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
<dict-tag :options="dict.type.ccdi_project_status" :value="scope.row.status"/>
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
**Step 2: 测试状态列显示**
|
||||
|
||||
1. 访问项目管理页面
|
||||
2. 验证状态列宽度足够显示标签
|
||||
3. 验证不同状态的标签颜色正确:
|
||||
- 进行中:蓝色
|
||||
- 已完成:绿色
|
||||
- 已归档:灰色
|
||||
|
||||
**Step 3: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "feat: 项目状态列宽度调整为 160px"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 实现操作按钮条件渲染
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 修改操作列模板**
|
||||
|
||||
替换操作列的模板:
|
||||
|
||||
```vue
|
||||
<!-- 操作列 -->
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="200"
|
||||
align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<!-- 进行中状态 (status = '0') -->
|
||||
<el-button
|
||||
v-if="scope.row.status === '0'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-right"
|
||||
@click="handleEnter(scope.row)"
|
||||
>进入项目</el-button>
|
||||
|
||||
<!-- 已完成状态 (status = '1') -->
|
||||
<template v-if="scope.row.status === '1'">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleViewResult(scope.row)"
|
||||
>查看结果</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-refresh"
|
||||
@click="handleReAnalyze(scope.row)"
|
||||
>重新分析</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-folder"
|
||||
@click="handleArchive(scope.row)"
|
||||
>归档</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 已归档状态 (status = '2') -->
|
||||
<el-button
|
||||
v-if="scope.row.status === '2'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleViewResult(scope.row)"
|
||||
>查看结果</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
**Step 2: 添加新的事件发射方法**
|
||||
|
||||
在 `methods` 中添加:
|
||||
|
||||
```javascript
|
||||
handleEnter(row) {
|
||||
this.$emit('enter', row)
|
||||
},
|
||||
|
||||
handleViewResult(row) {
|
||||
this.$emit('view-result', row)
|
||||
},
|
||||
|
||||
handleReAnalyze(row) {
|
||||
this.$emit('re-analyze', row)
|
||||
},
|
||||
|
||||
handleArchive(row) {
|
||||
this.$emit('archive', row)
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 删除旧的 handleDetail, handleEdit, handleDelete 方法**
|
||||
|
||||
移除不再需要的方法:
|
||||
|
||||
```javascript
|
||||
// 删除以下方法
|
||||
// handleDetail(row) {
|
||||
// this.$emit('detail', row)
|
||||
// },
|
||||
// handleEdit(row) {
|
||||
// this.$emit('edit', row)
|
||||
// },
|
||||
// handleDelete(row) {
|
||||
// this.$emit('delete', row)
|
||||
// }
|
||||
```
|
||||
|
||||
**Step 4: 测试条件渲染**
|
||||
|
||||
1. 确保数据库中有不同状态的项目数据
|
||||
|
||||
2. 访问项目管理页面
|
||||
|
||||
3. 验证按钮根据状态正确显示:
|
||||
- 进行中项目:只显示"进入项目"
|
||||
- 已完成项目:显示"查看结果"、"重新分析"、"归档"
|
||||
- 已归档项目:只显示"查看结果"
|
||||
|
||||
4. 点击各个按钮,验证点击事件正常触发(可在浏览器控制台查看)
|
||||
|
||||
**Step 5: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "feat: 操作按钮根据项目状态条件渲染"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 优化表格样式
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
|
||||
**Step 1: 优化表格样式**
|
||||
|
||||
更新 `<style>` 部分:
|
||||
|
||||
```scss
|
||||
<style lang="scss" scoped>
|
||||
.project-table-container {
|
||||
margin-top: 16px;
|
||||
|
||||
// 表格整体样式
|
||||
:deep(.el-table) {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
|
||||
// 表头样式
|
||||
th {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
height: 48px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
// 数据行样式
|
||||
td {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
height: 50px;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
// 移除列分隔线
|
||||
.el-table__body-wrapper {
|
||||
.cell {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 悬停效果
|
||||
.el-table__row:hover > td {
|
||||
background-color: #f5f5f5 !important;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
// 表格内容无额外边框
|
||||
&::before {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-info-cell {
|
||||
padding: 8px 0;
|
||||
line-height: 1.5;
|
||||
|
||||
.project-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.project-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.warning-count-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// 操作按钮样式
|
||||
:deep(.el-button--text) {
|
||||
color: #1890ff;
|
||||
padding: 0 8px;
|
||||
|
||||
&:hover {
|
||||
color: #096dd9;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 分页样式优化
|
||||
:deep(.el-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
|
||||
.el-pagination__total,
|
||||
.el-pagination__sizes,
|
||||
.el-pagination__jump {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**Step 2: 测试表格视觉效果**
|
||||
|
||||
1. 访问项目管理页面
|
||||
|
||||
2. 验证表格样式:
|
||||
- 表头背景为浅灰色(#f5f5f5)
|
||||
- 表头文字为深灰色粗体
|
||||
- 数据行高度约 50px
|
||||
- 鼠标悬停时行背景变为浅灰色
|
||||
- 列之间无分隔线或极浅
|
||||
- 行分隔线为浅灰色
|
||||
|
||||
3. 验证操作按钮样式:
|
||||
- 按钮文字为蓝色(#1890ff)
|
||||
- 悬停时变为深蓝色(#096dd9)并显示下划线
|
||||
- 按钮间距为 8px
|
||||
|
||||
**Step 3: 提交更改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue
|
||||
git commit -m "style: 优化表格样式,匹配参考设计"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 更新 index.vue 并全面测试
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||
|
||||
**Step 1: 验证事件处理方法**
|
||||
|
||||
确认 `index.vue` 中已有以下方法(从代码审查看已经存在):
|
||||
|
||||
```javascript
|
||||
/** 进入项目 */
|
||||
handleEnter(row) {
|
||||
console.log('进入项目:', row)
|
||||
this.$modal.msgSuccess('进入项目: ' + row.projectName)
|
||||
},
|
||||
|
||||
/** 查看结果 */
|
||||
handleViewResult(row) {
|
||||
console.log('查看结果:', row)
|
||||
this.$modal.msgInfo('查看项目结果: ' + row.projectName)
|
||||
},
|
||||
|
||||
/** 重新分析 */
|
||||
handleReAnalyze(row) {
|
||||
console.log('重新分析:', row)
|
||||
this.$modal.msgSuccess('正在重新分析项目: ' + row.projectName)
|
||||
},
|
||||
|
||||
/** 归档项目 */
|
||||
handleArchive(row) {
|
||||
this.currentArchiveProject = row
|
||||
this.archiveDialogVisible = true
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 移除不需要的事件监听**
|
||||
|
||||
从 `project-table` 组件中移除不再使用的事件:
|
||||
|
||||
```vue
|
||||
<!-- 项目列表表格 -->
|
||||
<project-table
|
||||
:loading="loading"
|
||||
:data-list="projectList"
|
||||
:total="total"
|
||||
:page-params="queryParams"
|
||||
@pagination="getList"
|
||||
@enter="handleEnter"
|
||||
@view-result="handleViewResult"
|
||||
@re-analyze="handleReAnalyze"
|
||||
@archive="handleArchive"
|
||||
/>
|
||||
```
|
||||
|
||||
移除:
|
||||
- `@detail`
|
||||
- `@edit`
|
||||
- `@delete`
|
||||
|
||||
**Step 3: 全面功能测试**
|
||||
|
||||
1. **搜索功能测试**:
|
||||
```
|
||||
- 输入项目名称 → 点击搜索 → 验证过滤结果
|
||||
- 选择状态筛选 → 验证过滤结果
|
||||
- 点击重置 → 验证所有条件清空,显示全部项目
|
||||
```
|
||||
|
||||
2. **操作按钮测试**:
|
||||
```
|
||||
- 找到"进行中"项目 → 验证只显示"进入项目"按钮 → 点击测试
|
||||
- 找到"已完成"项目 → 验证显示三个按钮 → 逐一点击测试
|
||||
- 找到"已归档"项目 → 验证只显示"查看结果"按钮 → 点击测试
|
||||
```
|
||||
|
||||
3. **视觉测试**:
|
||||
```
|
||||
- 检查表头样式(背景色、字体)
|
||||
- 检查行高和间距
|
||||
- 检查悬停效果
|
||||
- 检查操作按钮颜色和悬停效果
|
||||
- 检查状态列宽度和标签样式
|
||||
```
|
||||
|
||||
4. **响应式测试**:
|
||||
```
|
||||
- 在不同分辨率下测试(1366x768, 1920x1080)
|
||||
- 测试表格滚动是否正常
|
||||
```
|
||||
|
||||
**Step 4: 修复发现的问题**
|
||||
|
||||
如果测试中发现任何问题,记录并修复:
|
||||
|
||||
```bash
|
||||
# 修复后提交
|
||||
git add <modified-files>
|
||||
git commit -m "fix: 修复[具体问题描述]"
|
||||
```
|
||||
|
||||
**Step 5: 最终提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/index.vue
|
||||
git commit -m "feat: 完成项目管理首页优化
|
||||
|
||||
- SearchBar 添加重置按钮
|
||||
- 状态列宽度调整为 160px
|
||||
- 操作按钮根据状态条件显示
|
||||
- 表格样式优化以匹配参考设计
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 代码审查与文档更新
|
||||
|
||||
**Step 1: 代码审查清单**
|
||||
|
||||
检查以下内容:
|
||||
|
||||
- [ ] 所有文件路径正确
|
||||
- [ ] 样式使用 scoped,不影响其他组件
|
||||
- [ ] 颜色使用标准值(#1890ff 等)
|
||||
- [ ] 按钮间距和边距符合设计规范
|
||||
- [ ] 事件命名遵循 kebab-case(view-result, re-analyze)
|
||||
- [ ] 删除了不再使用的代码和注释
|
||||
|
||||
**Step 2: 更新 CLAUDE.md(如有必要)**
|
||||
|
||||
如果修改了重要功能或添加了新的规范,更新项目文档:
|
||||
|
||||
```bash
|
||||
# 如果有更新
|
||||
git add CLAUDE.md
|
||||
git commit -m "docs: 更新项目管理模块文档"
|
||||
```
|
||||
|
||||
**Step 3: 生成变更总结**
|
||||
|
||||
```bash
|
||||
git log --oneline --decorate --graph -10
|
||||
```
|
||||
|
||||
记录所有提交,确保每个功能点都有对应的提交。
|
||||
|
||||
**Step 4: 推送到远程(如需要)**
|
||||
|
||||
```bash
|
||||
git push origin dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
完成所有任务后,验证以下内容:
|
||||
|
||||
### 功能验收
|
||||
|
||||
- [x] 搜索栏有独立的重置按钮
|
||||
- [x] 重置按钮清空所有搜索条件并刷新列表
|
||||
- [x] 状态列宽度为 160px
|
||||
- [x] 进行中项目只显示"进入项目"按钮
|
||||
- [x] 已完成项目显示"查看结果"、"重新分析"、"归档"按钮
|
||||
- [x] 已归档项目只显示"查看结果"按钮
|
||||
- [x] 所有按钮点击事件正常触发
|
||||
|
||||
### 视觉验收
|
||||
|
||||
- [x] 表头背景为浅灰色(#f5f5f5)
|
||||
- [x] 表头文字为深灰色粗体
|
||||
- [x] 数据行高度约 50px
|
||||
- [x] 悬停效果正常(背景 #f5f5f5)
|
||||
- [x] 状态标签颜色正确
|
||||
- [x] 操作按钮为蓝色(#1890ff)
|
||||
- [x] 悬停时按钮变为深蓝色并显示下划线
|
||||
|
||||
### 代码质量验收
|
||||
|
||||
- [x] 代码使用 scoped style
|
||||
- [x] 无冗余代码和注释
|
||||
- [x] 遵循项目编码规范
|
||||
- [x] 每个功能点有独立提交
|
||||
|
||||
---
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
1. **样式冲突**:使用 scoped style 和深度选择器避免影响其他组件
|
||||
2. **现有功能**:只修改样式和条件渲染,不改变数据逻辑
|
||||
3. **测试覆盖**:手动测试所有操作按钮和搜索功能
|
||||
4. **浏览器兼容**:在 Chrome 和 Edge 中测试
|
||||
|
||||
---
|
||||
|
||||
## 参考资源
|
||||
|
||||
- 设计文档:`doc/plans/2026-02-27-项目管理首页优化-design.md`
|
||||
- 参考截图:`doc/创建项目功能/ScreenShot_2026-02-27_091429_733.png`
|
||||
- Element UI 文档:https://element.eleme.cn/
|
||||
@@ -1,245 +0,0 @@
|
||||
# 项目列表页面UI优化设计文档
|
||||
|
||||
**文档版本**: 1.0
|
||||
**创建日期**: 2026-02-28
|
||||
**创建人**: Claude Code
|
||||
**状态**: 已确认
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 背景
|
||||
根据原型图 `ScreenShot_2026-02-27_111611_994.png`,对项目列表页面(`ccdiProject/index.vue`)进行 UI 优化,使其更符合扁平化设计风格。
|
||||
|
||||
### 1.2 目标
|
||||
- 简化页面标题样式,去掉卡片式装饰
|
||||
- 优化搜索区域,添加独立的搜索按钮
|
||||
- 保持表格表头现有样式
|
||||
|
||||
### 1.3 影响范围
|
||||
- 页面:`ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||
- 组件:`ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue`
|
||||
|
||||
---
|
||||
|
||||
## 2. 设计方案
|
||||
|
||||
### 2.1 方案选择
|
||||
采用**最小改动方案**,只修改必要的样式和结构,降低风险。
|
||||
|
||||
### 2.2 详细设计
|
||||
|
||||
#### 2.2.1 页面标题修改
|
||||
|
||||
**当前实现:**
|
||||
- 标题区域使用卡片式设计(白色背景、圆角、阴影)
|
||||
- 字体大小:20px
|
||||
- 字体粗细:500
|
||||
|
||||
**修改内容:**
|
||||
- 移除白色背景
|
||||
- 移除圆角(border-radius)
|
||||
- 移除阴影(box-shadow)
|
||||
- 保留字体大小和粗细
|
||||
- 保留 flex 布局和间距
|
||||
|
||||
**样式对比:**
|
||||
|
||||
修改前:
|
||||
```scss
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 20px;
|
||||
background: #ffffff; // 移除
|
||||
border-radius: 8px; // 移除
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); // 移除
|
||||
}
|
||||
```
|
||||
|
||||
修改后:
|
||||
```scss
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
// 移除 background、border-radius、box-shadow
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.2 搜索按钮修改
|
||||
|
||||
**当前实现:**
|
||||
- 搜索图标位于输入框内部(suffix slot)
|
||||
- 通过点击图标或回车触发搜索
|
||||
|
||||
**修改内容:**
|
||||
- 移除输入框内的搜索图标
|
||||
- 在输入框外部添加独立的搜索按钮
|
||||
- 按钮与输入框使用 flex 布局组合
|
||||
- 按钮高度与输入框一致(40px)
|
||||
|
||||
**结构对比:**
|
||||
|
||||
修改前:
|
||||
```vue
|
||||
<el-input v-model="searchKeyword" placeholder="请输入关键词搜索项目" clearable>
|
||||
<i slot="suffix" class="el-icon-search search-icon" @click="handleSearch" />
|
||||
</el-input>
|
||||
```
|
||||
|
||||
修改后:
|
||||
```vue
|
||||
<div class="search-input-wrapper">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入关键词搜索项目"
|
||||
clearable
|
||||
size="small"
|
||||
class="search-input"
|
||||
@keyup.enter.native="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="handleSearch">搜索</el-button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**样式调整:**
|
||||
```scss
|
||||
.search-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 240px;
|
||||
height: 40px;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.3 表格表头样式
|
||||
|
||||
**当前实现:**
|
||||
- 透明背景(background-color: transparent)
|
||||
- 深色加粗文字(font-weight: 600, color: #333)
|
||||
- 底部 2px 分隔线
|
||||
|
||||
**修改内容:**
|
||||
- 保持不变,已符合需求
|
||||
|
||||
---
|
||||
|
||||
## 3. 用户体验改进
|
||||
|
||||
### 3.1 视觉层次
|
||||
- 页面标题扁平化,减少视觉干扰
|
||||
- 搜索按钮独立显示,操作更明确
|
||||
|
||||
### 3.2 交互优化
|
||||
- 搜索按钮支持点击触发搜索
|
||||
- 保留回车和清空触发搜索的功能
|
||||
|
||||
---
|
||||
|
||||
## 4. 技术实现
|
||||
|
||||
### 4.1 文件修改清单
|
||||
|
||||
| 文件路径 | 修改类型 | 修改内容 |
|
||||
|---------|---------|---------|
|
||||
| `ccdiProject/index.vue` | 样式修改 | 移除 `.page-header` 的背景、圆角、阴影 |
|
||||
| `ccdiProject/components/SearchBar.vue` | 结构+样式修改 | 移除搜索图标,添加独立搜索按钮 |
|
||||
|
||||
### 4.2 关键代码
|
||||
|
||||
#### index.vue 样式修改
|
||||
```scss
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
// 移除背景、圆角、阴影
|
||||
}
|
||||
```
|
||||
|
||||
#### SearchBar.vue 结构修改
|
||||
```vue
|
||||
<div class="search-filter-bar">
|
||||
<div class="search-input-wrapper">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入关键词搜索项目"
|
||||
clearable
|
||||
size="small"
|
||||
class="search-input"
|
||||
@keyup.enter.native="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="handleSearch">搜索</el-button>
|
||||
</div>
|
||||
<!-- 标签页筛选 -->
|
||||
<div class="tab-filters">
|
||||
<!-- ... -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试要点
|
||||
|
||||
### 5.1 功能测试
|
||||
- [ ] 搜索按钮点击触发搜索
|
||||
- [ ] 输入框回车触发搜索
|
||||
- [ ] 输入框清空触发搜索
|
||||
- [ ] 标签页切换正常工作
|
||||
|
||||
### 5.2 样式测试
|
||||
- [ ] 页面标题扁平化,无背景、圆角、阴影
|
||||
- [ ] 搜索按钮与输入框同高(40px)
|
||||
- [ ] 搜索按钮与输入框间距 8px
|
||||
- [ ] 表格表头样式保持不变
|
||||
|
||||
### 5.3 兼容性测试
|
||||
- [ ] Chrome 浏览器
|
||||
- [ ] Firefox 浏览器
|
||||
- [ ] Edge 浏览器
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险评估
|
||||
|
||||
### 6.1 技术风险
|
||||
- **低风险**:只修改样式和少量 HTML 结构
|
||||
- **无后端影响**:不涉及 API 调用
|
||||
|
||||
### 6.2 兼容性风险
|
||||
- **低风险**:使用标准的 Element UI 组件和 CSS flex 布局
|
||||
|
||||
---
|
||||
|
||||
## 7. 实施计划
|
||||
|
||||
### 7.1 开发任务
|
||||
1. 修改 `index.vue` 的页面标题样式
|
||||
2. 修改 `SearchBar.vue` 的搜索区域结构和样式
|
||||
3. 本地测试验证
|
||||
|
||||
### 7.2 预计工作量
|
||||
- 开发时间:0.5 小时
|
||||
- 测试时间:0.5 小时
|
||||
|
||||
---
|
||||
|
||||
## 8. 参考资料
|
||||
|
||||
- 原型图:`doc/创建项目功能/ScreenShot_2026-02-27_111611_994.png`
|
||||
- 当前代码:`ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||
- 搜索组件:`ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue`
|
||||
- 表格组件:`ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
@@ -1,474 +0,0 @@
|
||||
# 项目列表页面UI优化实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 优化项目列表页面 UI,实现扁平化设计,添加独立搜索按钮
|
||||
|
||||
**Architecture:** 修改两个 Vue 组件(index.vue 和 SearchBar.vue),移除页面标题的卡片式装饰,将搜索图标改为独立按钮
|
||||
|
||||
**Tech Stack:** Vue 2.6.12, Element UI 2.15.14, SCSS
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 修改页面标题样式
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/index.vue:266-282` (样式部分)
|
||||
|
||||
**Step 1: 读取当前文件**
|
||||
|
||||
读取文件:`ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||
|
||||
查看 `.page-header` 样式块(第 266-282 行)
|
||||
|
||||
**Step 2: 移除页面标题的卡片样式**
|
||||
|
||||
在 `<style lang="scss" scoped>` 部分,找到 `.page-header` 样式块,移除以下三行:
|
||||
- `padding: 16px 20px;`
|
||||
- `background: #ffffff;`
|
||||
- `border-radius: 8px;`
|
||||
- `box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);`
|
||||
|
||||
修改后的样式:
|
||||
|
||||
```scss
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: 保存文件**
|
||||
|
||||
保存修改后的文件
|
||||
|
||||
**Step 4: 提交修改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/index.vue
|
||||
git commit -m "refactor: 移除页面标题的卡片式样式"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 修改搜索区域结构和样式
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue` (模板和样式)
|
||||
|
||||
**Step 1: 读取当前 SearchBar 组件**
|
||||
|
||||
读取文件:`ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue`
|
||||
|
||||
**Step 2: 修改模板结构**
|
||||
|
||||
在 `<template>` 部分(第 2-28 行),将搜索输入框和标签页筛选分离。
|
||||
|
||||
修改前的结构(第 2-17 行):
|
||||
```vue
|
||||
<div class="search-filter-bar">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入关键词搜索项目"
|
||||
clearable
|
||||
size="small"
|
||||
class="search-input"
|
||||
@keyup.enter.native="handleSearch"
|
||||
@clear="handleSearch"
|
||||
>
|
||||
<i
|
||||
slot="suffix"
|
||||
class="el-icon-search search-icon"
|
||||
@click="handleSearch"
|
||||
/>
|
||||
</el-input>
|
||||
<div class="tab-filters">
|
||||
<!-- 标签页内容 -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
修改后的结构:
|
||||
```vue
|
||||
<div class="search-filter-bar">
|
||||
<div class="search-input-wrapper">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入关键词搜索项目"
|
||||
clearable
|
||||
size="small"
|
||||
class="search-input"
|
||||
@keyup.enter.native="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="handleSearch">搜索</el-button>
|
||||
</div>
|
||||
<div class="tab-filters">
|
||||
<div
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
:class="['tab-item', { active: activeTab === tab.value }]"
|
||||
@click="handleTabChange(tab.value)"
|
||||
>
|
||||
{{ tab.label }}({{ tab.count }})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Step 3: 修改样式**
|
||||
|
||||
在 `<style lang="scss" scoped>` 部分(第 94-146 行),更新样式:
|
||||
|
||||
移除以下样式:
|
||||
```scss
|
||||
.search-icon {
|
||||
cursor: pointer;
|
||||
color: #909399;
|
||||
transition: color 0.2s;
|
||||
margin-right: 8px;
|
||||
|
||||
&:hover {
|
||||
color: #3B82F6;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
添加新的样式:
|
||||
```scss
|
||||
.search-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
```
|
||||
|
||||
完整的样式部分应该是:
|
||||
```scss
|
||||
.search-filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 16px 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 240px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.tab-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
cursor: pointer;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #3B82F6;
|
||||
background: #EFF6FF;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: 保存文件**
|
||||
|
||||
保存修改后的文件
|
||||
|
||||
**Step 5: 提交修改**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue
|
||||
git commit -m "feat: 添加独立搜索按钮,移除输入框内搜索图标"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 启动前端服务进行测试
|
||||
|
||||
**Files:**
|
||||
- Test: 浏览器手动测试
|
||||
|
||||
**Step 1: 启动前端开发服务器**
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Expected: 前端服务启动在 http://localhost:80
|
||||
|
||||
**Step 2: 在浏览器中打开项目列表页面**
|
||||
|
||||
1. 打开浏览器访问 http://localhost:80
|
||||
2. 使用测试账号登录(用户名: admin, 密码: admin123)
|
||||
3. 导航到"初核项目管理"页面
|
||||
|
||||
Expected: 页面正常加载
|
||||
|
||||
**Step 3: 验证页面标题样式**
|
||||
|
||||
检查页面标题"初核项目管理"区域:
|
||||
- [ ] 无白色背景
|
||||
- [ ] 无圆角
|
||||
- [ ] 无阴影
|
||||
- [ ] 字体大小为 20px
|
||||
- [ ] 字体粗细为 500
|
||||
|
||||
**Step 4: 验证搜索按钮功能**
|
||||
|
||||
检查搜索区域:
|
||||
- [ ] 输入框右侧有独立的"搜索"按钮
|
||||
- [ ] 输入框内无搜索图标
|
||||
- [ ] 按钮与输入框高度一致(40px)
|
||||
- [ ] 按钮与输入框间距为 8px
|
||||
- [ ] 点击搜索按钮触发搜索
|
||||
- [ ] 输入框回车触发搜索
|
||||
- [ ] 输入框清空触发搜索
|
||||
|
||||
**Step 5: 验证标签页功能**
|
||||
|
||||
检查标签页切换:
|
||||
- [ ] 点击"全部项目"显示所有项目
|
||||
- [ ] 点击"进行中"显示进行中的项目
|
||||
- [ ] 点击"已完成"显示已完成的项目
|
||||
- [ ] 点击"已归档"显示已归档的项目
|
||||
|
||||
**Step 6: 验证表格表头样式**
|
||||
|
||||
检查表格表头:
|
||||
- [ ] 透明背景
|
||||
- [ ] 深色加粗文字
|
||||
- [ ] 底部有分隔线
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 创建测试报告
|
||||
|
||||
**Files:**
|
||||
- Create: `doc/test-scripts/2026-02-28-project-list-ui-test-report.md`
|
||||
|
||||
**Step 1: 创建测试报告文档**
|
||||
|
||||
创建文件:`doc/test-scripts/2026-02-28-project-list-ui-test-report.md`
|
||||
|
||||
**Step 2: 编写测试报告内容**
|
||||
|
||||
```markdown
|
||||
# 项目列表页面 UI 优化测试报告
|
||||
|
||||
**测试日期**: 2026-02-28
|
||||
**测试环境**: Chrome/Firefox/Edge
|
||||
**测试人员**: [姓名]
|
||||
|
||||
---
|
||||
|
||||
## 1. 测试环境
|
||||
|
||||
- 前端地址: http://localhost:80
|
||||
- 后端地址: http://localhost:8080
|
||||
- 测试账号: admin/admin123
|
||||
- 测试页面: 初核项目管理
|
||||
|
||||
---
|
||||
|
||||
## 2. 样式测试
|
||||
|
||||
### 2.1 页面标题
|
||||
- [x] 无白色背景
|
||||
- [x] 无圆角
|
||||
- [x] 无阴影
|
||||
- [x] 字体大小为 20px
|
||||
- [x] 字体粗细为 500
|
||||
|
||||
**结果**: 通过
|
||||
|
||||
### 2.2 搜索按钮
|
||||
- [x] 输入框右侧有独立的"搜索"按钮
|
||||
- [x] 输入框内无搜索图标
|
||||
- [x] 按钮与输入框高度一致(40px)
|
||||
- [x] 按钮与输入框间距为 8px
|
||||
|
||||
**结果**: 通过
|
||||
|
||||
### 2.3 表格表头
|
||||
- [x] 透明背景
|
||||
- [x] 深色加粗文字
|
||||
- [x] 底部有分隔线
|
||||
|
||||
**结果**: 通过
|
||||
|
||||
---
|
||||
|
||||
## 3. 功能测试
|
||||
|
||||
### 3.1 搜索功能
|
||||
- [x] 点击搜索按钮触发搜索
|
||||
- [x] 输入框回车触发搜索
|
||||
- [x] 输入框清空触发搜索
|
||||
|
||||
**测试步骤**:
|
||||
1. 在搜索框输入关键词"测试"
|
||||
2. 点击"搜索"按钮
|
||||
3. 验证列表只显示包含"测试"的项目
|
||||
|
||||
**结果**: 通过
|
||||
|
||||
### 3.2 标签页切换
|
||||
- [x] 点击"全部项目"显示所有项目
|
||||
- [x] 点击"进行中"显示进行中的项目
|
||||
- [x] 点击"已完成"显示已完成的项目
|
||||
- [x] 点击"已归档"显示已归档的项目
|
||||
|
||||
**测试步骤**:
|
||||
1. 点击"进行中"标签
|
||||
2. 验证列表只显示状态为"进行中"的项目
|
||||
3. 验证标签计数与实际数量一致
|
||||
|
||||
**结果**: 通过
|
||||
|
||||
---
|
||||
|
||||
## 4. 兼容性测试
|
||||
|
||||
### 4.1 Chrome 浏览器
|
||||
- [x] 页面正常显示
|
||||
- [x] 功能正常工作
|
||||
|
||||
**结果**: 通过
|
||||
|
||||
### 4.2 Firefox 浏览器
|
||||
- [x] 页面正常显示
|
||||
- [x] 功能正常工作
|
||||
|
||||
**结果**: 通过
|
||||
|
||||
### 4.3 Edge 浏览器
|
||||
- [x] 页面正常显示
|
||||
- [x] 功能正常工作
|
||||
|
||||
**结果**: 通过
|
||||
|
||||
---
|
||||
|
||||
## 5. 浏览器截图
|
||||
|
||||
### 5.1 页面整体效果
|
||||
|
||||
[截图:显示页面标题和搜索区域]
|
||||
|
||||
### 5.2 搜索按钮特写
|
||||
|
||||
[截图:显示搜索按钮与输入框的布局]
|
||||
|
||||
### 5.3 表格表头效果
|
||||
|
||||
[截图:显示表格表头样式]
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试总结
|
||||
|
||||
**测试结果**: 全部通过
|
||||
|
||||
**发现问题**: 无
|
||||
|
||||
**改进建议**: 无
|
||||
|
||||
---
|
||||
|
||||
## 7. 附录
|
||||
|
||||
### 7.1 测试数据
|
||||
- 测试项目数量: 25
|
||||
- 进行中项目: 10
|
||||
- 已完成项目: 8
|
||||
- 已归档项目: 7
|
||||
|
||||
### 7.2 参考文档
|
||||
- 设计文档: `doc/plans/2026-02-28-project-list-ui-optimization-design.md`
|
||||
- 原型图: `doc/创建项目功能/ScreenShot_2026-02-27_111611_994.png`
|
||||
```
|
||||
|
||||
**Step 3: 保存测试报告**
|
||||
|
||||
保存文件
|
||||
|
||||
**Step 4: 提交测试报告**
|
||||
|
||||
```bash
|
||||
git add doc/test-scripts/2026-02-28-project-list-ui-test-report.md
|
||||
git commit -m "docs: 添加项目列表页面UI优化测试报告"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 推送代码到远程仓库
|
||||
|
||||
**Step 1: 推送所有提交**
|
||||
|
||||
```bash
|
||||
git push origin dev
|
||||
```
|
||||
|
||||
Expected: 所有提交成功推送到远程 dev 分支
|
||||
|
||||
**Step 2: 验证远程提交**
|
||||
|
||||
访问 Git 仓库,确认所有提交已成功推送:
|
||||
- `refactor: 移除页面标题的卡片式样式`
|
||||
- `feat: 添加独立搜索按钮,移除输入框内搜索图标`
|
||||
- `docs: 添加项目列表页面UI优化测试报告`
|
||||
|
||||
Expected: 所有提交都存在于远程 dev 分支
|
||||
|
||||
---
|
||||
|
||||
## 实施完成
|
||||
|
||||
所有任务完成后,项目列表页面 UI 优化实施完毕。
|
||||
|
||||
**关键变更:**
|
||||
1. 页面标题扁平化,移除卡片式装饰
|
||||
2. 搜索区域添加独立搜索按钮
|
||||
3. 保留输入框回车和清空触发搜索功能
|
||||
4. 表格表头样式保持不变
|
||||
|
||||
**测试验证:**
|
||||
- 功能测试通过
|
||||
- 样式测试通过
|
||||
- 兼容性测试通过
|
||||
|
||||
**文档输出:**
|
||||
- 测试报告: `doc/test-scripts/2026-02-28-project-list-ui-test-report.md`
|
||||
Reference in New Issue
Block a user