22 Commits

Author SHA1 Message Date
wkc
a5072c5e7a Merge branch 'feature/project-detail-nav-menu' into dev 2026-03-04 11:09:30 +08:00
wkc
206754adb4 test: 添加项目创建功能测试脚本和文档
- 添加 Bash 测试脚本 (test-project-creation.sh)
- 添加 PowerShell 测试脚本 (test-project-creation.ps1)
- 添加批处理测试脚本 (test-project-creation.bat)
- 添加测试说明文档 (README.md)
- 支持4个测试场景:成功、参数校验、查询列表、异常处理
- 包含数据库验证和事务回滚验证
2026-03-04 11:04:16 +08:00
wkc
a5a3e36d48 refactor(ccdiProject): 将下拉菜单项提升为顶层菜单项实现扁平化导航 2026-03-04 11:03:09 +08:00
wkc
9ffcb22929 fix(ccdiProject): 统一菜单项高度为 40px 实现垂直对齐 2026-03-04 10:57:02 +08:00
wkc
b9ca44cbca feat: createProject方法集成流水分析平台调用 2026-03-04 10:56:34 +08:00
wkc
9916f641ac feat: 实现callLsfxPlatform方法调用流水分析平台 2026-03-04 10:55:31 +08:00
wkc
4cf76a13a0 feat: CcdiProjectServiceImpl注入LsfxAnalysisClient依赖 2026-03-04 10:54:55 +08:00
wkc
5ac8d0bb99 fix(ccdiProject): 修复导航菜单垂直居中对齐问题 2026-03-04 10:52:34 +08:00
wkc
5e85533062 style(ccdiProject): 添加导航菜单响应式布局支持 2026-03-04 10:41:14 +08:00
wkc
4678f2cd44 style(ccdiProject): 添加导航菜单简洁链接风格样式 2026-03-04 10:40:30 +08:00
wkc
9f2a2b7c17 feat(ccdiProject): 添加菜单选择处理方法并清理废弃代码 2026-03-04 10:39:27 +08:00
wkc
6d322ea7da feat(ccdiProject): 替换按钮组为导航菜单并使用动态组件 2026-03-04 10:38:18 +08:00
wkc
38adbaed90 feat(ccdiProject): 导入子组件并添加菜单状态数据 2026-03-04 10:36:58 +08:00
wkc
b0f5422593 feat(ccdiProject): 添加流水明细查询占位组件 2026-03-04 10:35:23 +08:00
wkc
bf68f5e7ee feat(ccdiProject): 添加专项排查占位组件 2026-03-04 10:32:48 +08:00
wkc
bd2d7b80dc feat(ccdiProject): 添加结果总览占位组件 2026-03-04 10:31:59 +08:00
wkc
1feb295a93 feat(ccdiProject): 添加参数配置占位组件 2026-03-04 10:31:15 +08:00
wkc
c7b140c5db chore: 清理 Python 缓存文件 2026-03-04 10:28:20 +08:00
wkc
6e30a0ccf4 docs: 添加项目详情页面导航菜单改造实施计划 2026-03-04 10:22:53 +08:00
wkc
33994531b0 docs: 添加项目详情页面导航菜单改造设计文档 2026-03-04 10:19:25 +08:00
wkc
e43d2ac0f6 feat: CcdiProjectVO添加lsfxProjectId字段 2026-03-04 09:55:38 +08:00
wkc
4a2d993a91 feat: CcdiProject实体类添加lsfxProjectId字段 2026-03-04 09:55:10 +08:00
23 changed files with 2813 additions and 95 deletions

View File

@@ -23,6 +23,13 @@
<artifactId>ruoyi-common</artifactId>
</dependency>
<!-- 流水分析模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ccdi-lsfx</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
@@ -36,6 +43,13 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -50,6 +50,9 @@ public class CcdiProject implements Serializable {
/** 低风险人数 */
private Integer lowRiskCount;
/** 流水分析平台项目ID */
private Integer lsfxProjectId;
/** 删除标志0-存在2-删除 */
@TableLogic
private String delFlag;

View File

@@ -41,6 +41,9 @@ public class CcdiProjectVO {
/** 低风险人数 */
private Integer lowRiskCount;
/** 流水分析平台项目ID */
private Integer lsfxProjectId;
/** 创建时间 */
private Date createTime;

View File

@@ -10,9 +10,13 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
import com.ruoyi.lsfx.domain.request.GetTokenRequest;
import com.ruoyi.lsfx.domain.response.GetTokenResponse;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 项目Service实现类
@@ -25,21 +29,32 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Resource
private CcdiProjectMapper projectMapper;
@Resource
private LsfxAnalysisClient lsfxAnalysisClient;
@Override
@Transactional(rollbackFor = Exception.class)
public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) {
// 1. 调用流水分析平台获取projectId
Integer lsfxProjectId = callLsfxPlatform(dto.getProjectName());
// 2. 创建项目实体
CcdiProject project = new CcdiProject();
BeanUtils.copyProperties(dto, project);
// 设置默认值
// 3. 设置默认值和流水分析平台ID
project.setStatus("0"); // 进行中
project.setIsArchived(0); // 未归档
project.setTargetCount(0);
project.setHighRiskCount(0);
project.setMediumRiskCount(0);
project.setLowRiskCount(0);
project.setLsfxProjectId(lsfxProjectId); // 设置流水分析平台ID
// 4. 保存到数据库
projectMapper.insert(project);
// 5. 返回VO
CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(project, vo);
return vo;
@@ -120,4 +135,43 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
return vo;
}
/**
* 调用流水分析平台获取projectId
*
* @param projectName 项目名称
* @return 流水分析平台项目ID
* @throws ServiceException 调用失败或响应无效时抛出
*/
private Integer callLsfxPlatform(String projectName) {
// 构建请求参数
GetTokenRequest request = new GetTokenRequest();
request.setProjectNo("902000_" + System.currentTimeMillis());
request.setEntityName(projectName);
request.setUserId("902001");
request.setUserName("902001");
request.setRole("VIEWER");
request.setOrgCode("902000");
request.setAnalysisType("-1");
request.setDepartmentCode("902000");
// 调用流水分析平台(异常处理和日志已在 LsfxAnalysisClient 中完成)
GetTokenResponse response = lsfxAnalysisClient.getToken(request);
// 业务层校验:确保响应有效
if (response == null || response.getData() == null) {
throw new ServiceException("流水分析平台响应数据为空");
}
if (response.getData().getProjectId() == null) {
throw new ServiceException("流水分析平台返回的projectId为空");
}
// 校验返回码
if (!"200".equals(response.getCode())) {
throw new ServiceException("流水分析平台返回错误: " + response.getMessage());
}
return response.getData().getProjectId();
}
}

View File

@@ -0,0 +1,334 @@
# 项目详情页面导航菜单改造设计文档
## 概述
将项目详情页面detail.vue右侧的按钮组改为水平导航菜单使用 Element UI Menu 组件实现简洁链接风格。
## 当前问题
项目详情页面右侧的操作按钮(上传数据、参数配置、初核结果)占用空间较大,视觉层级不够清晰,交互方式不够统一。
## 解决方案
### 核心设计
- 使用 Element UI 的 `el-menu` 组件(水平模式)
- 菜单放在标题右侧,右对齐
- "上传数据"和"参数配置"作为普通菜单项
- "初核结果"保留下拉菜单结构,包含三个子项:结果总览、专项排查、流水明细查询
- 采用简洁链接风格:透明背景 + 底部下划线激活效果
### 视觉风格
- 默认状态:灰色文字(#606266),透明背景
- Hover 状态:浅灰背景(#f5f7fa),深色文字(#303133
- 激活状态:蓝色文字(#1890ff+ 底部 2px 蓝色下划线
- 下拉菜单:白色背景,激活项浅蓝背景(#e6f7ff
## 技术设计
### 1. 组件结构
#### detail.vue 模板改造
```vue
<div class="header-right">
<el-menu
:default-active="activeTab"
mode="horizontal"
@select="handleMenuSelect"
class="nav-menu"
>
<el-menu-item index="upload">上传数据</el-menu-item>
<el-menu-item index="config">参数配置</el-menu-item>
<el-submenu index="result">
<template slot="title">初核结果</template>
<el-menu-item index="overview">结果总览</el-menu-item>
<el-menu-item index="special">专项排查</el-menu-item>
<el-menu-item index="detail">流水明细查询</el-menu-item>
</el-submenu>
</el-menu>
</div>
<!-- 动态组件渲染区域 -->
<component
:is="currentComponent"
:project-id="projectId"
:project-info="projectInfo"
@menu-change="handleMenuChange"
@data-uploaded="handleDataUploaded"
@name-selected="handleNameSelected"
@generate-report="handleGenerateReport"
@fetch-bank-info="handleFetchBankInfo"
/>
```
### 2. 数据结构
```javascript
data() {
return {
activeTab: 'upload', // 当前激活的菜单项索引
currentComponent: 'UploadData', // 当前显示的组件名称
// ... 其他现有数据
}
}
```
### 3. 交互逻辑
```javascript
methods: {
/** 菜单选择事件 */
handleMenuSelect(index) {
this.activeTab = index;
// 组件映射
const componentMap = {
'upload': 'UploadData',
'config': 'ParamConfig',
'overview': 'PreliminaryCheck',
'special': 'SpecialCheck',
'detail': 'DetailQuery'
};
this.currentComponent = componentMap[index] || 'UploadData';
},
// ... 其他现有方法
}
```
### 4. 组件导入
```javascript
import UploadData from "./components/detail/UploadData";
import ParamConfig from "./components/detail/ParamConfig";
import PreliminaryCheck from "./components/detail/PreliminaryCheck";
import SpecialCheck from "./components/detail/SpecialCheck";
import DetailQuery from "./components/detail/DetailQuery";
export default {
name: "ProjectDetail",
components: {
UploadData,
ParamConfig,
PreliminaryCheck,
SpecialCheck,
DetailQuery,
},
// ...
}
```
## 样式设计
### 1. 导航菜单样式
```scss
.header-right {
.nav-menu {
// 移除默认背景色和边框
background-color: transparent;
border-bottom: none;
// 菜单项基础样式
.el-menu-item,
.el-submenu__title {
font-size: 14px;
color: #606266;
padding: 0 16px;
height: 40px;
line-height: 40px;
border-bottom: 2px solid transparent;
&:hover {
background-color: #f5f7fa;
color: #303133;
}
}
// 激活状态:底部下划线 + 蓝色文字
.el-menu-item.is-active {
color: #1890ff;
border-bottom: 2px solid #1890ff;
background-color: transparent;
}
// 下拉菜单激活状态
.el-submenu.is-active > .el-submenu__title {
color: #1890ff;
border-bottom: 2px solid #1890ff;
}
// 下拉菜单图标
.el-submenu__icon-arrow {
margin-left: 4px;
}
}
}
```
### 2. 下拉菜单弹窗样式
```scss
::v-deep .el-menu--popup {
min-width: 140px;
.el-menu-item {
font-size: 14px;
&:hover {
background-color: #f5f7fa;
}
&.is-active {
color: #1890ff;
background-color: #e6f7ff;
}
}
}
```
### 3. 响应式适配
```scss
@media (max-width: 768px) {
.detail-header {
flex-direction: column;
align-items: flex-start;
.header-right {
width: 100%;
margin-top: 12px;
.nav-menu {
width: 100%;
display: flex;
justify-content: flex-start;
.el-menu-item,
.el-submenu {
flex: 1;
text-align: center;
padding: 0 8px;
font-size: 13px;
}
}
}
}
}
```
## 组件规范
### Props 接口
所有子组件应接收相同的 props
```javascript
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
```
### Events 接口
```javascript
this.$emit('data-uploaded', { type: 'xxx' });
this.$emit('generate-report');
this.$emit('fetch-bank-info');
this.$emit('menu-change', { key, route });
this.$emit('name-selected', nameList);
```
## 实施步骤
### 第一步:修改 detail.vue 文件
1. 替换 header-right 中的按钮为 el-menu 组件
2. 添加 activeTab 和 currentComponent 数据字段
3. 实现 handleMenuSelect 方法
4. 添加动态组件渲染区域
5. 导入所有子组件
### 第二步:添加样式
1. 添加导航菜单的自定义样式
2. 添加下拉菜单样式
3. 添加响应式样式
### 第三步:创建占位组件
为未实现的功能创建占位组件:
- ParamConfig.vue
- PreliminaryCheck.vue
- SpecialCheck.vue
- DetailQuery.vue
### 第四步:测试验证
- 功能测试:菜单切换、下拉菜单交互
- 视觉测试:样式符合设计要求
- 响应式测试:移动端布局正常
## 技术栈
- Element UI Menu 组件(`el-menu`, `el-menu-item`, `el-submenu`
- Vue 动态组件(`<component :is="...">`
- Scoped CSS 样式覆盖
- Vue 2.6.12
- Element UI 2.15.14
## 预期效果
### 视觉效果
- 菜单项横向排列在标题右侧,右对齐
- 简洁链接风格,无背景色和边框
- 激活项显示蓝色文字和底部下划线
- 下拉菜单样式统一
### 交互效果
- 点击菜单项切换组件URL 不变
- 下拉菜单点击外部区域可关闭
- 组件切换流畅,数据正确传递
- 响应式布局在移动端自适应
## 代码改动量估算
- detail.vue 文件改动:约 80-100 行(模板 + 脚本 + 样式)
- 占位组件创建4 个文件,每个约 20 行
- 总代码量:约 150-180 行
## 风险与注意事项
1. **组件文件不存在**ParamConfig、PreliminaryCheck 等组件需要创建占位组件
2. **样式覆盖**Element UI 默认样式覆盖需要使用 `::v-deep``/deep/`
3. **事件传递**:确保所有子组件的事件正确向上传递
4. **路由监听**:移除原有路由相关的逻辑,改为组件状态管理
## 后续优化建议
1. 添加菜单切换动画效果
2. 为占位组件实现完整功能
3. 添加面包屑导航支持
4. 支持菜单项权限控制
5. 添加快捷键支持Ctrl+Tab 切换)
## 测试清单
- [ ] 点击"上传数据"菜单,显示 UploadData 组件
- [ ] 点击"参数配置"菜单,显示占位组件或 ParamConfig 组件
- [ ] 点击"初核结果"菜单,展开下拉菜单
- [ ] 点击下拉菜单子项,切换到对应组件
- [ ] 激活菜单项显示底部下划线
- [ ] Hover 菜单项显示浅灰背景
- [ ] 下拉菜单点击外部区域关闭
- [ ] 组件切换时 projectId 和 projectInfo 正确传递
- [ ] 移动端菜单响应式布局正常
- [ ] 现有功能不受影响(返回按钮、项目信息显示等)

View File

@@ -0,0 +1,960 @@
# 项目详情页面导航菜单改造实施计划
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 将项目详情页面右侧的按钮组改为水平导航菜单,使用 Element UI Menu 组件实现简洁链接风格,支持菜单切换组件内容。
**Architecture:** 使用 Element UI 的 `el-menu` 组件(水平模式)替换现有的按钮组,通过 Vue 动态组件(`<component :is="...">`)实现内容切换。菜单项包括"上传数据"、"参数配置"和"初核结果"下拉菜单(含三个子项)。采用简洁链接风格,激活状态显示底部下划线。
**Tech Stack:** Vue 2.6.12, Element UI 2.15.14, Scoped CSS
---
## 前置检查
**验证当前项目状态:**
```bash
cd D:/ccdi/ccdi
git status
```
预期:工作目录干净,或只有 CLAUDE.md 修改
**验证文件存在:**
```bash
ls ruoyi-ui/src/views/ccdiProject/detail.vue
ls ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue
```
预期:两个文件都存在
---
## Task 1: 创建占位组件 ParamConfig
**Files:**
- Create: `ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue`
**Step 1: 创建 ParamConfig 占位组件**
创建文件 `ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue`:
```vue
<template>
<div class="param-config-container">
<div class="placeholder-content">
<i class="el-icon-setting"></i>
<p>参数配置功能开发中...</p>
</div>
</div>
</template>
<script>
export default {
name: "ParamConfig",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
};
</script>
<style lang="scss" scoped>
.param-config-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
}
</style>
```
**Step 2: 提交 ParamConfig 组件**
```bash
git add ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue
git commit -m "feat(ccdiProject): 添加参数配置占位组件"
```
---
## Task 2: 创建占位组件 PreliminaryCheck
**Files:**
- Create: `ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue`
**Step 1: 创建 PreliminaryCheck 占位组件**
创建文件 `ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue`:
```vue
<template>
<div class="preliminary-check-container">
<div class="placeholder-content">
<i class="el-icon-data-analysis"></i>
<p>结果总览功能开发中...</p>
</div>
</div>
</template>
<script>
export default {
name: "PreliminaryCheck",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
};
</script>
<style lang="scss" scoped>
.preliminary-check-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
}
</style>
```
**Step 2: 提交 PreliminaryCheck 组件**
```bash
git add ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue
git commit -m "feat(ccdiProject): 添加结果总览占位组件"
```
---
## Task 3: 创建占位组件 SpecialCheck
**Files:**
- Create: `ruoyi-ui/src/views/ccdiProject/components/detail/SpecialCheck.vue`
**Step 1: 创建 SpecialCheck 占位组件**
创建文件 `ruoyi-ui/src/views/ccdiProject/components/detail/SpecialCheck.vue`:
```vue
<template>
<div class="special-check-container">
<div class="placeholder-content">
<i class="el-icon-search"></i>
<p>专项排查功能开发中...</p>
</div>
</div>
</template>
<script>
export default {
name: "SpecialCheck",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
};
</script>
<style lang="scss" scoped>
.special-check-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
}
</style>
```
**Step 2: 提交 SpecialCheck 组件**
```bash
git add ruoyi-ui/src/views/ccdiProject/components/detail/SpecialCheck.vue
git commit -m "feat(ccdiProject): 添加专项排查占位组件"
```
---
## Task 4: 创建占位组件 DetailQuery
**Files:**
- Create: `ruoyi-ui/src/views/ccdiProject/components/detail/DetailQuery.vue`
**Step 1: 创建 DetailQuery 占位组件**
创建文件 `ruoyi-ui/src/views/ccdiProject/components/detail/DetailQuery.vue`:
```vue
<template>
<div class="detail-query-container">
<div class="placeholder-content">
<i class="el-icon-document"></i>
<p>流水明细查询功能开发中...</p>
</div>
</div>
</template>
<script>
export default {
name: "DetailQuery",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
};
</script>
<style lang="scss" scoped>
.detail-query-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
}
</style>
```
**Step 2: 提交 DetailQuery 组件**
```bash
git add ruoyi-ui/src/views/ccdiProject/components/detail/DetailQuery.vue
git commit -m "feat(ccdiProject): 添加流水明细查询占位组件"
```
---
## Task 5: 修改 detail.vue - 添加数据字段和导入组件
**Files:**
- Modify: `ruoyi-ui/src/views/ccdiProject/detail.vue`
**Step 1: 添加组件导入**
`detail.vue``<script>` 部分,找到 import 语句(第 72 行附近),替换为:
```javascript
import UploadData from "./components/detail/UploadData";
import ParamConfig from "./components/detail/ParamConfig";
import PreliminaryCheck from "./components/detail/PreliminaryCheck";
import SpecialCheck from "./components/detail/SpecialCheck";
import DetailQuery from "./components/detail/DetailQuery";
```
**Step 2: 注册组件**
`components` 对象中(第 81-88 行),替换为:
```javascript
components: {
UploadData,
ParamConfig,
PreliminaryCheck,
SpecialCheck,
DetailQuery,
},
```
**Step 3: 添加数据字段**
`data()` 函数中(第 89-110 行),在 `activeTab: "data"` 之后添加:
```javascript
data() {
return {
// 当前激活的菜单项索引
activeTab: "upload",
// 当前显示的组件名称
currentComponent: "UploadData",
// 项目ID
projectId: this.$route.params.projectId,
// ... 其他现有数据保持不变
projectInfo: {
projectId: this.$route.params.projectId,
projectName: "",
projectDesc: "",
createTime: "",
updateTime: "",
startDate: "",
endDate: "",
targetCount: 0,
warningCount: 0,
warningThreshold: 60,
projectStatus: "0",
},
};
},
```
**Step 4: 验证文件语法正确**
```bash
cd ruoyi-ui
npm run lint -- --fix src/views/ccdiProject/detail.vue
```
预期:无语法错误
**Step 5: 提交组件导入修改**
```bash
git add ruoyi-ui/src/views/ccdiProject/detail.vue
git commit -m "feat(ccdiProject): 导入子组件并添加菜单状态数据"
```
---
## Task 6: 修改 detail.vue - 替换模板中的按钮为菜单
**Files:**
- Modify: `ruoyi-ui/src/views/ccdiProject/detail.vue`
**Step 1: 替换 header-right 中的按钮组**
找到 `<div class="header-right">` 部分(第 27-55 行),替换为:
```vue
<div class="header-right">
<el-menu
:default-active="activeTab"
mode="horizontal"
@select="handleMenuSelect"
class="nav-menu"
>
<el-menu-item index="upload">上传数据</el-menu-item>
<el-menu-item index="config">参数配置</el-menu-item>
<el-submenu index="result">
<template slot="title">初核结果</template>
<el-menu-item index="overview">结果总览</el-menu-item>
<el-menu-item index="special">专项排查</el-menu-item>
<el-menu-item index="detail">流水明细查询</el-menu-item>
</el-submenu>
</el-menu>
</div>
```
**Step 2: 替换 UploadData 为动态组件**
找到 `<UploadData>` 组件(第 59-67 行),替换为:
```vue
<!-- 动态组件渲染区域 -->
<component
:is="currentComponent"
:project-id="projectId"
:project-info="projectInfo"
@menu-change="handleMenuChange"
@data-uploaded="handleDataUploaded"
@name-selected="handleNameSelected"
@generate-report="handleGenerateReport"
@fetch-bank-info="handleFetchBankInfo"
/>
```
**Step 3: 验证模板语法**
```bash
cd ruoyi-ui
npm run lint -- --fix src/views/ccdiProject/detail.vue
```
预期:无语法错误
**Step 4: 提交模板修改**
```bash
git add ruoyi-ui/src/views/ccdiProject/detail.vue
git commit -m "feat(ccdiProject): 替换按钮组为导航菜单并使用动态组件"
```
---
## Task 7: 修改 detail.vue - 添加菜单选择处理方法
**Files:**
- Modify: `ruoyi-ui/src/views/ccdiProject/detail.vue`
**Step 1: 添加菜单选择处理方法**
`methods` 对象中(第 124 行),在 `handleBack()` 方法之后添加新方法:
```javascript
/** 菜单选择事件 */
handleMenuSelect(index) {
console.log("菜单选择:", index);
this.activeTab = index;
// 组件映射
const componentMap = {
upload: "UploadData",
config: "ParamConfig",
overview: "PreliminaryCheck",
special: "SpecialCheck",
detail: "DetailQuery",
};
this.currentComponent = componentMap[index] || "UploadData";
},
```
**Step 2: 删除废弃的方法**
删除以下不再使用的方法(第 226-251 行):
- `handleUploadData()`
- `handleParamConfig()`
- `handleCheckResultCommand()`
**Step 3: 更新 handleMenuChange 方法**
修改 `handleMenuChange` 方法(第 185-205 行),简化为:
```javascript
/** UploadData 组件:菜单切换 */
handleMenuChange({ key, route }) {
console.log("切换到菜单:", key, route);
// 直接触发菜单选择
this.handleMenuSelect(route);
},
```
**Step 4: 验证方法逻辑**
```bash
cd ruoyi-ui
npm run lint -- --fix src/views/ccdiProject/detail.vue
```
预期:无语法错误,未使用的方法已删除
**Step 5: 提交方法修改**
```bash
git add ruoyi-ui/src/views/ccdiProject/detail.vue
git commit -m "feat(ccdiProject): 添加菜单选择处理方法并清理废弃代码"
```
---
## Task 8: 修改 detail.vue - 添加导航菜单样式
**Files:**
- Modify: `ruoyi-ui/src/views/ccdiProject/detail.vue`
**Step 1: 添加导航菜单样式**
`<style lang="scss" scoped>` 部分(第 306 行之后),在 `.header-right` 样式块内部添加:
```scss
.header-right {
.nav-menu {
// 移除默认背景色和边框
background-color: transparent;
border-bottom: none;
// 菜单项基础样式
.el-menu-item,
.el-submenu__title {
font-size: 14px;
color: #606266;
padding: 0 16px;
height: 40px;
line-height: 40px;
border-bottom: 2px solid transparent;
&:hover {
background-color: #f5f7fa;
color: #303133;
}
}
// 激活状态:底部下划线 + 蓝色文字
.el-menu-item.is-active {
color: #1890ff;
border-bottom: 2px solid #1890ff;
background-color: transparent;
}
// 下拉菜单激活状态
.el-submenu.is-active > .el-submenu__title {
color: #1890ff;
border-bottom: 2px solid #1890ff;
}
// 下拉菜单图标
.el-submenu__icon-arrow {
margin-left: 4px;
}
}
}
```
**Step 2: 添加下拉菜单弹窗样式**
在样式末尾(第 496 行之后),添加深度选择器样式:
```scss
// 下拉菜单弹窗样式
::v-deep .el-menu--popup {
min-width: 140px;
.el-menu-item {
font-size: 14px;
&:hover {
background-color: #f5f7fa;
}
&.is-active {
color: #1890ff;
background-color: #e6f7ff;
}
}
}
```
**Step 3: 验证样式语法**
```bash
cd ruoyi-ui
npm run lint -- --fix src/views/ccdiProject/detail.vue
```
预期:无语法错误
**Step 4: 提交导航菜单样式**
```bash
git add ruoyi-ui/src/views/ccdiProject/detail.vue
git commit -m "style(ccdiProject): 添加导航菜单简洁链接风格样式"
```
---
## Task 9: 修改 detail.vue - 添加响应式样式
**Files:**
- Modify: `ruoyi-ui/src/views/ccdiProject/detail.vue`
**Step 1: 添加响应式样式**
在现有的 `@media (max-width: 768px)` 媒体查询中(第 464 行),找到 `.detail-header` 样式块,添加响应式菜单样式:
```scss
@media (max-width: 768px) {
.dpc-detail-container {
padding: 8px;
}
.detail-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
.header-right {
width: 100%;
margin-top: 12px;
.nav-menu {
width: 100%;
display: flex;
justify-content: flex-start;
.el-menu-item,
.el-submenu {
flex: 1;
text-align: center;
padding: 0 8px;
font-size: 13px;
}
}
}
}
// ... 其他现有响应式样式保持不变
}
```
**Step 2: 验证响应式样式**
```bash
cd ruoyi-ui
npm run lint -- --fix src/views/ccdiProject/detail.vue
```
预期:无语法错误
**Step 3: 提交响应式样式**
```bash
git add ruoyi-ui/src/views/ccdiProject/detail.vue
git commit -m "style(ccdiProject): 添加导航菜单响应式布局支持"
```
---
## Task 10: 手动测试验证
**Files:**
- Test: `ruoyi-ui/src/views/ccdiProject/detail.vue`
**Step 1: 启动前端开发服务器**
```bash
cd ruoyi-ui
npm run dev
```
预期:服务启动成功,监听 http://localhost:80
**Step 2: 访问项目详情页面**
在浏览器中访问:`http://localhost/ccdiProject`,点击任意项目进入详情页面
预期:页面正常加载,右侧显示导航菜单
**Step 3: 测试菜单切换功能**
测试步骤:
1. 点击"上传数据"菜单项
- 预期:激活状态显示蓝色文字和底部下划线,显示 UploadData 组件
2. 点击"参数配置"菜单项
- 预期:激活状态切换,显示 ParamConfig 占位组件("参数配置功能开发中..."
3. 点击"初核结果"菜单,展开下拉菜单
- 预期:下拉菜单展开,显示三个子项
4. 点击"结果总览"子菜单项
- 预期:激活状态切换,显示 PreliminaryCheck 占位组件
5. 点击"专项排查"子菜单项
- 预期:显示 SpecialCheck 占位组件
6. 点击"流水明细查询"子菜单项
- 预期:显示 DetailQuery 占位组件
**Step 4: 测试样式效果**
测试步骤:
1. Hover 菜单项
- 预期:显示浅灰背景(#f5f7fa
2. 检查激活菜单项
- 预期:蓝色文字(#1890ff+ 底部 2px 蓝色下划线
3. 点击下拉菜单外部区域
- 预期:下拉菜单关闭
4. 调整浏览器窗口宽度至 768px 以下
- 预期:菜单项平均分配宽度,布局自适应
**Step 5: 测试数据传递**
测试步骤:
1. 切换到"参数配置"组件
2. 在浏览器控制台检查组件 props
- 预期projectId 和 projectInfo 正确传递
3. 点击"上传数据"中的功能按钮
- 预期:事件正常触发,原有功能不受影响
**Step 6: 记录测试结果**
创建测试报告文件 `docs/test-reports/2026-03-04-navigation-menu-test.md`:
```markdown
# 项目详情页面导航菜单改造测试报告
## 测试环境
- 浏览器: [记录浏览器名称和版本]
- 测试时间: 2026-03-04
- 测试人员: [你的名字]
## 功能测试
### 菜单切换
- [x] 点击"上传数据",显示 UploadData 组件
- [x] 点击"参数配置",显示 ParamConfig 占位组件
- [x] 点击"初核结果",下拉菜单展开
- [x] 点击"结果总览",显示 PreliminaryCheck 占位组件
- [x] 点击"专项排查",显示 SpecialCheck 占位组件
- [x] 点击"流水明细查询",显示 DetailQuery 占位组件
### 样式测试
- [x] 默认状态:灰色文字,透明背景
- [x] Hover 状态:浅灰背景,深色文字
- [x] 激活状态:蓝色文字 + 底部下划线
- [x] 下拉菜单样式统一
### 交互测试
- [x] 下拉菜单点击外部区域关闭
- [x] 菜单切换流畅无闪烁
- [x] 组件切换数据正确传递
### 响应式测试
- [x] 移动端菜单布局正常
- [x] 菜单项平均分配宽度
## 问题记录
[记录发现的任何问题]
## 测试结论
[通过/需要修复]
```
**Step 7: 提交测试报告**
```bash
git add docs/test-reports/2026-03-04-navigation-menu-test.md
git commit -m "test(ccdiProject): 添加导航菜单改造测试报告"
```
---
## Task 11: 清理和最终提交
**Step 1: 检查所有修改文件**
```bash
git status
```
预期:所有修改已提交
**Step 2: 查看提交历史**
```bash
git log --oneline -10
```
预期:看到 10 个新提交:
1. feat(ccdiProject): 添加参数配置占位组件
2. feat(ccdiProject): 添加结果总览占位组件
3. feat(ccdiProject): 添加专项排查占位组件
4. feat(ccdiProject): 添加流水明细查询占位组件
5. feat(ccdiProject): 导入子组件并添加菜单状态数据
6. feat(ccdiProject): 替换按钮组为导航菜单并使用动态组件
7. feat(ccdiProject): 添加菜单选择处理方法并清理废弃代码
8. style(ccdiProject): 添加导航菜单简洁链接风格样式
9. style(ccdiProject): 添加导航菜单响应式布局支持
10. test(ccdiProject): 添加导航菜单改造测试报告
**Step 3: 验证无遗留问题**
```bash
cd ruoyi-ui
npm run lint
```
预期:无 lint 错误
**Step 4: 推送到远程分支**
```bash
git push origin dev
```
预期:推送成功
---
## 实施后检查清单
- [ ] 所有 4 个占位组件已创建
- [ ] detail.vue 已修改完成(模板、脚本、样式)
- [ ] 导航菜单样式符合简洁链接风格
- [ ] 菜单切换功能正常
- [ ] 下拉菜单交互正常
- [ ] 响应式布局正常
- [ ] 所有修改已提交到 git
- [ ] 测试报告已完成
- [ ] 代码已推送到远程仓库
---
## 预期成果
### 文件创建
- `ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue`
- `ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue`
- `ruoyi-ui/src/views/ccdiProject/components/detail/SpecialCheck.vue`
- `ruoyi-ui/src/views/ccdiProject/components/detail/DetailQuery.vue`
- `docs/test-reports/2026-03-04-navigation-menu-test.md`
### 文件修改
- `ruoyi-ui/src/views/ccdiProject/detail.vue`(模板、脚本、样式)
### Git 提交
- 10 个功能清晰的提交记录
### 功能实现
- ✅ 水平导航菜单替代按钮组
- ✅ 简洁链接风格样式
- ✅ 菜单切换组件内容
- ✅ 下拉菜单支持
- ✅ 响应式布局
- ✅ 原有功能不受影响
---
## 潜在问题和解决方案
### 问题 1: 组件切换时状态丢失
**解决方案:** 使用 `<keep-alive>` 包裹动态组件(可选优化)
```vue
<keep-alive>
<component :is="currentComponent" ... />
</keep-alive>
```
### 问题 2: 下拉菜单样式不生效
**解决方案:** 检查 `::v-deep` 是否被正确编译,可能需要使用 `/deep/``>>>`
### 问题 3: 移动端菜单换行
**解决方案:** 调整响应式断点或使用折叠菜单el-menu 的 collapse 模式)
### 问题 4: 原有事件未触发
**解决方案:** 检查动态组件的事件绑定是否完整,确保所有事件都有对应的处理方法
---
## 后续优化建议
1. **添加组件切换动画**
```vue
<transition name="fade" mode="out-in">
<component :is="currentComponent" ... />
</transition>
```
2. **实现占位组件的完整功能**
- ParamConfig: 模型参数配置界面
- PreliminaryCheck: 结果总览数据展示
- SpecialCheck: 专项排查功能
- DetailQuery: 流水明细查询和筛选
3. **添加菜单权限控制**
- 根据用户权限显示/隐藏菜单项
- 使用 `v-if` 或动态生成菜单配置
4. **添加面包屑导航**
- 在页面顶部显示当前位置
- 支持快速返回上级页面
5. **添加快捷键支持**
- Ctrl+Tab: 切换到下一个菜单
- Ctrl+Shift+Tab: 切换到上一个菜单
---
## 相关技能参考
- @superpowers:brainstorming - 需求分析和设计
- @superpowers:test-driven-development - TDD 开发流程
- @superpowers:verification-before-completion - 完成前验证
- @superpowers:requesting-code-review - 代码审查
---
## 文档参考
- 设计文档: `docs/plans/2026-03-04-project-detail-navigation-menu-design.md`
- Element UI Menu 文档: https://element.eleme.cn/#/zh-CN/component/menu
- Vue 动态组件: https://cn.vuejs.org/v2/guide/components.html#动态组件

258
docs/test-scripts/README.md Normal file
View File

@@ -0,0 +1,258 @@
# 项目创建功能测试说明
## 概述
本文档说明如何使用测试脚本测试"创建项目时集成流水分析平台"功能。
## 测试场景
### 1. 创建项目成功
- **目标**: 验证创建项目时成功调用流水分析平台并保存 `lsfxProjectId`
- **步骤**:
1. 准备项目数据(项目名称、描述、配置方式)
2. 调用创建项目接口
3. 验证响应中包含 `lsfxProjectId`
4. 验证数据库中 `lsfx_project_id` 字段已保存
### 2. 创建项目失败(项目名称为空)
- **目标**: 验证参数校验功能
- **预期**: 接口应拒绝空项目名称,返回错误信息
### 3. 查询项目列表
- **目标**: 验证项目列表中正确显示 `lsfxProjectId`
- **步骤**:
1. 调用项目列表查询接口
2. 验证返回数据包含 `lsfxProjectId` 字段
### 4. 流水分析平台不可用(可选)
- **目标**: 验证异常处理和事务回滚
- **步骤**:
1. 停止 Mock Server
2. 尝试创建项目
3. 验证返回错误信息
4. 验证数据库无脏数据(事务已回滚)
## 前置条件
### 必须条件
1. **后端服务已启动**
```bash
cd ruoyi-admin
mvn spring-boot:run
```
访问地址: http://localhost:8080
2. **Mock Server 已启动**测试场景1-3
```bash
cd lsfx-mock-server
python app.py
```
访问地址: http://localhost:8000
3. **数据库连接正常**
- 主机: 116.62.17.81
- 数据库: ccdi
- 用户: root
### 可选条件
- **MySQL客户端**: 用于验证数据库bash脚本需要
- **PowerShell 5.1+**: 用于运行 PowerShell 脚本
- **Git Bash**: 用于运行 bash 脚本Windows 环境)
## 测试脚本
提供了三个版本的测试脚本:
### 1. Bash 脚本(推荐)
**适用环境**: Linux、MacOS、Git Bash (Windows)
**执行方式**:
```bash
# 进入测试脚本目录
cd docs/test-scripts
# 赋予执行权限
chmod +x test-project-creation.sh
# 执行测试
./test-project-creation.sh
```
**优点**:
- 功能最完整
- 支持数据库验证
- 彩色输出
### 2. PowerShell 脚本
**适用环境**: Windows (PowerShell 5.1+)
**执行方式**:
```powershell
# 进入测试脚本目录
cd docs\test-scripts
# 执行测试
.\test-project-creation.ps1
```
**优点**:
- Windows 原生支持
- 交互式提示
- 无需额外工具
### 3. 批处理脚本
**适用环境**: Windows (CMD)
**执行方式**:
```cmd
cd docs\test-scripts
test-project-creation.bat
```
**优点**:
- 简单易用
- 无需额外工具
## 预期结果
### 成功指标
1. **创建项目成功**
- 响应 `code: 200`
- 响应包含 `lsfxProjectId` 字段77
- 数据库 `ccdi_project` 表中 `lsfx_project_id` 不为空
2. **参数校验**
- 空项目名称被拒绝
- 返回错误信息
3. **查询列表**
- 响应 `code: 200`
- 列表数据包含 `lsfxProjectId` 字段
4. **异常处理**(可选)
- Mock Server 不可用时返回错误
- 数据库无脏数据(事务回滚)
### 测试输出示例
```
==========================================
开始执行项目创建功能测试
==========================================
[INFO] 检查后端服务状态...
[INFO] ✓ 后端服务运行正常
[INFO] 获取访问令牌...
[INFO] ✓ 成功获取令牌
==========================================
测试场景1创建项目成功
==========================================
[INFO] 请求数据: {"projectName":"集成测试项目_20260304_105500","description":"测试集成流水分析平台","configType":"default"}
[INFO] 响应内容: {"code":200,"msg":"项目创建成功","data":{...}}
[INFO] ✓ 项目创建成功
[INFO] ✓ 流水分析平台项目ID: 77
[INFO] 验证数据库...
[INFO] ✓ 数据库验证通过lsfx_project_id 已正确保存
...
==========================================
测试结果汇总
==========================================
[INFO] 通过: 3
[ERROR] 失败: 0
[INFO] 总计: 3
[INFO] ✓ 所有测试通过!
```
## 手动测试Swagger UI
如果需要手动测试,可以使用 Swagger UI
1. **访问 Swagger UI**
```
http://localhost:8080/swagger-ui/index.html
```
2. **获取 Token**
- 找到 `/login/test` 接口
- 点击 "Try it out"
- 输入 username: `admin`, password: `admin123`
- 点击 "Execute"
- 复制返回的 token
3. **设置认证**
- 点击页面顶部 "Authorize" 按钮
- 输入 `Bearer <token>`
- 点击 "Authorize"
4. **创建项目**
- 找到 `POST /ccdi/project` 接口
- 点击 "Try it out"
- 输入请求体:
```json
{
"projectName": "手动测试项目",
"description": "通过Swagger测试",
"configType": "default"
}
```
- 点击 "Execute"
- 查看响应,验证 `lsfxProjectId` 存在
5. **查询项目**
- 找到 `GET /ccdi/project/list` 接口
- 点击 "Try it out"
- 点击 "Execute"
- 验证返回数据包含 `lsfxProjectId`
## 故障排查
### 问题1: 后端服务连接失败
**原因**: 后端未启动或端口被占用
**解决**:
- 检查端口 8080 是否被占用
- 重启后端服务
### 问题2: Mock Server 连接失败
**原因**: Mock Server 未启动
**解决**:
```bash
cd lsfx-mock-server
python app.py
```
### 问题3: 获取 Token 失败
**原因**: 用户名或密码错误
**解决**:
- 确认使用 admin/admin123
- 检查后端日志
### 问题4: lsfxProjectId 为空
**原因**: 流水分析平台调用失败
**解决**:
- 检查 Mock Server 是否运行
- 查看后端日志中的错误信息
- 确认 `LsfxAnalysisClient` 配置正确
### 问题5: 数据库验证失败
**原因**: 数据库连接问题或字段未保存
**解决**:
- 检查数据库连接配置
- 确认 `lsfx_project_id` 字段已添加到表
- 查看后端日志
## 注意事项
1. **测试顺序**: 按照场景1→2→3→4的顺序执行
2. **数据清理**: 测试会创建临时项目数据,建议定期清理
3. **Mock Server**: 场景4需要停止 Mock Server其他场景需要运行
4. **事务回滚**: 异常场景验证事务是否正确回滚
5. **权限**: 测试账号需要有项目创建权限
## 相关文档
- [设计文档](../design/2026-03-04-create-project-integrate-lsfx-design.md)
- [实施计划](../plans/2026-03-04-create-project-integrate-lsfx.md)
- [流水分析对接文档](../../assets/对接流水分析/兰溪-流水分析对接-新版.md)

View File

@@ -0,0 +1,226 @@
@echo off
REM ====================================
REM 项目创建功能测试脚本 (Windows版本)
REM 功能:测试创建项目时集成流水分析平台
REM 作者Claude Code
REM 日期2026-03-04
REM ====================================
setlocal enabledelayedexpansion
REM 配置
set BASE_URL=http://localhost:8080
set USERNAME=admin
set PASSWORD=admin123
set TOKEN=
REM 颜色设置Windows 10+
set "GREEN=[92m"
set "RED=[91m"
set "YELLOW=[93m"
set "NC=[0m"
REM 计数器
set PASS_COUNT=0
set FAIL_COUNT=0
REM 日志函数
goto :main
:log_info
echo %GREEN%[INFO]%NC% %~1
goto :eof
:log_error
echo %RED%[ERROR]%NC% %~1
goto :eof
:log_warning
echo %YELLOW%[WARNING]%NC% %~1
goto :eof
REM 检查curl是否存在
:check_curl
where curl >nul 2>&1
if %ERRORLEVEL% neq 0 (
call :log_error "curl 未安装,请先安装 curl 或使用 Git Bash 运行 test-project-creation.sh"
exit /b 1
)
exit /b 0
REM 检查后端服务
:check_backend_service
call :log_info "检查后端服务状态..."
curl -s --connect-timeout 5 "%BASE_URL%/actuator/health" >nul 2>&1
if %ERRORLEVEL% equ 0 (
call :log_info "✓ 后端服务运行正常"
exit /b 0
) else (
call :log_error "✗ 后端服务未运行,请先启动后端服务"
call :log_info "启动命令: cd ruoyi-admin && mvn spring-boot:run"
exit /b 1
)
REM 获取访问令牌
:get_token
call :log_info "获取访问令牌..."
for /f "delims=" %%i in ('curl -s -X POST "%BASE_URL%/login/test?username=%USERNAME%&password=%PASSWORD%"') do set TOKEN_RESPONSE=%%i
REM 提取token简单解析
for /f "tokens=2 delims=:," %%a in ('echo %TOKEN_RESPONSE% ^| findstr /r "token"') do (
set TOKEN=%%a
set TOKEN=!TOKEN:"=!
goto :token_extracted
)
:token_extracted
if "%TOKEN%"=="" (
call :log_error "获取令牌失败:无法从响应中提取 token"
call :log_info "响应内容: %TOKEN_RESPONSE%"
exit /b 1
)
call :log_info "✓ 成功获取令牌"
exit /b 0
REM 测试场景1创建项目成功
:test_create_project_success
call :log_info "=========================================="
call :log_info "测试场景1创建项目成功"
call :log_info "=========================================="
REM 生成时间戳
for /f "tokens=1-6 delims=/:. " %%a in ("%date% %time%") do (
set TIMESTAMP=%%a%%b%%c_%%d%%e%%f
)
set PROJECT_NAME=集成测试项目_%TIMESTAMP%
REM 准备JSON数据需要转义
set REQUEST_DATA={"projectName":"%PROJECT_NAME%","description":"测试集成流水分析平台","configType":"default"}
call :log_info "请求数据: %REQUEST_DATA%"
REM 发送请求并保存响应到文件
curl -s -X POST "%BASE_URL%/ccdi/project" ^
-H "Content-Type: application/json" ^
-H "Authorization: Bearer %TOKEN%" ^
-d "%REQUEST_DATA%" > response.json
type response.json
echo.
REM 检查是否成功
findstr /c:"code":200 response.json >nul
if %ERRORLEVEL% equ 0 (
call :log_info "✓ 项目创建成功"
REM 检查lsfxProjectId
findstr /c:"lsfxProjectId" response.json >nul
if %ERRORLEVEL% equ 0 (
call :log_info "✓ 流水分析平台项目ID存在"
set /a PASS_COUNT+=1
) else (
call :log_error "✗ 流水分析平台项目ID为空"
set /a FAIL_COUNT+=1
)
) else (
call :log_error "✗ 项目创建失败"
set /a FAIL_COUNT+=1
)
del response.json
exit /b 0
REM 测试场景2创建项目失败项目名称为空
:test_create_project_empty_name
call :log_info "=========================================="
call :log_info "测试场景2创建项目失败项目名称为空"
call :log_info "=========================================="
set REQUEST_DATA={"projectName":"","description":"测试异常场景","configType":"default"}
call :log_info "请求数据: %REQUEST_DATA%"
curl -s -X POST "%BASE_URL%/ccdi/project" ^
-H "Content-Type: application/json" ^
-H "Authorization: Bearer %TOKEN%" ^
-d "%REQUEST_DATA%" > response.json
REM 检查是否失败
findstr /c:"code":200 response.json >nul
if %ERRORLEVEL% neq 0 (
call :log_info "✓ 正确拒绝了空项目名称"
set /a PASS_COUNT+=1
) else (
call :log_error "✗ 未正确验证项目名称"
set /a FAIL_COUNT+=1
)
del response.json
exit /b 0
REM 测试场景3查询项目列表
:test_query_project_list
call :log_info "=========================================="
call :log_info "测试场景3查询项目列表"
call :log_info "=========================================="
curl -s -X GET "%BASE_URL%/ccdi/project/list?pageNum=1&pageSize=10" ^
-H "Authorization: Bearer %TOKEN%" > response.json
REM 检查是否成功
findstr /c:"code":200 response.json >nul
if %ERRORLEVEL% equ 0 (
call :log_info "✓ 查询项目列表成功"
REM 检查lsfxProjectId
findstr /c:"lsfxProjectId" response.json >nul
if %ERRORLEVEL% equ 0 (
call :log_info "✓ 项目列表包含 lsfxProjectId 字段"
) else (
call :log_warning "! 项目列表可能缺少 lsfxProjectId 字段"
)
set /a PASS_COUNT+=1
) else (
call :log_error "✗ 查询项目列表失败"
set /a FAIL_COUNT+=1
)
del response.json
exit /b 0
REM 主函数
:main
call :log_info "=========================================="
call :log_info "开始执行项目创建功能测试"
call :log_info "=========================================="
REM 检查curl
call :check_curl || exit /b 1
REM 检查后端服务
call :check_backend_service || exit /b 1
REM 获取令牌
call :get_token || exit /b 1
REM 执行测试
call :test_create_project_success
call :test_create_project_empty_name
call :test_query_project_list
REM 输出测试结果
call :log_info "=========================================="
call :log_info "测试结果汇总"
call :log_info "=========================================="
call :log_info "通过: %PASS_COUNT%"
call :log_error "失败: %FAIL_COUNT%"
call :log_info "总计: %PASS_COUNT%"
if %FAIL_COUNT% equ 0 (
call :log_info "✓ 所有测试通过!"
exit /b 0
) else (
call :log_error "✗ 存在失败的测试"
exit /b 1
)

View File

@@ -0,0 +1,300 @@
# ====================================
# 项目创建功能测试脚本 (PowerShell版本)
# 功能:测试创建项目时集成流水分析平台
# 作者Claude Code
# 日期2026-03-04
# ====================================
# 配置
$BaseUrl = "http://localhost:8080"
$Username = "admin"
$Password = "admin123"
$Token = $null
# 计数器
$PassCount = 0
$FailCount = 0
# 日志函数
function Write-LogInfo {
param([string]$Message)
Write-Host "[INFO] " -ForegroundColor Green -NoNewline
Write-Host $Message
}
function Write-LogError {
param([string]$Message)
Write-Host "[ERROR] " -ForegroundColor Red -NoNewline
Write-Host $Message
}
function Write-LogWarning {
param([string]$Message)
Write-Host "[WARNING] " -ForegroundColor Yellow -NoNewline
Write-Host $Message
}
# 检查后端服务
function Test-BackendService {
Write-LogInfo "检查后端服务状态..."
try {
$response = Invoke-WebRequest -Uri "$BaseUrl/actuator/health" -TimeoutSec 5 -ErrorAction Stop
Write-LogInfo "✓ 后端服务运行正常"
return $true
} catch {
Write-LogError "✗ 后端服务未运行,请先启动后端服务"
Write-LogInfo "启动命令: cd ruoyi-admin; mvn spring-boot:run"
return $false
}
}
# 获取访问令牌
function Get-AccessToken {
Write-LogInfo "获取访问令牌..."
try {
$response = Invoke-RestMethod -Uri "$BaseUrl/login/test?username=$Username&password=$Password" -Method POST
if ($response.code -eq 200 -and $response.token) {
$script:Token = $response.token
Write-LogInfo "✓ 成功获取令牌"
return $true
} else {
Write-LogError "获取令牌失败:响应格式不正确"
Write-LogInfo "响应内容: $($response | ConvertTo-Json)"
return $false
}
} catch {
Write-LogError "获取令牌失败: $($_.Exception.Message)"
return $false
}
}
# 测试场景1创建项目成功
function Test-CreateProjectSuccess {
Write-LogInfo "=========================================="
Write-LogInfo "测试场景1创建项目成功"
Write-LogInfo "=========================================="
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$projectName = "集成测试项目_$timestamp"
$requestData = @{
projectName = $projectName
description = "测试集成流水分析平台"
configType = "default"
} | ConvertTo-Json
Write-LogInfo "请求数据: $requestData"
try {
$headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $Token"
}
$response = Invoke-RestMethod -Uri "$BaseUrl/ccdi/project" -Method POST -Headers $headers -Body $requestData
Write-LogInfo "响应内容: $($response | ConvertTo-Json -Depth 5)"
if ($response.code -eq 200) {
Write-LogInfo "✓ 项目创建成功"
if ($response.data.lsfxProjectId) {
Write-LogInfo "✓ 流水分析平台项目ID: $($response.data.lsfxProjectId)"
$script:PassCount++
return $true
} else {
Write-LogError "✗ 流水分析平台项目ID为空"
$script:FailCount++
return $false
}
} else {
Write-LogError "✗ 项目创建失败: $($response.msg)"
$script:FailCount++
return $false
}
} catch {
Write-LogError "请求失败: $($_.Exception.Message)"
$script:FailCount++
return $false
}
}
# 测试场景2创建项目失败项目名称为空
function Test-CreateProjectEmptyName {
Write-LogInfo "=========================================="
Write-LogInfo "测试场景2创建项目失败项目名称为空"
Write-LogInfo "=========================================="
$requestData = @{
projectName = ""
description = "测试异常场景"
configType = "default"
} | ConvertTo-Json
Write-LogInfo "请求数据: $requestData"
try {
$headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $Token"
}
$response = Invoke-RestMethod -Uri "$BaseUrl/ccdi/project" -Method POST -Headers $headers -Body $requestData
if ($response.code -ne 200) {
Write-LogInfo "✓ 正确拒绝了空项目名称"
$script:PassCount++
return $true
} else {
Write-LogError "✗ 未正确验证项目名称"
$script:FailCount++
return $false
}
} catch {
Write-LogInfo "✓ 正确拒绝了空项目名称(请求失败)"
$script:PassCount++
return $true
}
}
# 测试场景3查询项目列表
function Test-QueryProjectList {
Write-LogInfo "=========================================="
Write-LogInfo "测试场景3查询项目列表"
Write-LogInfo "=========================================="
try {
$headers = @{
"Authorization" = "Bearer $Token"
}
$response = Invoke-RestMethod -Uri "$BaseUrl/ccdi/project/list?pageNum=1&pageSize=10" -Method GET -Headers $headers
Write-LogInfo "响应内容前500字符: $($response | ConvertTo-Json -Depth 3 | Select-Object -First 500)"
if ($response.code -eq 200) {
Write-LogInfo "✓ 查询项目列表成功"
if ($response.rows -and $response.rows[0].lsfxProjectId) {
Write-LogInfo "✓ 项目列表包含 lsfxProjectId 字段"
} else {
Write-LogWarning "! 项目列表可能缺少 lsfxProjectId 字段"
}
$script:PassCount++
return $true
} else {
Write-LogError "✗ 查询项目列表失败"
$script:FailCount++
return $false
}
} catch {
Write-LogError "请求失败: $($_.Exception.Message)"
$script:FailCount++
return $false
}
}
# 测试场景4流水分析平台不可用
function Test-LsfxUnavailable {
Write-LogInfo "=========================================="
Write-LogInfo "测试场景4流水分析平台不可用"
Write-LogInfo "=========================================="
Write-LogWarning "注意:此测试需要停止 Mock Server"
Write-LogInfo "请手动停止 lsfx-mock-server 并重新运行此测试"
$confirm = Read-Host "是否已停止 Mock Server(y/n)"
if ($confirm -ne "y") {
Write-LogInfo "跳过此测试"
return
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$projectName = "异常测试项目_$timestamp"
$requestData = @{
projectName = $projectName
description = "测试流水分析平台不可用"
configType = "default"
} | ConvertTo-Json
Write-LogInfo "请求数据: $requestData"
try {
$headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $Token"
}
$response = Invoke-RestMethod -Uri "$BaseUrl/ccdi/project" -Method POST -Headers $headers -Body $requestData
if ($response.code -eq 500) {
Write-LogInfo "✓ 正确处理了流水分析平台不可用的情况"
Write-LogInfo "错误信息: $($response.msg)"
# 注意PowerShell版本无法直接验证数据库需要MySQL工具
Write-LogWarning "请手动验证数据库无脏数据"
$script:PassCount++
return $true
} else {
Write-LogError "✗ 未正确处理异常情况"
$script:FailCount++
return $false
}
} catch {
Write-LogError "请求失败: $($_.Exception.Message)"
$script:FailCount++
return $false
}
}
# 主函数
function Main {
Write-LogInfo "=========================================="
Write-LogInfo "开始执行项目创建功能测试"
Write-LogInfo "=========================================="
# 检查后端服务
if (-not (Test-BackendService)) {
exit 1
}
# 获取令牌
if (-not (Get-AccessToken)) {
exit 1
}
# 执行测试
Test-CreateProjectSuccess
Test-CreateProjectEmptyName
Test-QueryProjectList
# 可选测试
Write-LogInfo "=========================================="
Write-LogInfo "可选测试:流水分析平台不可用场景"
Write-LogInfo "=========================================="
$runUnavailableTest = Read-Host "是否执行流水分析平台不可用测试?(y/n)"
if ($runUnavailableTest -eq "y") {
Test-LsfxUnavailable
}
# 输出测试结果
Write-LogInfo "=========================================="
Write-LogInfo "测试结果汇总"
Write-LogInfo "=========================================="
Write-LogInfo "通过: $PassCount"
Write-LogError "失败: $FailCount"
Write-LogInfo "总计: $($PassCount + $FailCount)"
if ($FailCount -eq 0) {
Write-LogInfo "✓ 所有测试通过!"
exit 0
} else {
Write-LogError "✗ 存在失败的测试"
exit 1
}
}
# 执行主函数
Main

View File

@@ -0,0 +1,335 @@
#!/bin/bash
# ====================================
# 项目创建功能测试脚本
# 功能:测试创建项目时集成流水分析平台
# 作者Claude Code
# 日期2026-03-04
# ====================================
# 配置
BASE_URL="http://localhost:8080"
USERNAME="admin"
PASSWORD="admin123"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
# 检查命令是否存在
check_command() {
if ! command -v $1 &> /dev/null; then
log_error "$1 未安装,请先安装 $1"
exit 1
fi
}
# 检查后端服务是否运行
check_backend_service() {
log_info "检查后端服务状态..."
if curl -s --connect-timeout 5 "$BASE_URL/actuator/health" > /dev/null 2>&1; then
log_info "✓ 后端服务运行正常"
return 0
else
log_error "✗ 后端服务未运行,请先启动后端服务"
log_info "启动命令: cd ruoyi-admin && mvn spring-boot:run"
return 1
fi
}
# 获取访问令牌
get_token() {
log_info "获取访问令牌..."
TOKEN_RESPONSE=$(curl -s -X POST "$BASE_URL/login/test?username=$USERNAME&password=$PASSWORD")
# 检查响应是否为空
if [ -z "$TOKEN_RESPONSE" ]; then
log_error "获取令牌失败:响应为空"
return 1
fi
# 提取 token假设返回格式为 {"code":200,"msg":"操作成功","data":"token"}
TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | sed 's/"token":"//;s/"//')
if [ -z "$TOKEN" ]; then
log_error "获取令牌失败:无法从响应中提取 token"
log_info "响应内容: $TOKEN_RESPONSE"
return 1
fi
log_info "✓ 成功获取令牌"
return 0
}
# 测试场景1创建项目成功
test_create_project_success() {
log_info "=========================================="
log_info "测试场景1创建项目成功"
log_info "=========================================="
# 准备测试数据
PROJECT_NAME="集成测试项目_$(date +%Y%m%d_%H%M%S)"
REQUEST_DATA=$(cat <<EOF
{
"projectName": "$PROJECT_NAME",
"description": "测试集成流水分析平台",
"configType": "default"
}
EOF
)
log_info "请求数据: $REQUEST_DATA"
# 发送请求
RESPONSE=$(curl -s -X POST "$BASE_URL/ccdi/project" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "$REQUEST_DATA")
log_info "响应内容: $RESPONSE"
# 验证响应
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | sed 's/"code"://')
MSG=$(echo "$RESPONSE" | grep -o '"msg":"[^"]*"' | sed 's/"msg":"//;s/"//')
if [ "$CODE" == "200" ]; then
log_info "✓ 项目创建成功"
# 验证 lsfxProjectId 是否存在
LSFX_PROJECT_ID=$(echo "$RESPONSE" | grep -o '"lsfxProjectId":[0-9]*' | sed 's/"lsfxProjectId"://')
if [ -n "$LSFX_PROJECT_ID" ]; then
log_info "✓ 流水分析平台项目ID: $LSFX_PROJECT_ID"
else
log_error "✗ 流水分析平台项目ID为空"
return 1
fi
# 验证数据库
log_info "验证数据库..."
DB_CHECK=$(mysql -h 116.62.17.81 -u root -pKfcx@1234 ccdi -N -B -e \
"SELECT COUNT(*) FROM ccdi_project WHERE project_name='$PROJECT_NAME' AND lsfx_project_id IS NOT NULL;" 2>/dev/null)
if [ "$DB_CHECK" == "1" ]; then
log_info "✓ 数据库验证通过lsfx_project_id 已正确保存"
else
log_error "✗ 数据库验证失败lsfx_project_id 未保存"
return 1
fi
return 0
else
log_error "✗ 项目创建失败: $MSG"
return 1
fi
}
# 测试场景2创建项目失败项目名称为空
test_create_project_empty_name() {
log_info "=========================================="
log_info "测试场景2创建项目失败项目名称为空"
log_info "=========================================="
REQUEST_DATA=$(cat <<EOF
{
"projectName": "",
"description": "测试异常场景",
"configType": "default"
}
EOF
)
log_info "请求数据: $REQUEST_DATA"
RESPONSE=$(curl -s -X POST "$BASE_URL/ccdi/project" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "$REQUEST_DATA")
log_info "响应内容: $RESPONSE"
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | sed 's/"code"://')
if [ "$CODE" != "200" ]; then
log_info "✓ 正确拒绝了空项目名称"
return 0
else
log_error "✗ 未正确验证项目名称"
return 1
fi
}
# 测试场景3流水分析平台不可用
test_lsfx_unavailable() {
log_info "=========================================="
log_info "测试场景3流水分析平台不可用"
log_info "=========================================="
log_warning "注意:此测试需要停止 Mock Server"
log_info "请手动停止 lsfx-mock-server 并重新运行此测试"
log_info "提示:在 lsfx-mock-server 目录按 Ctrl+C 停止"
# 询问用户是否继续
read -p "是否已停止 Mock Server(y/n): " confirm
if [ "$confirm" != "y" ]; then
log_info "跳过此测试"
return 0
fi
REQUEST_DATA=$(cat <<EOF
{
"projectName": "异常测试项目_$(date +%Y%m%d_%H%M%S)",
"description": "测试流水分析平台不可用",
"configType": "default"
}
EOF
)
log_info "请求数据: $REQUEST_DATA"
RESPONSE=$(curl -s -X POST "$BASE_URL/ccdi/project" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "$REQUEST_DATA")
log_info "响应内容: $RESPONSE"
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | sed 's/"code"://')
MSG=$(echo "$RESPONSE" | grep -o '"msg":"[^"]*"' | sed 's/"msg":"//;s/"//')
if [ "$CODE" == "500" ]; then
log_info "✓ 正确处理了流水分析平台不可用的情况"
log_info "错误信息: $MSG"
# 验证数据库没有脏数据
PROJECT_NAME=$(echo "$REQUEST_DATA" | grep -o '"projectName":"[^"]*"' | sed 's/"projectName":"//;s/"//')
DB_CHECK=$(mysql -h 116.62.17.81 -u root -pKfcx@1234 ccdi -N -B -e \
"SELECT COUNT(*) FROM ccdi_project WHERE project_name='$PROJECT_NAME';" 2>/dev/null)
if [ "$DB_CHECK" == "0" ]; then
log_info "✓ 事务已回滚,数据库无脏数据"
else
log_error "✗ 事务未回滚,存在脏数据"
return 1
fi
return 0
else
log_error "✗ 未正确处理异常情况"
return 1
fi
}
# 测试场景4查询项目列表
test_query_project_list() {
log_info "=========================================="
log_info "测试场景4查询项目列表"
log_info "=========================================="
RESPONSE=$(curl -s -X GET "$BASE_URL/ccdi/project/list?pageNum=1&pageSize=10" \
-H "Authorization: Bearer $TOKEN")
log_info "响应内容前500字符: ${RESPONSE:0:500}"
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | sed 's/"code"://')
if [ "$CODE" == "200" ]; then
log_info "✓ 查询项目列表成功"
# 检查是否包含 lsfxProjectId
if echo "$RESPONSE" | grep -q "lsfxProjectId"; then
log_info "✓ 项目列表包含 lsfxProjectId 字段"
else
log_warning "! 项目列表可能缺少 lsfxProjectId 字段"
fi
return 0
else
log_error "✗ 查询项目列表失败"
return 1
fi
}
# 主测试流程
main() {
log_info "=========================================="
log_info "开始执行项目创建功能测试"
log_info "=========================================="
# 检查依赖
check_command curl
check_command mysql
# 检查后端服务
check_backend_service || exit 1
# 获取令牌
get_token || exit 1
# 执行测试
PASS_COUNT=0
FAIL_COUNT=0
if test_create_project_success; then
((PASS_COUNT++))
else
((FAIL_COUNT++))
fi
if test_create_project_empty_name; then
((PASS_COUNT++))
else
((FAIL_COUNT++))
fi
if test_query_project_list; then
((PASS_COUNT++))
else
((FAIL_COUNT++))
fi
# 可选测试
log_info "=========================================="
log_info "可选测试:流水分析平台不可用场景"
log_info "=========================================="
read -p "是否执行流水分析平台不可用测试?(y/n): " run_unavailable_test
if [ "$run_unavailable_test" == "y" ]; then
if test_lsfx_unavailable; then
((PASS_COUNT++))
else
((FAIL_COUNT++))
fi
fi
# 输出测试结果
log_info "=========================================="
log_info "测试结果汇总"
log_info "=========================================="
log_info "通过: $PASS_COUNT"
log_info "失败: $FAIL_COUNT"
log_info "总计: $((PASS_COUNT + FAIL_COUNT))"
if [ $FAIL_COUNT -eq 0 ]; then
log_info "✓ 所有测试通过!"
exit 0
else
log_error "✗ 存在失败的测试"
exit 1
fi
}
# 执行主函数
main

View File

@@ -0,0 +1,51 @@
<template>
<div class="detail-query-container">
<div class="placeholder-content">
<i class="el-icon-document"></i>
<p>流水明细查询功能开发中...</p>
</div>
</div>
</template>
<script>
export default {
name: "DetailQuery",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
};
</script>
<style lang="scss" scoped>
.detail-query-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<div class="param-config-container">
<div class="placeholder-content">
<i class="el-icon-setting"></i>
<p>参数配置功能开发中...</p>
</div>
</div>
</template>
<script>
export default {
name: "ParamConfig",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
};
</script>
<style lang="scss" scoped>
.param-config-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<div class="preliminary-check-container">
<div class="placeholder-content">
<i class="el-icon-data-analysis"></i>
<p>结果总览功能开发中...</p>
</div>
</div>
</template>
<script>
export default {
name: "PreliminaryCheck",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
};
</script>
<style lang="scss" scoped>
.preliminary-check-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<div class="special-check-container">
<div class="placeholder-content">
<i class="el-icon-search"></i>
<p>专项排查功能开发中...</p>
</div>
</div>
</template>
<script>
export default {
name: "SpecialCheck",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
};
</script>
<style lang="scss" scoped>
.special-check-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
}
</style>

View File

@@ -25,38 +25,24 @@
</div>
</div>
<div class="header-right">
<div class="action-buttons">
<el-button
size="small"
type="primary"
icon="el-icon-upload2"
@click="handleUploadData"
<el-menu
:default-active="activeTab"
mode="horizontal"
@select="handleMenuSelect"
class="nav-menu"
>
上传数据
</el-button>
<el-button
size="small"
icon="el-icon-setting"
@click="handleParamConfig"
>
参数配置
</el-button>
<el-dropdown @command="handleCheckResultCommand" trigger="click">
<el-button size="small" icon="el-icon-document">
初核结果<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="overview">结果总览</el-dropdown-item>
<el-dropdown-item command="special">专项排查</el-dropdown-item>
<el-dropdown-item command="detail">流水明细查询</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<el-menu-item index="upload">上传数据</el-menu-item>
<el-menu-item index="config">参数配置</el-menu-item>
<el-menu-item index="overview">结果总览</el-menu-item>
<el-menu-item index="special">专项排查</el-menu-item>
<el-menu-item index="detail">流水明细查询</el-menu-item>
</el-menu>
</div>
</div>
<!-- 数据上传页面 -->
<UploadData
<!-- 动态组件渲染区域 -->
<component
:is="currentComponent"
:project-id="projectId"
:project-info="projectInfo"
@menu-change="handleMenuChange"
@@ -70,26 +56,26 @@
<script>
import UploadData from "./components/detail/UploadData";
// import UploadParams from "./components/detail/UploadParams";
// import ParamConfig from "./components/detail/ParamConfig";
// import PreliminaryCheck from "./components/detail/PreliminaryCheck";
// import SpecialCheck from "./components/detail/SpecialCheck";
// import DetailQuery from './components/detail/DetailQuery'
import ParamConfig from "./components/detail/ParamConfig";
import PreliminaryCheck from "./components/detail/PreliminaryCheck";
import SpecialCheck from "./components/detail/SpecialCheck";
import DetailQuery from "./components/detail/DetailQuery";
export default {
name: "ProjectDetail",
components: {
UploadData,
// UploadParams,
// ParamConfig,
// PreliminaryCheck,
// SpecialCheck,
// DetailQuery,
ParamConfig,
PreliminaryCheck,
SpecialCheck,
DetailQuery,
},
data() {
return {
// 当前标签页
activeTab: "data",
// 当前激活的菜单项索引
activeTab: "upload",
// 当前显示的组件名称
currentComponent: "UploadData",
// 项目ID
projectId: this.$route.params.projectId,
// 项目信息
@@ -182,26 +168,27 @@ export default {
handleBack() {
this.$router.push("/ccdiProject");
},
/** 菜单选择事件 */
handleMenuSelect(index) {
console.log("菜单选择:", index);
this.activeTab = index;
// 组件映射
const componentMap = {
upload: "UploadData",
config: "ParamConfig",
overview: "PreliminaryCheck",
special: "SpecialCheck",
detail: "DetailQuery",
};
this.currentComponent = componentMap[index] || "UploadData";
},
/** UploadData 组件:菜单切换 */
handleMenuChange({ key, route }) {
console.log("切换到菜单:", key, route);
// 根据不同的菜单项跳转到不同的组件或页面
switch (route) {
case "config":
this.$message.info("参数配置功能开发中");
break;
case "overview":
this.$message.info("结果总览功能开发中");
break;
case "special":
this.$message.info("专项排查功能开发中");
break;
case "detail":
this.$message.info("流水明细查询功能开发中");
break;
default:
break;
}
// 直接触发菜单选择
this.handleMenuSelect(route);
},
/** UploadData 组件:数据上传完成 */
handleDataUploaded({ type }) {
@@ -223,33 +210,6 @@ export default {
console.log("拉取本行信息");
this.$message.info("拉取本行信息功能开发中");
},
/** 上传数据 (原方法已由UploadData组件处理) */
handleUploadData() {
console.log("上传数据");
this.$message.info("上传数据功能已迁移至上传页面");
},
/** 参数配置 (原方法) */
handleParamConfig() {
console.log("参数配置");
this.$message.info("参数配置功能开发中");
},
/** 初核结果下拉菜单命令 */
handleCheckResultCommand(command) {
console.log("初核结果命令:", command);
switch (command) {
case "overview":
this.$message.info("结果总览功能开发中");
break;
case "special":
this.$message.info("专项排查功能开发中");
break;
case "detail":
this.$message.info("流水明细查询功能开发中");
break;
default:
break;
}
},
/** 数据上传完成 */
handleDataUploaded() {
console.log("数据上传完成");
@@ -353,21 +313,51 @@ export default {
}
.header-right {
.action-buttons {
display: flex;
align-items: center;
gap: 12px;
.el-button {
padding: 8px 16px;
.nav-menu {
// 移除默认背景色和边框
background-color: transparent;
border-bottom: none;
// 菜单项基础样式
.el-menu-item,
.el-submenu__title {
font-size: 14px;
color: #606266;
padding: 0 16px;
height: 40px;
line-height: 40px;
border-bottom: 2px solid transparent;
.el-icon--right {
margin-left: 4px;
&:hover {
background-color: #f5f7fa;
color: #303133;
}
}
.el-tag {
// 子菜单容器高度统一
.el-submenu {
height: 40px;
line-height: 40px;
}
// 激活状态:底部下划线 + 蓝色文字
.el-menu-item.is-active {
color: #1890ff;
border-bottom: 2px solid #1890ff;
background-color: transparent;
}
// 下拉菜单激活状态
.el-submenu.is-active > .el-submenu__title {
color: #1890ff;
border-bottom: 2px solid #1890ff;
}
// 下拉菜单图标
.el-submenu__icon-arrow {
margin-left: 4px;
}
}
@@ -471,6 +461,25 @@ export default {
flex-direction: column;
align-items: flex-start;
gap: 12px;
.header-right {
width: 100%;
margin-top: 12px;
.nav-menu {
width: 100%;
display: flex;
justify-content: flex-start;
.el-menu-item,
.el-submenu {
flex: 1;
text-align: center;
padding: 0 8px;
font-size: 13px;
}
}
}
}
.info-content {
@@ -494,4 +503,22 @@ export default {
}
}
}
// 下拉菜单弹窗样式
::v-deep .el-menu--popup {
min-width: 140px;
.el-menu-item {
font-size: 14px;
&:hover {
background-color: #f5f7fa;
}
&.is-active {
color: #1890ff;
background-color: #e6f7ff;
}
}
}
</style>