Merge branch 'dev' into dev-lgw

This commit is contained in:
mengke
2026-03-02 19:20:51 +08:00
298 changed files with 20428 additions and 6241 deletions

4
ruoyi-ui/.gitignore vendored
View File

@@ -21,3 +21,7 @@ selenium-debug.log
package-lock.json
yarn.lock
# 备份文件
*.backup
*.bak

View File

@@ -0,0 +1,40 @@
import request from '@/utils/request'
/**
* 查询模型列表
* @param {Object} query - 查询参数
* @param {Number} query.projectId - 项目ID
*/
export function listModels(query) {
return request({
url: '/ccdi/modelParam/modelList',
method: 'get',
params: query
})
}
/**
* 查询模型参数列表
* @param {Object} query - 查询参数
* @param {Number} query.projectId - 项目ID
* @param {String} query.modelCode - 模型编码
*/
export function listParams(query) {
return request({
url: '/ccdi/modelParam/list',
method: 'get',
params: query
})
}
/**
* 保存模型参数
* @param {Object} data - 保存数据
*/
export function saveParams(data) {
return request({
url: '/ccdi/modelParam/save',
method: 'post',
data: data
})
}

View File

@@ -1,6 +1,15 @@
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',
@@ -86,7 +95,7 @@ export function importFromHistory(data) {
})
}
// Mock数据获取项目列表
// Mock数据获取项目列表(保留用于测试)
export function getMockProjectList() {
return Promise.resolve({
code: 200,
@@ -95,35 +104,44 @@ export function getMockProjectList() {
{
projectId: 1,
projectName: '2024年Q1初核',
projectDesc: '2024年第一季度纪检初核排查工作',
description: '2024年第一季度纪检初核排查工作',
createTime: '2024-01-01',
projectStatus: '0',
status: '0',
configType: 'default',
targetCount: 500,
warningCount: 15,
startDate: '2024-01-01',
endDate: '2024-03-31'
highRiskCount: 5,
mediumRiskCount: 10,
lowRiskCount: 0,
createBy: 'admin',
createByName: '管理员'
},
{
projectId: 2,
projectName: '2023年Q4初核',
projectDesc: '2023年第四季度纪检初核排查工作',
description: '2023年第四季度纪检初核排查工作',
createTime: '2023-10-01',
projectStatus: '1',
status: '1',
configType: 'custom',
targetCount: 480,
warningCount: 23,
startDate: '2023-10-01',
endDate: '2023-12-31'
highRiskCount: 8,
mediumRiskCount: 15,
lowRiskCount: 0,
createBy: 'admin',
createByName: '管理员'
},
{
projectId: 3,
projectName: '2023年Q3初核',
projectDesc: '2023年第三季度纪检初核排查工作',
description: '2023年第三季度纪检初核排查工作',
createTime: '2023-07-01',
projectStatus: '2',
status: '2',
configType: 'default',
targetCount: 450,
warningCount: 18,
startDate: '2023-07-01',
endDate: '2023-09-30'
highRiskCount: 0,
mediumRiskCount: 18,
lowRiskCount: 5,
createBy: 'admin',
createByName: '管理员'
}
]
})
@@ -137,21 +155,29 @@ export function getMockHistoryProjects() {
{
projectId: 3,
projectName: '2023年Q3初核',
projectDesc: '2023年第三季度纪检初核排查工作',
description: '2023年第三季度纪检初核排查工作',
createTime: '2023-07-01',
projectStatus: '2',
status: '2',
targetCount: 450,
warningCount: 18
},
{
projectId: 4,
projectName: '2023年Q2初核',
projectDesc: '2023年第二季度纪检初核排查工作',
description: '2023年第二季度纪检初核排查工作',
createTime: '2023-04-01',
projectStatus: '2',
status: '2',
targetCount: 420,
warningCount: 12
}
]
})
}
// 查询项目状态统计
export function getStatusCounts() {
return request({
url: '/ccdi/project/statusCounts',
method: 'get'
})
}

View File

@@ -0,0 +1,194 @@
<template>
<div class="app-container">
<!-- 顶部标题 -->
<div class="header">
<span class="title">模型参数管理</span>
</div>
<!-- 查询筛选区 -->
<div class="filter-container">
<el-form :inline="true" :model="queryParams" ref="queryForm">
<el-form-item label="模型名称" prop="modelCode">
<el-select v-model="queryParams.modelCode" placeholder="请选择模型">
<el-option
v-for="model in modelList"
:key="model.modelCode"
:label="model.modelName"
:value="model.modelCode"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">
查询
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 参数配置表格 -->
<div class="table-container">
<h3 class="table-title">阈值参数配置</h3>
<el-table :data="paramList" border style="width: 100%">
<el-table-column label="监测项" prop="paramName" width="200" />
<el-table-column label="描述" prop="paramDesc" />
<el-table-column label="阈值设置" width="200">
<template #default="{ row }">
<el-input
v-model="row.paramValue"
placeholder="请输入阈值"
@input="markAsModified(row)"
/>
</template>
</el-table-column>
<el-table-column label="单位" prop="paramUnit" width="120" />
</el-table>
</div>
<!-- 操作按钮 -->
<div class="button-container">
<el-button type="primary" @click="handleSave" :loading="saving">
保存配置
</el-button>
</div>
</div>
</template>
<script>
import {listModels, listParams, saveParams} from "@/api/ccdi/modelParam";
export default {
name: "ModelParam",
data() {
return {
// 模型列表
modelList: [],
// 查询参数
queryParams: {
modelCode: undefined,
projectId: 0, // 默认查询系统级参数
},
// 参数列表
paramList: [],
// 保存中状态
saving: false,
};
},
created() {
this.getModelList();
},
methods: {
/** 查询模型列表 */
getModelList() {
listModels({ projectId: this.queryParams.projectId }).then((response) => {
this.modelList = response.data;
if (this.modelList.length > 0) {
this.queryParams.modelCode = this.modelList[0].modelCode;
this.handleQuery();
}
});
},
/** 查询参数列表 */
handleQuery() {
if (!this.queryParams.modelCode) {
this.$message.warning("请选择模型");
return;
}
listParams(this.queryParams).then((response) => {
this.paramList = response.data;
});
},
/** 标记参数为已修改 */
markAsModified(row) {
row.modified = true;
},
/** 保存配置 */
handleSave() {
if (!this.queryParams.modelCode) {
this.$message.warning("请选择模型");
return;
}
// 只保存修改过的参数值
const modifiedParams = this.paramList.filter((item) => item.modified);
if (modifiedParams.length === 0) {
this.$message.info("没有需要保存的修改");
return;
}
const saveDTO = {
projectId: this.queryParams.projectId,
modelCode: this.queryParams.modelCode,
params: modifiedParams.map((item) => ({
paramCode: item.paramCode,
paramValue: item.paramValue,
})),
};
this.saving = true;
saveParams(saveDTO)
.then((response) => {
this.$modal.msgSuccess("保存成功");
// 清除修改标记
this.paramList.forEach((item) => {
item.modified = false;
});
})
.finally(() => {
this.saving = false;
});
},
},
};
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
.header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 4px;
.title {
font-size: 18px;
font-weight: bold;
color: #333;
}
}
.filter-container {
padding: 15px;
background: #fff;
border-radius: 4px;
margin-bottom: 20px;
}
.table-container {
padding: 20px;
background: #fff;
border-radius: 4px;
margin-bottom: 20px;
.table-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin: 0 0 15px 0;
}
}
.button-container {
padding: 15px;
background: #fff;
border-radius: 4px;
text-align: left;
}
</style>

View File

@@ -73,7 +73,7 @@
>
{{ isUploading ? '导入中...' : '开始导入' }}
</el-button>
<el-button icon="el-icon-close" @click="visible = false" :disabled="isUploading">
<el-button :disabled="isUploading" icon="el-icon-close" @click="handleCancel">
</el-button>
</div>
@@ -144,6 +144,10 @@ export default {
this.isFileSelected = false;
this.$emit("close");
},
handleCancel() {
// 通过 $emit 通知父组件更新 visible 状态,而不是直接修改 prop
this.$emit('update:visible', false);
},
handleImportTypeChange() {
if (this.$refs.upload) {
this.$refs.upload.clearFiles();

View File

@@ -19,116 +19,44 @@
<el-input
v-model="formData.projectName"
placeholder="请输入项目名称"
maxlength="50"
maxlength="100"
show-word-limit
/>
</el-form-item>
<!-- 项目描述 -->
<el-form-item label="项目描述" prop="projectDesc">
<el-form-item label="项目描述" prop="description">
<el-input
v-model="formData.projectDesc"
v-model="formData.description"
type="textarea"
:rows="3"
:rows="4"
placeholder="请输入项目描述"
maxlength="200"
maxlength="500"
show-word-limit
/>
</el-form-item>
<!-- 目标人员 -->
<el-form-item label="目标人员">
<div class="target-persons-wrapper">
<el-button
icon="el-icon-plus"
size="small"
@click="handleAddPerson"
>添加人员</el-button>
<div v-if="formData.targetPersons && formData.targetPersons.length > 0" class="persons-list">
<el-tag
v-for="(person, index) in formData.targetPersons"
:key="index"
closable
@close="handleRemovePerson(index)"
type="info"
>
{{ person.name }} ({{ person.certNo }})
</el-tag>
</div>
<div v-else class="empty-hint">
<i class="el-icon-info"></i>
<span>暂未添加目标人员可后续添加</span>
</div>
</div>
</el-form-item>
<!-- 时间范围 -->
<el-form-item label="开始日期" prop="startDate">
<el-date-picker
v-model="formData.startDate"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="结束日期" prop="endDate">
<el-date-picker
v-model="formData.endDate"
type="date"
placeholder="选择结束日期"
value-format="yyyy-MM-dd"
:picker-options="endDatePickerOptions"
style="width: 100%"
/>
</el-form-item>
<!-- 目标人数 -->
<el-form-item label="目标人数" prop="targetCount">
<el-input-number
v-model="formData.targetCount"
:min="0"
:max="10000"
:step="10"
controls-position="right"
style="width: 100%"
/>
<!-- 配置方式 -->
<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>
<!-- 高级设置可选扩展 -->
<el-collapse class="advanced-settings" v-model="activeCollapse">
<el-collapse-item title="高级设置" name="advanced">
<el-form label-width="100px" label-position="right">
<el-form-item label="自动预警">
<el-switch v-model="formData.autoWarning" />
<span class="form-item-hint">开启后将自动计算预警人员</span>
</el-form-item>
<el-form-item label="预警阈值">
<el-input-number
v-model="formData.warningThreshold"
:min="1"
:max="100"
controls-position="right"
/>
<span class="form-item-hint">匹配度低于此值时触发预警</span>
</el-form-item>
</el-form>
</el-collapse-item>
</el-collapse>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">
<i v-if="!submitting" class="el-icon-check"></i>
创建项目
</el-button>
</div>
</el-dialog>
</template>
<script>
import { createProject } from '@/api/ccdiProject'
export default {
name: 'AddProjectDialog',
props: {
@@ -146,52 +74,21 @@ export default {
}
},
data() {
// 结束日期验证规则
const validateEndDate = (rule, value, callback) => {
if (value && this.formData.startDate && value < this.formData.startDate) {
callback(new Error('结束日期不能早于开始日期'))
} else {
callback()
}
}
return {
submitting: false,
activeCollapse: [],
formData: {
projectId: null,
projectName: '',
projectDesc: '',
startDate: '',
endDate: '',
targetCount: 0,
targetPersons: [],
autoWarning: true,
warningThreshold: 60
description: '',
configType: 'default'
},
rules: {
projectName: [
{ required: true, message: '请输入项目名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
],
startDate: [
{ required: true, message: '请选择开始日期', trigger: 'change' }
],
endDate: [
{ required: true, message: '请选择结束日期', trigger: 'change' },
{ validator: validateEndDate, trigger: 'change' }
],
targetCount: [
{ required: true, message: '请输入目标人数', trigger: 'blur' }
configType: [
{ required: true, message: '请选择配置方式', trigger: 'change' }
]
},
endDatePickerOptions: {
disabledDate: (time) => {
if (this.formData.startDate) {
return time.getTime() < new Date(this.formData.startDate).getTime()
}
return false
}
}
}
},
@@ -219,7 +116,6 @@ export default {
},
visible(val) {
if (val) {
// 对话框打开时重置表单验证状态
this.$nextTick(() => {
if (this.$refs.projectForm) {
this.$refs.projectForm.clearValidate()
@@ -229,100 +125,38 @@ export default {
}
},
methods: {
/** 添加人员 */
handleAddPerson() {
// 这里可以打开一个选择人员的对话框
this.$message.info('人员选择功能待实现')
// 模拟添加人员
if (!this.formData.targetPersons) {
this.formData.targetPersons = []
}
this.formData.targetPersons.push({
name: '张三',
certNo: '3301**********202101'
})
},
/** 移除人员 */
handleRemovePerson(index) {
this.formData.targetPersons.splice(index, 1)
},
/** 提交表单 */
handleSubmit() {
this.$refs.projectForm.validate(valid => {
if (valid) {
this.submitting = true
// 模拟提交
setTimeout(() => {
createProject(this.formData).then(response => {
this.$message.success('项目创建成功')
this.submitting = false
this.$emit('submit', { ...this.formData })
}, 500)
this.$emit('submit', response.data)
this.handleClose()
}).catch(() => {
this.submitting = false
})
}
})
},
/** 关闭对话框 */
handleClose() {
this.$emit('close')
this.$refs.projectForm.resetFields()
this.formData = {
projectId: null,
projectName: '',
projectDesc: '',
startDate: '',
endDate: '',
targetCount: 0,
targetPersons: [],
autoWarning: true,
warningThreshold: 60
description: '',
configType: 'default'
}
this.activeCollapse = []
}
}
}
</script>
<style lang="scss" scoped>
.target-persons-wrapper {
width: 100%;
.persons-list {
margin-top: 12px;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.empty-hint {
margin-top: 12px;
padding: 12px;
background-color: #f5f7fa;
border-radius: 4px;
color: #909399;
font-size: 13px;
display: flex;
align-items: center;
gap: 6px;
i {
font-size: 14px;
}
}
}
.advanced-settings {
margin: 20px 0;
:deep(.el-collapse-item__header) {
font-size: 14px;
color: #606266;
}
}
.form-item-hint {
margin-left: 12px;
font-size: 12px;
color: #909399;
}
.dialog-footer {
text-align: right;
@@ -330,4 +164,14 @@ export default {
margin-left: 8px;
}
}
:deep(.el-radio-group) {
display: flex;
flex-direction: column;
gap: 12px;
.el-radio {
line-height: 32px;
}
}
</style>

View File

@@ -1,173 +1,182 @@
<template>
<div class="project-table-container">
<el-card class="table-card" shadow="hover">
<el-table
v-loading="loading"
:data="dataList"
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: '600' }"
<el-table
v-loading="loading"
:data="dataList"
style="width: 100%"
>
<!-- 项目名称含描述 -->
<el-table-column
label="项目名称"
min-width="180"
align="left"
>
<!-- 序号 -->
<el-table-column
type="index"
label="序号"
width="60"
align="center"
/>
<template slot-scope="scope">
<div class="project-info-cell">
<div class="project-name">{{ scope.row.projectName }}</div>
<div class="project-desc">{{ scope.row.description || '暂无描述' }}</div>
</div>
</template>
</el-table-column>
<!-- 项目名称 -->
<el-table-column
label="项目名称"
min-width="160"
show-overflow-tooltip
>
<template slot-scope="scope">
<div class="project-name-cell">
<div class="name">{{ scope.row.projectName }}</div>
<div class="desc">{{ scope.row.projectDesc }}</div>
<!-- 更新/创建时间 -->
<el-table-column
prop="updateTime"
label="更新/创建时间"
width="180"
align="center"
>
<template slot-scope="scope">
<span>{{ parseTime(scope.row.updateTime || scope.row.createTime) }}</span>
</template>
</el-table-column>
<!-- 创建人 -->
<el-table-column
prop="createByName"
label="创建人"
width="120"
align="center"
/>
<!-- 状态 -->
<el-table-column
prop="status"
label="状态"
width="120"
align="center"
>
<template slot-scope="scope">
<div class="status-tag">
<span class="status-dot" :style="{ color: getStatusColor(scope.row.status) }"></span>
<dict-tag :options="dict.type.ccdi_project_status" :value="scope.row.status"/>
</div>
</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>
</template>
</el-table-column>
<!-- 创建时间 -->
<el-table-column
label="创建时间"
prop="createTime"
width="110"
align="center"
/>
<!-- 状态 -->
<el-table-column
label="状态"
prop="projectStatus"
width="90"
align="center"
>
<template slot-scope="scope">
<el-tag
:type="getStatusType(scope.row.projectStatus)"
size="medium"
effect="plain"
>
{{ getStatusLabel(scope.row.projectStatus) }}
</el-tag>
</template>
</el-table-column>
<!-- 目标人数 -->
<el-table-column
label="目标人数"
prop="targetCount"
width="80"
align="center"
>
<template slot-scope="scope">
<span class="count-number">{{ scope.row.targetCount }}</span>
</template>
</el-table-column>
<!-- 预警人数 -->
<el-table-column
label="预警人数"
width="90"
align="center"
>
<template slot-scope="scope">
<span :class="getWarningClass(scope.row)">{{ scope.row.warningCount }}</span>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column
label="操作"
width="200"
align="center"
fixed="right"
class-name="operation-column"
>
<template slot-scope="scope">
<div class="operation-buttons">
<!-- 进行中项目 -->
<template v-if="scope.row.projectStatus === '0'">
<el-button
size="mini"
type="text"
icon="el-icon-s-data"
@click="handleEnter(scope.row)"
>进入项目</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh"
@click="handleReAnalyze(scope.row)"
>重新分析</el-button>
</template>
<!-- 已完成项目 -->
<template v-else-if="scope.row.projectStatus === '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>
<!-- 已归档项目 -->
<template v-else>
<el-button
size="mini"
type="text"
icon="el-icon-document"
@click="handleDetail(scope.row)"
>查看详情</el-button>
</template>
<div class="warning-count-wrapper">
<span :class="getWarningClass(scope.row)" style="cursor: pointer;">
{{ scope.row.highRiskCount + scope.row.mediumRiskCount + scope.row.lowRiskCount }}
</span>
</div>
</template>
</el-table-column>
</el-table>
</el-tooltip>
</template>
</el-table-column>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
:current-page="pageParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageParams.pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 操作列 -->
<el-table-column
label="操作"
width="350"
align="left"
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>
</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: {
loading: {
type: Boolean,
default: false
},
dataList: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
},
total: {
type: Number,
default: 0
@@ -181,57 +190,49 @@ export default {
}
},
methods: {
/** 获取状态类型 */
getStatusType(status) {
const statusMap = {
'0': 'primary', // 进行中
'1': 'success', // 已完成
'2': 'info' // 已归档
getStatusColor(status) {
const colorMap = {
'0': '#1890ff', // 进行中 - 蓝色
'1': '#52c41a', // 已完成 - 绿色
'2': '#8c8c8c' // 已归档 - 灰色
}
return statusMap[status] || 'info'
return colorMap[status] || '#8c8c8c'
},
/** 获取状态标签 */
getStatusLabel(status) {
const statusMap = {
'0': '进行中',
'1': '已完成',
'2': '已归档'
}
return statusMap[status] || '未知'
},
/** 获取预警数量样式类名 */
getWarningClass(row) {
if (row.warningCount > 20) return 'warning-high'
if (row.warningCount > 10) return 'warning-medium'
return 'warning-normal'
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 ''
},
/** 进入项目 */
handleEnter(row) {
this.$emit('enter', row)
},
/** 查看详情 */
handleDetail(row) {
this.$emit('detail', row)
},
/** 查看结果 */
handleViewResult(row) {
this.$emit('view-result', row)
},
/** 重新分析 */
handleReAnalyze(row) {
this.$emit('re-analyze', row)
},
/** 归档 */
handleArchive(row) {
this.$emit('archive', row)
},
/** 分页大小变化 */
handleSizeChange(val) {
this.$emit('pagination', { pageSize: val, pageNum: 1 })
this.$emit('pagination', { pageNum: this.pageParams.pageNum, pageSize: val })
},
/** 当前页变化 */
handleCurrentChange(val) {
this.$emit('pagination', { pageNum: val })
this.$emit('pagination', { pageNum: val, pageSize: this.pageParams.pageSize })
}
}
}
@@ -239,115 +240,172 @@ export default {
<style lang="scss" scoped>
.project-table-container {
margin-bottom: 12px;
}
margin-top: 16px;
.table-card {
border-radius: 4px;
border: 1px solid #EBEEF5;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
// 表格整体样式 - 扁平化设计
::v-deep .el-table {
// 移除边框和卡片效果,设置透明背景
border: none !important;
background-color: transparent !important;
overflow: hidden;
:deep(.el-card__body) {
padding: 0;
}
}
// 表头样式 - 扁平化,无背景色
th.el-table__cell {
background-color: transparent !important;
color: #333;
font-weight: 600;
font-size: 14px;
height: 44px;
padding: 12px 10px;
.project-name-cell {
.name {
font-weight: 500;
color: #303133;
margin-bottom: 2px;
font-size: 14px;
}
.desc {
font-size: 12px;
color: #909399;
}
}
.count-number {
font-weight: 500;
color: #606266;
}
.warning-count-cell {
.warning-high {
color: #F56C6C;
font-weight: 600;
}
.warning-medium {
color: #E6A23C;
font-weight: 500;
}
.warning-normal {
color: #67C23A;
font-weight: 400;
}
}
.pagination-container {
padding: 12px 16px;
display: flex;
justify-content: flex-end;
border-top: 1px solid #EBEEF5;
}
// 表格行样式优化
:deep(.el-table) {
.el-table__row {
transition: background-color 0.3s;
&:hover {
background-color: #f5f7fa;
}
}
.el-table__body-wrapper {
&::-webkit-scrollbar {
width: 6px;
height: 6px;
// 只保留底部一条分隔线
border-bottom: 2px solid #e0e0e0 !important;
border-right: none !important;
}
&::-webkit-scrollbar-thumb {
background-color: #dcdfe6;
border-radius: 3px;
// 数据行样式 - 增加留白,移除分隔线
td.el-table__cell {
color: #333;
font-size: 14px;
height: 48px;
padding: 12px 10px;
border-bottom: none !important;
border-right: none !important;
}
&:hover {
background-color: #c0c4cc;
// 移除列分隔线
.el-table__body-wrapper {
.cell {
border-right: none;
}
}
}
// 操作列样式
.operation-column {
.cell {
padding: 0 8px;
// 悬停效果
.el-table__row {
transition: background-color 0.2s ease;
&:hover > td.el-table__cell {
background-color: #fafafa !important;
}
}
// 移除额外边框
&::before,
&::after {
display: none !important;
}
// 移除 inner border
.el-table__inner-wrapper::before {
display: none !important;
}
}
}
.operation-buttons {
display: flex;
.status-tag {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 2px;
flex-wrap: nowrap;
white-space: nowrap;
gap: 6px;
:deep(.el-button--mini) {
padding: 4px 6px;
.status-dot {
font-size: 10px;
line-height: 1;
}
}
.project-info-cell {
padding: 4px 0;
line-height: 1.4;
.project-name {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 2px;
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 风格
::v-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;
}
:deep(.el-button--mini .el-icon--left) {
margin-right: 2px;
&:first-child {
padding-left: 0;
}
:deep(.el-button + .el-button) {
margin-left: 0;
// 按钮间距
& + .el-button--text {
margin-left: 4px;
}
}
// 分页样式优化 - Material Design 风格
::v-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;
}
}
</style>

View File

@@ -2,7 +2,7 @@
<div class="quick-entry-container">
<div class="section-title">
<i class="el-icon-s-grid title-icon"></i>
<span>快捷入口</span>
<span>快捷方式</span>
</div>
<el-row :gutter="12">
<el-col :span="6">
@@ -12,7 +12,7 @@
</div>
<div class="card-content">
<div class="card-title">导入历史项目</div>
<div class="card-desc">从历史项目中快速创建新项目</div>
<div class="card-desc">从历史项目中导入配置</div>
</div>
</div>
</el-col>
@@ -23,7 +23,7 @@
</div>
<div class="card-content">
<div class="card-title">创建季度初核</div>
<div class="card-desc">按季度创建初核排查项目</div>
<div class="card-desc">创建季度初核</div>
</div>
</div>
</el-col>
@@ -34,7 +34,7 @@
</div>
<div class="card-content">
<div class="card-title">创建新员工排查</div>
<div class="card-desc">针对新入职员工的初核排查</div>
<div class="card-desc">创建新员工排查</div>
</div>
</div>
</el-col>
@@ -45,7 +45,7 @@
</div>
<div class="card-content">
<div class="card-title">创建高风险专项</div>
<div class="card-desc">针对高风险人员的专项排查</div>
<div class="card-desc">创建高风险专项</div>
</div>
</div>
</el-col>
@@ -123,7 +123,7 @@ export default {
.card-icon {
width: 48px;
height: 48px;
border-radius: 4px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
@@ -134,19 +134,19 @@ export default {
color: white;
&.import-icon {
background-color: #667eea;
background-color: #6B7280;
}
&.quarterly-icon {
background-color: #f5576c;
background-color: #3B82F6;
}
&.employee-icon {
background-color: #4facfe;
background-color: #10B981;
}
&.highrisk-icon {
background-color: #F56C6C;
background-color: #F59E0B;
}
}

View File

@@ -1,55 +1,27 @@
<template>
<div class="search-bar-container">
<el-card class="search-card" shadow="hover">
<el-row :gutter="12" align="middle">
<el-col :span="8">
<el-input
v-model="searchKeyword"
placeholder="请输入项目名称"
prefix-icon="el-icon-search"
clearable
size="medium"
@keyup.enter.native="handleSearch"
>
<el-button
slot="append"
icon="el-icon-search"
@click="handleSearch"
>搜索</el-button>
</el-input>
</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="11" 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>
</el-row>
</el-card>
<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>
</template>
@@ -60,81 +32,107 @@ export default {
showSearch: {
type: Boolean,
default: true
},
tabCounts: {
type: Object,
default: () => ({
all: 0,
'0': 0,
'1': 0,
'2': 0
})
}
},
data() {
return {
searchKeyword: '',
selectedStatus: '',
statusOptions: [
{ label: '进行中', value: '0' },
{ label: '已完成', value: '1' },
{ label: '已归档', value: '2' }
activeTab: 'all',
tabs: [
{ label: '全部项目', value: 'all', count: 0 },
{ label: '进行中', value: '0', count: 0 },
{ label: '已完成', value: '1', count: 0 },
{ label: '已归档', value: '2', count: 0 }
]
}
},
watch: {
tabCounts: {
handler(newVal) {
this.tabs = this.tabs.map(tab => ({
...tab,
count: newVal[tab.value] || 0
}))
},
immediate: true,
deep: true
}
},
methods: {
/** 搜索 */
handleSearch() {
this.emitQuery()
},
/** 状态变化 */
handleStatusChange() {
/** 标签页切换 */
handleTabChange(tabValue) {
this.activeTab = tabValue
this.emitQuery()
},
/** 发送查询 */
emitQuery() {
this.$emit('query', {
projectName: this.searchKeyword || null,
projectStatus: this.selectedStatus || null
status: this.activeTab === 'all' ? null : this.activeTab
})
},
/** 新增 */
handleAdd() {
this.$emit('add')
},
/** 导入 */
handleImport() {
this.$emit('import')
}
},
watch: {
searchKeyword(newVal) {
if (newVal === '') {
this.emitQuery()
}
}
}
}
</script>
<style lang="scss" scoped>
.search-bar-container {
margin-bottom: 12px;
.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-card {
border-radius: 4px;
border: 1px solid #EBEEF5;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
:deep(.el-card__body) {
padding: 12px 16px;
}
.search-input-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
:deep(.el-input-group__append) {
background-color: #409EFF;
color: white;
border-color: #409EFF;
.search-input {
width: 240px;
// Let Element UI's size="small" control the height naturally
}
.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 {
background-color: #66b1ff;
color: #3B82F6;
}
&.active {
color: #3B82F6;
background: #EFF6FF;
font-weight: 500;
}
}
:deep(.el-button--medium) {
padding: 10px 16px;
}
</style>

View File

@@ -3,15 +3,14 @@
<!-- 页面标题 -->
<div class="page-header">
<h2 class="page-title">初核项目管理</h2>
<p class="page-subtitle">管理纪检初核排查项目跟踪预警信息</p>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建项目</el-button>
</div>
<!-- 搜索和操作区 -->
<search-bar
:show-search="showSearch"
:tab-counts="tabCounts"
@query="handleQuery"
@add="handleAdd"
@import="handleImport"
/>
<!-- 项目列表表格 -->
@@ -20,8 +19,7 @@
:data-list="projectList"
:total="total"
:page-params="queryParams"
@pagination="getList"
@detail="handleDetail"
@pagination="handlePagination"
@enter="handleEnter"
@view-result="handleViewResult"
@re-analyze="handleReAnalyze"
@@ -63,13 +61,13 @@
</template>
<script>
import { getMockProjectList } from "@/api/ccdiProject";
import SearchBar from "./components/SearchBar";
import ProjectTable from "./components/ProjectTable";
import QuickEntry from "./components/QuickEntry";
import AddProjectDialog from "./components/AddProjectDialog";
import ImportHistoryDialog from "./components/ImportHistoryDialog";
import ArchiveConfirmDialog from "./components/ArchiveConfirmDialog";
import {listProject, getStatusCounts} from '@/api/ccdiProject'
import SearchBar from './components/SearchBar'
import ProjectTable from './components/ProjectTable'
import QuickEntry from './components/QuickEntry'
import AddProjectDialog from './components/AddProjectDialog'
import ImportHistoryDialog from './components/ImportHistoryDialog'
import ArchiveConfirmDialog from './components/ArchiveConfirmDialog'
export default {
name: "DpcProject",
@@ -96,7 +94,14 @@ export default {
pageNum: 1,
pageSize: 10,
projectName: null,
projectStatus: null,
status: null
},
// 标签页数量统计
tabCounts: {
all: 0,
'0': 0,
'1': 0,
'2': 0
},
// 新增/编辑弹窗
addDialogVisible: false,
@@ -115,17 +120,32 @@ export default {
methods: {
/** 查询项目列表 */
getList() {
this.loading = true;
// 使用Mock数据
getMockProjectList()
.then((response) => {
this.projectList = response.rows;
this.total = response.total;
this.loading = false;
})
.catch(() => {
this.loading = false;
});
this.loading = true
// 并行请求列表数据和状态统计
Promise.all([
listProject(this.queryParams),
getStatusCounts()
]).then(([listResponse, countsResponse]) => {
// 处理列表数据
this.projectList = listResponse.rows
this.total = listResponse.total
// 处理状态统计
const counts = countsResponse.data || {}
this.tabCounts = {
all: counts.all || 0,
'0': counts.status0 || 0,
'1': counts.status1 || 0,
'2': counts.status2 || 0
}
this.loading = false
}).catch((error) => {
this.loading = false
console.error('加载数据失败:', error)
this.$modal.msgError('加载数据失败,请稍后重试')
})
},
/** 搜索按钮操作 */
handleQuery(queryParams) {
@@ -135,6 +155,14 @@ export default {
this.queryParams.pageNum = 1;
this.getList();
},
/** 分页事件处理 */
handlePagination(pagination) {
if (pagination) {
this.queryParams.pageNum = pagination.pageNum
this.queryParams.pageSize = pagination.pageSize
}
this.getList()
},
/** 新增按钮操作 */
handleAdd() {
this.projectForm = this.getEmptyForm();
@@ -160,11 +188,9 @@ export default {
},
/** 提交项目表单 */
handleSubmitProject(data) {
// 这里应该调用实际的API
console.log("提交项目数据:", data);
this.$modal.msgSuccess("项目创建成功");
this.addDialogVisible = false;
this.getList();
// 不需要再次调用API因为AddProjectDialog已经处理了
this.addDialogVisible = false
this.getList() // 刷新列表
},
/** 导入历史项目 */
handleImport() {
@@ -199,11 +225,6 @@ export default {
this.addDialogTitle = "创建高风险专项项目";
this.addDialogVisible = true;
},
/** 查看详情 */
handleDetail(row) {
console.log("查看详情:", row);
this.$modal.msgInfo("查看项目详情: " + row.projectName);
},
/** 进入项目 */
handleEnter(row) {
this.$router.push({
@@ -239,30 +260,22 @@ export default {
<style lang="scss" scoped>
.dpc-project-container {
padding: 16px;
background: #f0f2f5;
padding: 24px;
background: #F8F9FA;
min-height: calc(100vh - 140px);
}
.page-header {
margin-bottom: 12px;
padding: 16px 20px;
background: #ffffff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.page-title {
margin: 0;
font-size: 18px;
font-size: 20px;
font-weight: 500;
color: #303133;
}
.page-subtitle {
margin: 4px 0 0 0;
font-size: 13px;
color: #909399;
font-weight: 400;
}
}
</style>

View File

@@ -37,6 +37,7 @@ module.exports = {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
target: baseUrl,
// target: "http://116.62.17.81:20202",
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''