项管首页

This commit is contained in:
wkc
2026-01-30 11:01:13 +08:00
parent ac4e02e8c5
commit e99b05acc2
17 changed files with 2106 additions and 329 deletions

View File

@@ -0,0 +1,333 @@
<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="50"
show-word-limit
/>
</el-form-item>
<!-- 项目描述 -->
<el-form-item label="项目描述" prop="projectDesc">
<el-input
v-model="formData.projectDesc"
type="textarea"
:rows="3"
placeholder="请输入项目描述"
maxlength="200"
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>
</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>
export default {
name: 'AddProjectDialog',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: '新建项目'
},
form: {
type: Object,
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
},
rules: {
projectName: [
{ required: true, message: '请输入项目名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
startDate: [
{ required: true, message: '请选择开始日期', trigger: 'change' }
],
endDate: [
{ required: true, message: '请选择结束日期', trigger: 'change' },
{ validator: validateEndDate, trigger: 'change' }
],
targetCount: [
{ required: true, message: '请输入目标人数', trigger: 'blur' }
]
},
endDatePickerOptions: {
disabledDate: (time) => {
if (this.formData.startDate) {
return time.getTime() < new Date(this.formData.startDate).getTime()
}
return false
}
}
}
},
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: {
/** 添加人员 */
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(() => {
this.submitting = false
this.$emit('submit', { ...this.formData })
}, 500)
}
})
},
/** 关闭对话框 */
handleClose() {
this.$emit('close')
this.$refs.projectForm.resetFields()
this.formData = {
projectId: null,
projectName: '',
projectDesc: '',
startDate: '',
endDate: '',
targetCount: 0,
targetPersons: [],
autoWarning: true,
warningThreshold: 60
}
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;
.el-button + .el-button {
margin-left: 8px;
}
}
</style>

View File

@@ -0,0 +1,247 @@
<template>
<el-dialog
:visible.sync="dialogVisible"
title="归档确认"
width="500px"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="archive-confirm-content">
<!-- 警告图标 -->
<div class="warning-icon">
<i class="el-icon-warning"></i>
</div>
<!-- 确认文字 -->
<div class="confirm-text">
确定要归档项目"<span class="project-name-highlight">{{ projectData && projectData.projectName }}</span>"
</div>
<!-- 归档说明 -->
<div class="archive-info">
<div class="info-title">归档后将</div>
<ul class="info-list">
<li>
<i class="el-icon-check"></i>
<span>项目状态变为"已归档"</span>
</li>
<li>
<i class="el-icon-check"></i>
<span>自动生成项目报告PDF</span>
</li>
<li>
<i class="el-icon-check"></i>
<span>移入归档库可随时查看</span>
</li>
</ul>
</div>
<!-- 删除选项 -->
<div class="delete-option">
<el-checkbox v-model="deleteData">同时删除项目相关数据不可恢复</el-checkbox>
</div>
<!-- 提示信息 -->
<div class="archive-hint">
<i class="el-icon-info"></i>
<span>归档后可从"归档库"中查看和恢复</span>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button
type="primary"
:loading="submitting"
@click="handleConfirm"
>
<i v-if="!submitting" class="el-icon-folder-checked"></i>
确认归档
</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'ArchiveConfirmDialog',
props: {
visible: {
type: Boolean,
default: false
},
projectData: {
type: Object,
default: null
}
},
data() {
return {
submitting: false,
deleteData: false
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
if (!val) {
this.handleClose()
}
}
}
},
watch: {
visible(val) {
if (!val) {
this.deleteData = false
}
}
},
methods: {
handleConfirm() {
if (this.deleteData) {
this.$confirm('删除后数据将无法恢复,是否确认删除?', '警告', {
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.doArchive()
}).catch(() => {
// 取消删除,但仍归档
this.deleteData = false
this.doArchive()
})
} else {
this.doArchive()
}
},
doArchive() {
this.submitting = true
setTimeout(() => {
this.submitting = false
this.$emit('confirm', {
projectId: this.projectData.projectId,
deleteData: this.deleteData
})
}, 500)
},
handleClose() {
this.$emit('close')
this.deleteData = false
}
}
}
</script>
<style lang="scss" scoped>
.archive-confirm-content {
padding: 20px 0;
}
.warning-icon {
text-align: center;
margin-bottom: 20px;
i {
font-size: 64px;
color: #E6A23C;
}
}
.confirm-text {
text-align: center;
font-size: 16px;
color: #303133;
line-height: 1.6;
margin-bottom: 24px;
.project-name-highlight {
color: #409EFF;
font-weight: 600;
}
}
.archive-info {
background-color: #f5f7fa;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 16px;
.info-title {
font-size: 14px;
font-weight: 600;
color: #606266;
margin-bottom: 12px;
}
.info-list {
margin: 0;
padding-left: 0;
list-style: none;
li {
display: flex;
align-items: center;
font-size: 14px;
color: #606266;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
i {
color: #67C23A;
margin-right: 8px;
font-size: 16px;
font-weight: bold;
}
}
}
}
.delete-option {
margin-bottom: 16px;
padding: 12px;
background-color: #fef0f0;
border-radius: 6px;
border: 1px solid #fde2e2;
:deep(.el-checkbox__label) {
color: #F56C6C;
}
:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
color: #F56C6C;
}
}
.archive-hint {
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
color: #909399;
padding: 12px;
background-color: #ecf5ff;
border-radius: 6px;
border: 1px solid #d9ecff;
i {
margin-right: 6px;
font-size: 14px;
color: #409EFF;
}
}
.dialog-footer {
text-align: right;
.el-button + .el-button {
margin-left: 8px;
}
}
</style>

View File

@@ -0,0 +1,428 @@
<template>
<el-dialog
:visible.sync="dialogVisible"
title="导入历史项目"
width="700px"
:close-on-click-modal="false"
@close="handleClose"
>
<!-- 搜索区 -->
<div class="search-section">
<el-input
v-model="searchKeyword"
placeholder="搜索历史项目名称"
prefix-icon="el-icon-search"
clearable
size="small"
style="width: 300px"
>
<el-button slot="append" icon="el-icon-search" @click="handleSearch" />
</el-input>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
size="small"
style="width: 240px; margin-left: 12px"
/>
</div>
<!-- 历史项目列表 -->
<div class="project-list-section">
<div class="section-title">历史项目列表</div>
<el-radio-group v-model="selectedProjectId" class="project-radio-group">
<div
v-for="item in filteredProjects"
:key="item.projectId"
class="project-item-wrapper"
>
<el-radio :label="item.projectId" class="project-radio">
<div class="project-item">
<div class="project-header">
<span class="name">{{ item.projectName }}</span>
<el-tag
:type="getStatusType(item.projectStatus)"
size="mini"
>{{ getStatusLabel(item.projectStatus) }}</el-tag>
</div>
<div class="project-info">
<span class="info-item">
<i class="el-icon-time"></i>
创建时间: {{ item.createTime }}
</span>
<span class="info-item">
<i class="el-icon-user"></i>
目标人数: {{ item.targetCount }}
</span>
<span class="info-item warning">
<i class="el-icon-warning"></i>
预警人数: {{ item.warningCount }}
</span>
</div>
</div>
</el-radio>
</div>
</el-radio-group>
<el-empty
v-if="filteredProjects.length === 0"
description="暂无历史项目"
:image-size="100"
/>
</div>
<!-- 新项目配置 -->
<div class="new-project-config">
<el-divider />
<el-form
ref="configForm"
:model="configForm"
:rules="configRules"
label-width="100px"
label-position="right"
>
<el-form-item label="新项目名称" prop="newProjectName">
<el-input
v-model="configForm.newProjectName"
placeholder="新项目名称(可自动生成)"
maxlength="50"
/>
</el-form-item>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="开始日期" prop="startDate">
<el-date-picker
v-model="configForm.startDate"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束日期" prop="endDate">
<el-date-picker
v-model="configForm.endDate"
type="date"
placeholder="选择结束日期"
value-format="yyyy-MM-dd"
:picker-options="endDatePickerOptions"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button
type="primary"
:loading="submitting"
:disabled="!selectedProjectId"
@click="handleSubmit"
>
<i v-if="!submitting" class="el-icon-download"></i>
</el-button>
</div>
</el-dialog>
</template>
<script>
import {getMockHistoryProjects} from '@/api/dpcProject'
export default {
name: 'ImportHistoryDialog',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
// 结束日期验证规则
const validateEndDate = (rule, value, callback) => {
if (value && this.configForm.startDate && value < this.configForm.startDate) {
callback(new Error('结束日期不能早于开始日期'))
} else {
callback()
}
}
return {
submitting: false,
searchKeyword: '',
dateRange: null,
selectedProjectId: null,
historyProjects: [],
configForm: {
newProjectName: '',
startDate: '',
endDate: ''
},
configRules: {
newProjectName: [
{ required: true, message: '请输入新项目名称', trigger: 'blur' }
],
startDate: [
{ required: true, message: '请选择开始日期', trigger: 'change' }
],
endDate: [
{ required: true, message: '请选择结束日期', trigger: 'change' },
{ validator: validateEndDate, trigger: 'change' }
]
},
endDatePickerOptions: {
disabledDate: (time) => {
if (this.configForm.startDate) {
return time.getTime() < new Date(this.configForm.startDate).getTime()
}
return false
}
}
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
if (!val) {
this.handleClose()
}
}
},
filteredProjects() {
let result = [...this.historyProjects]
if (this.searchKeyword) {
result = result.filter(item =>
item.projectName.toLowerCase().includes(this.searchKeyword.toLowerCase())
)
}
if (this.dateRange && this.dateRange.length === 2) {
const [start, end] = this.dateRange
result = result.filter(item => {
const createTime = new Date(item.createTime)
return createTime >= new Date(start) && createTime <= new Date(end)
})
}
return result
}
},
watch: {
visible(val) {
if (val) {
this.loadHistoryProjects()
}
},
selectedProjectId(val) {
if (val) {
const project = this.historyProjects.find(p => p.projectId === val)
if (project) {
// 自动生成新项目名称
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
this.configForm.newProjectName = `${project.projectName}${year}-${month}-${day}复制)`
}
}
}
},
methods: {
/** 加载历史项目 */
loadHistoryProjects() {
getMockHistoryProjects().then(response => {
this.historyProjects = response.data
})
},
/** 获取状态类型 */
getStatusType(status) {
const statusMap = {
'0': 'primary',
'1': 'success',
'2': 'info'
}
return statusMap[status] || 'info'
},
/** 获取状态标签 */
getStatusLabel(status) {
const statusMap = {
'0': '进行中',
'1': '已完成',
'2': '已归档'
}
return statusMap[status] || '未知'
},
/** 搜索 */
handleSearch() {
// 搜索逻辑在 computed 中处理
},
/** 提交导入 */
handleSubmit() {
if (!this.selectedProjectId) {
this.$message.warning('请选择要导入的历史项目')
return
}
this.$refs.configForm.validate(valid => {
if (valid) {
this.submitting = true
const selectedProject = this.historyProjects.find(p => p.projectId === this.selectedProjectId)
setTimeout(() => {
this.submitting = false
this.$emit('submit', {
sourceProjectId: this.selectedProjectId,
sourceProject: selectedProject,
...this.configForm
})
}, 500)
}
})
},
/** 关闭对话框 */
handleClose() {
this.$emit('close')
this.selectedProjectId = null
this.searchKeyword = ''
this.dateRange = null
this.configForm = {
newProjectName: '',
startDate: '',
endDate: ''
}
this.$refs.configForm?.resetFields()
}
}
}
</script>
<style lang="scss" scoped>
.search-section {
display: flex;
align-items: center;
margin-bottom: 16px;
}
.project-list-section {
margin-bottom: 16px;
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
}
}
.project-radio-group {
width: 100%;
max-height: 300px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background-color: #dcdfe6;
border-radius: 3px;
&:hover {
background-color: #c0c4cc;
}
}
}
.project-item-wrapper {
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.project-radio {
display: block;
width: 100%;
:deep(.el-radio__label) {
width: calc(100% - 24px);
padding-left: 8px;
}
:deep(.el-radio__input) {
margin-top: 20px;
}
}
.project-item {
padding: 12px;
background-color: #f5f7fa;
border-radius: 8px;
transition: all 0.3s;
&:hover {
background-color: #ecf5ff;
}
.project-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
.name {
font-size: 15px;
font-weight: 500;
color: #303133;
}
}
.project-info {
display: flex;
flex-wrap: wrap;
gap: 16px;
.info-item {
font-size: 13px;
color: #909399;
display: flex;
align-items: center;
gap: 4px;
i {
font-size: 14px;
}
&.warning {
color: #E6A23C;
}
}
}
}
.new-project-config {
:deep(.el-divider) {
margin: 16px 0;
}
}
.dialog-footer {
text-align: right;
.el-button + .el-button {
margin-left: 8px;
}
}
:deep(.el-empty) {
padding: 20px 0;
}
</style>

View File

@@ -0,0 +1,347 @@
<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-column
type="index"
label="序号"
width="60"
align="center"
/>
<!-- 项目名称 -->
<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>
</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>
</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>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<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>
</div>
</template>
<script>
export default {
name: 'ProjectTable',
props: {
loading: {
type: Boolean,
default: false
},
dataList: {
type: Array,
default: () => []
},
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'
},
/** 获取状态标签 */
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'
},
/** 进入项目 */
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 })
},
/** 当前页变化 */
handleCurrentChange(val) {
this.$emit('pagination', { pageNum: val })
}
}
}
</script>
<style lang="scss" scoped>
.project-table-container {
margin-bottom: 12px;
}
.table-card {
border-radius: 4px;
border: 1px solid #EBEEF5;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
:deep(.el-card__body) {
padding: 0;
}
}
.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;
}
&::-webkit-scrollbar-thumb {
background-color: #dcdfe6;
border-radius: 3px;
&:hover {
background-color: #c0c4cc;
}
}
}
// 操作列样式
.operation-column {
.cell {
padding: 0 8px;
}
}
}
.operation-buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
flex-wrap: nowrap;
white-space: nowrap;
:deep(.el-button--mini) {
padding: 4px 6px;
font-size: 12px;
}
:deep(.el-button--mini .el-icon--left) {
margin-right: 2px;
}
:deep(.el-button + .el-button) {
margin-left: 0;
}
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<div class="quick-entry-container">
<div class="section-title">
<i class="el-icon-s-grid title-icon"></i>
<span>快捷入口</span>
</div>
<el-row :gutter="12">
<el-col :span="6">
<div class="entry-card" @click="handleImportHistory">
<div class="card-icon import-icon">
<i class="el-icon-folder-opened"></i>
</div>
<div class="card-content">
<div class="card-title">导入历史项目</div>
<div class="card-desc">从历史项目中快速创建新项目</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="entry-card" @click="handleCreateQuarterly">
<div class="card-icon quarterly-icon">
<i class="el-icon-date"></i>
</div>
<div class="card-content">
<div class="card-title">创建季度初核</div>
<div class="card-desc">按季度创建初核排查项目</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="entry-card" @click="handleCreateEmployee">
<div class="card-icon employee-icon">
<i class="el-icon-user"></i>
</div>
<div class="card-content">
<div class="card-title">创建新员工排查</div>
<div class="card-desc">针对新入职员工的初核排查</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="entry-card" @click="handleCreateHighRisk">
<div class="card-icon highrisk-icon">
<i class="el-icon-warning"></i>
</div>
<div class="card-content">
<div class="card-title">创建高风险专项</div>
<div class="card-desc">针对高风险人员的专项排查</div>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'QuickEntry',
methods: {
handleImportHistory() {
this.$emit('import-history')
},
handleCreateQuarterly() {
this.$emit('create-quarterly')
},
handleCreateEmployee() {
this.$emit('create-employee')
},
handleCreateHighRisk() {
this.$emit('create-highrisk')
}
}
}
</script>
<style lang="scss" scoped>
.quick-entry-container {
margin-top: 12px;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
font-weight: 500;
color: #606266;
.title-icon {
margin-right: 6px;
color: #909399;
font-size: 16px;
}
}
.entry-card {
display: flex;
align-items: center;
padding: 16px;
background: white;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
border: 1px solid #EBEEF5;
height: 100%;
&:hover {
transform: translateY(-2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
border-color: #409EFF;
.card-icon {
transform: scale(1.05);
}
}
&:active {
transform: translateY(0);
}
}
.card-icon {
width: 48px;
height: 48px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
flex-shrink: 0;
transition: all 0.2s ease;
font-size: 20px;
color: white;
&.import-icon {
background-color: #667eea;
}
&.quarterly-icon {
background-color: #f5576c;
}
&.employee-icon {
background-color: #4facfe;
}
&.highrisk-icon {
background-color: #F56C6C;
}
}
.card-content {
flex: 1;
}
.card-title {
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 2px;
}
.card-desc {
font-size: 12px;
color: #909399;
line-height: 1.4;
}
</style>

View File

@@ -0,0 +1,140 @@
<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>
</template>
<script>
export default {
name: 'SearchBar',
props: {
showSearch: {
type: Boolean,
default: true
}
},
data() {
return {
searchKeyword: '',
selectedStatus: '',
statusOptions: [
{ label: '进行中', value: '0' },
{ label: '已完成', value: '1' },
{ label: '已归档', value: '2' }
]
}
},
methods: {
/** 搜索 */
handleSearch() {
this.emitQuery()
},
/** 状态变化 */
handleStatusChange() {
this.emitQuery()
},
/** 发送查询 */
emitQuery() {
this.$emit('query', {
projectName: this.searchKeyword || null,
projectStatus: this.selectedStatus || null
})
},
/** 新增 */
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-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;
}
}
:deep(.el-input-group__append) {
background-color: #409EFF;
color: white;
border-color: #409EFF;
cursor: pointer;
&:hover {
background-color: #66b1ff;
}
}
:deep(.el-button--medium) {
padding: 10px 16px;
}
</style>