调整结构&待办任务页面

This commit is contained in:
Charles
2026-06-16 16:41:27 +08:00
parent 37deda18cc
commit c8b5b759f8
567 changed files with 240 additions and 583 deletions
+268
View File
@@ -0,0 +1,268 @@
<template>
<basic-container>
<!-- 页面标题和已办计数 -->
<div class="workflow-header">
<h3 class="workflow-title">我的已办</h3>
<el-tag type="success" size="large">已处理 {{ doneCount }} 项</el-tag>
</div>
<avue-crud
:option="option"
:table-loading="loading"
:data="data"
:page="page"
ref="crud"
v-model="form"
@search-change="searchChange"
@search-reset="searchReset"
@current-change="currentChange"
@size-change="sizeChange"
@refresh-change="refreshChange"
@on-load="onLoad"
>
<!-- 处理结果列自定义(已办接口暂不提供,预留) -->
<template #handleResult="{ row }">
<el-tag :type="row.handleResult === '已同意' ? 'success' : 'danger'">
{{ row.handleResult || '-' }}
</el-tag>
</template>
<!-- 自定义操作列 -->
<template #menu="scope">
<el-button
type="primary"
link
size="small"
@click.stop="handleView(scope.row)"
>查看</el-button>
<el-button
type="info"
link
size="small"
@click.stop="handleTransfer(scope.row)"
>流转</el-button>
</template>
</avue-crud>
<!-- 流转时间线弹窗 -->
<el-dialog
title="流程流转详情"
v-model="transferDialogVisible"
width="700px"
append-to-body
>
<div class="transfer-process-name">{{ currentProcessName }}</div>
<el-timeline>
<el-timeline-item
v-for="(step, index) in currentTrackSteps"
:key="index"
:type="step.type || 'primary'"
:timestamp="step.time"
placement="top"
>
<el-card :body-style="{ padding: '12px 16px' }">
<div class="step-header">
<span class="step-name">{{ step.stepName }}</span>
<el-tag
:type="step.resultType || 'primary'"
size="small"
effect="dark"
class="step-result"
>
{{ step.result }}
</el-tag>
</div>
<div class="step-info">
<span class="step-handler">
<el-icon><User /></el-icon>
{{ step.handler }}
</span>
</div>
<div v-if="step.remark" class="step-remark">
<el-icon><ChatDotSquare /></el-icon>
{{ step.remark }}
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</el-dialog>
</basic-container>
</template>
<script>
import { getDoneList, getDetail } from '@/api/workflow';
export default {
data() {
return {
form: {},
loading: true,
doneCount: 0,
page: {
pageSize: 10,
currentPage: 1,
total: 0,
},
query: {},
data: [],
// 流转弹窗
transferDialogVisible: false,
currentProcessName: '',
currentTrackSteps: [],
// Avue 表格配置
option: {
height: 'auto',
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
border: true,
index: true,
selection: false,
editBtn: false,
addBtn: false,
viewBtn: false,
delBtn: false,
menuWidth: 150,
dialogClickModal: false,
column: [
{
label: '流程名称',
prop: 'processName',
overHidden: true,
},
{
label: '申请人',
prop: 'tCreateUser',
width: 100,
},
{
label: '提交时间',
prop: 'tSubmitTime',
width: 180,
},
{
label: '当前节点',
prop: 'currentApprovalNode',
width: 140,
},
],
},
};
},
methods: {
searchReset() {
this.query = {};
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
refreshChange() {
this.onLoad(this.page, this.query);
},
onLoad(page, params = {}) {
this.loading = true;
const query = {
...this.query,
...params,
};
getDoneList(page.currentPage, page.pageSize, query).then(res => {
const data = res.data.data;
this.page.total = data.total;
this.data = data.records;
this.doneCount = data.total;
this.loading = false;
});
},
// 查看详情
handleView(row) {
this.$message.info(`查看流程详情: ${row.processName}`);
// TODO: 后续接入流程详情页面
},
// 流转查看(时间线弹窗)
handleTransfer(row) {
this.currentProcessName = row.processName;
// 若接口提供 trackSteps 则直接展示,否则尝试通过 instanceCode 获取
if (row.trackSteps && row.trackSteps.length) {
this.currentTrackSteps = row.trackSteps;
this.transferDialogVisible = true;
} else {
this.$message.info('暂无流转记录');
}
},
},
};
</script>
<style scoped>
.workflow-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 10px;
}
.workflow-title {
margin: 0;
font-size: 18px;
font-weight: 500;
}
.transfer-process-name {
font-size: 16px;
font-weight: 500;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid #e4e7ed;
color: #303133;
}
.step-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.step-name {
font-weight: 500;
font-size: 14px;
color: #303133;
}
.step-result {
margin-left: 8px;
}
.step-info {
color: #606266;
font-size: 13px;
margin-bottom: 6px;
}
.step-handler {
display: inline-flex;
align-items: center;
gap: 4px;
}
.step-remark {
color: #909399;
font-size: 13px;
padding: 8px;
background-color: #f5f7fa;
border-radius: 4px;
margin-top: 6px;
display: flex;
align-items: flex-start;
gap: 6px;
line-height: 1.5;
}
.step-remark .el-icon {
margin-top: 2px;
flex-shrink: 0;
}
</style>
+274
View File
@@ -0,0 +1,274 @@
<template>
<basic-container>
<!-- 页面标题和发起按钮 -->
<div class="workflow-header">
<h3 class="workflow-title">我的申请</h3>
<el-button type="primary" icon="el-icon-plus" @click="handleStart">
发起新流程
</el-button>
</div>
<avue-crud
:option="option"
:table-loading="loading"
:data="data"
:page="page"
ref="crud"
v-model="form"
@search-change="searchChange"
@search-reset="searchReset"
@current-change="currentChange"
@size-change="sizeChange"
@refresh-change="refreshChange"
@on-load="onLoad"
>
<!-- 状态列自定义 -->
<template #status="{ row }">
<el-tag
:type="
row.status === '已完成'
? 'success'
: row.status === '已驳回'
? 'danger'
: row.status === '待提交'
? 'info'
: 'warning'
"
:effect="row.status === '待提交' ? 'plain' : 'light'"
>
{{ row.status }}
</el-tag>
</template>
<!-- 自定义操作列 -->
<template #menu="scope">
<el-button
type="primary"
link
size="small"
@click.stop="handleView(scope.row)"
>查看</el-button>
<el-button
v-if="scope.row.status === '待提交'"
type="primary"
link
size="small"
@click.stop="handleEdit(scope.row)"
>编辑</el-button>
<el-button
v-if="scope.row.status === '待提交'"
type="danger"
link
size="small"
@click.stop="handleDelete(scope.row)"
>删除</el-button>
</template>
</avue-crud>
<!-- 发起流程弹窗 -->
<el-dialog
title="选择流程类型"
v-model="startDialogVisible"
width="600px"
append-to-body
>
<el-row :gutter="20">
<el-col
:span="8"
v-for="item in processTypes"
:key="item.value"
style="margin-bottom: 15px"
>
<el-card
shadow="hover"
:body-style="{ padding: '20px', textAlign: 'center', cursor: 'pointer' }"
@click="selectProcessType(item)"
>
<el-icon size="32" style="margin-bottom: 10px">
<Document />
</el-icon>
<div>{{ item.label }}</div>
</el-card>
</el-col>
</el-row>
</el-dialog>
</basic-container>
</template>
<script>
import { getMyList, getProcessTypes } from '@/api/workflow';
import { Document } from '@element-plus/icons-vue';
export default {
components: {
Document,
},
data() {
return {
form: {},
loading: true,
page: {
pageSize: 10,
currentPage: 1,
total: 0,
},
query: {},
data: [],
// 发起流程弹窗
startDialogVisible: false,
processTypes: [],
// Avue 表格配置
option: {
height: 'auto',
calcHeight: 32,
tip: false,
searchShow: false,
searchMenuSpan: 6,
border: true,
index: true,
selection: false,
editBtn: false,
addBtn: false,
viewBtn: false,
delBtn: false,
menuWidth: 200,
dialogClickModal: false,
column: [
{
label: '流程名称',
prop: 'processName',
search: true,
overHidden: true,
},
{
label: '状态',
prop: 'status',
slot: true,
width: 100,
search: true,
type: 'select',
dicData: [
{ label: '待提交', value: '待提交' },
{ label: '审批中', value: '审批中' },
{ label: '已完成', value: '已完成' },
{ label: '已驳回', value: '已驳回' },
],
},
{
label: '申请人',
prop: 'tCreateUser',
width: 100,
},
{
label: '申请时间',
prop: 'tArrivalTime',
width: 180,
},
{
label: '当前节点',
prop: 'currentApprovalNode',
width: 140,
},
],
},
};
},
mounted() {
this.loadProcessTypes();
},
methods: {
loadProcessTypes() {
getProcessTypes().then(res => {
const data = res.data.data;
this.processTypes = data.map(item => ({
label: item.processName,
value: item.processId,
}));
});
},
searchReset() {
this.query = {};
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
refreshChange() {
this.onLoad(this.page, this.query);
},
onLoad(page, params = {}) {
this.loading = true;
const query = {
...this.query,
...params,
};
getMyList(page.currentPage, page.pageSize, query).then(res => {
const data = res.data.data;
this.page.total = data.total;
this.data = data.records;
this.loading = false;
});
},
// 发起新流程
handleStart() {
this.startDialogVisible = true;
},
// 选择流程类型
selectProcessType(item) {
this.startDialogVisible = false;
this.$message.success(`选择了流程类型: ${item.label}`);
// TODO: 后续接入流程发起表单页面
},
// 查看详情
handleView(row) {
this.$message.info(`查看流程详情: ${row.processName}`);
// TODO: 后续接入流程详情页面
},
// 编辑(仅待提交状态)
handleEdit(row) {
this.$message.info(`编辑流程: ${row.processName}`);
// TODO: 后续接入编辑表单页面
},
// 删除(仅待提交状态)
handleDelete(row) {
this.$confirm(`确认删除流程 "${row.processName}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
// 从列表中移除
const index = this.data.findIndex(item => item.id === row.id);
if (index !== -1) {
this.data.splice(index, 1);
this.page.total -= 1;
}
this.$message.success('删除成功');
}).catch(() => {});
},
},
};
</script>
<style scoped>
.workflow-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 10px;
}
.workflow-title {
margin: 0;
font-size: 18px;
font-weight: 500;
}
</style>
+236
View File
@@ -0,0 +1,236 @@
<template>
<basic-container>
<div class="my-process">
<!-- 页面标题 -->
<div class="page-header">
<h3 class="page-title">我的流程</h3>
<el-tag type="info" class="process-count">共 {{ processList.length }} 个流程</el-tag>
</div>
<!-- 流程卡片网格 -->
<div class="process-grid">
<div
v-for="process in processList"
:key="process.id"
class="process-card"
:style="{ '--process-color': process.processColor }"
@click="handleStartProcess(process)"
>
<div class="process-content">
<div
class="process-icon"
:style="{ backgroundColor: process.iconColor }"
>
<el-icon :size="36"><component :is="process.icon" /></el-icon>
</div>
<div class="process-info">
<div class="process-name">{{ process.name }}</div>
<div class="process-category">{{ process.category }}</div>
<el-tag
:type="process.status === 'active' ? 'success' : 'info'"
size="small"
class="process-status"
>
{{ process.status === 'active' ? '启用' : '停用' }}
</el-tag>
</div>
</div>
</div>
</div>
</div>
</basic-container>
</template>
<script>
import { getProcessTypes } from '@/api/workflow';
import { Document, OfficeBuilding, ShoppingCart, Connection } from '@element-plus/icons-vue';
export default {
data() {
return {
loading: false,
processList: [],
};
},
mounted() {
this.loadProcessTypes();
},
methods: {
loadProcessTypes() {
this.loading = true;
getProcessTypes().then(res => {
const data = res.data.data;
// 将接口返回的 {processName, processId} 映射为页面需要的结构
const iconMap = [
{ icon: Document, iconColor: '#67c23a', processColor: '#67c23a', category: '非经营性商品管理' },
{ icon: OfficeBuilding, iconColor: '#409eff', processColor: '#409eff', category: '招标管理' },
{ icon: Document, iconColor: '#e6a23c', processColor: '#e6a23c', category: '招标管理' },
{ icon: Connection, iconColor: '#909399', processColor: '#909399', category: '订单管理' },
{ icon: ShoppingCart, iconColor: '#f56c6c', processColor: '#f56c6c', category: '询比价管理' },
];
this.processList = data.map((item, index) => {
const style = iconMap[index % iconMap.length];
return {
id: item.processId,
code: item.processId,
name: item.processName,
category: style.category,
status: 'active',
version: '1.0',
icon: style.icon,
iconColor: style.iconColor,
processColor: style.processColor,
};
});
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
handleStartProcess(process) {
this.$message.info(`流程"${process.name}"暂未实现`);
// TODO: 后续接入流程发起表单页面
},
},
};
</script>
<style scoped>
.my-process {
width: 100%;
min-height: calc(100vh - 140px);
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 0 10px;
}
.page-title {
margin: 0;
font-size: 18px;
font-weight: 500;
color: #303133;
}
.process-count {
height: 32px;
padding: 0 12px;
font-size: 14px;
}
.process-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 24px;
padding: 10px;
}
.process-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 30px 20px;
border: 1px solid #e4e7ed;
border-radius: 16px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
position: relative;
overflow: hidden;
}
.process-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--process-color) 0%, transparent 100%);
}
.process-card:hover {
border-color: var(--process-color);
box-shadow: 0 8px 30px rgba(64, 158, 255, 0.2);
transform: translateY(-4px);
}
.process-card:hover::before {
background: linear-gradient(90deg, var(--process-color) 0%, transparent 100%);
}
.process-content {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
z-index: 1;
}
.process-icon {
width: 72px;
height: 72px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-bottom: 20px;
transition: all 0.3s ease;
}
.process-card:hover .process-icon {
transform: scale(1.1) rotate(5deg);
}
.process-info {
text-align: center;
padding: 0 10px;
}
.process-name {
font-size: 17px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
line-height: 1.4;
}
.process-category {
font-size: 14px;
color: #909399;
margin-bottom: 12px;
}
.process-status {
margin-top: 4px;
height: 24px;
padding: 0 8px;
font-size: 12px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.my-process {
padding: 15px;
}
.process-grid {
gap: 16px;
padding: 5px;
}
.process-card {
padding: 20px 15px;
}
.process-name {
font-size: 15px;
}
}
</style>
+250
View File
@@ -0,0 +1,250 @@
<template>
<basic-container>
<!-- 页面标题和待办计数 -->
<div class="workflow-header">
<h3 class="workflow-title">我的待办</h3>
<el-tag type="danger" size="large">待处理 {{ todoCount }} 项</el-tag>
</div>
<avue-crud
:option="option"
:table-loading="loading"
:data="data"
:page="page"
ref="crud"
v-model="form"
@search-change="searchChange"
@search-reset="searchReset"
@current-change="currentChange"
@size-change="sizeChange"
@refresh-change="refreshChange"
@on-load="onLoad"
>
<!-- 自定义阅知状态列 -->
<template #pendingSubStatus="scope">
<el-tag
:type="scope.row.pendingSubStatus === '待阅' ? 'warning' : 'success'"
size="small"
>
{{ scope.row.pendingSubStatus || '待办' }}
</el-tag>
</template>
<!-- 自定义操作列 -->
<template #menu="scope">
<el-button
type="primary"
link
size="small"
@click.stop="handleApprove(scope.row)"
>审批</el-button>
<el-button
type="primary"
link
size="small"
@click.stop="handleView(scope.row)"
>查看</el-button>
</template>
</avue-crud>
<!-- 统一审批弹窗(同意/驳回) -->
<el-dialog
title="审批处理"
v-model="approveDialogVisible"
width="500px"
append-to-body
>
<el-form :model="approveForm" label-width="80px">
<el-form-item label="流程名称">
<el-input v-model="approveForm.processName" disabled />
</el-form-item>
<el-form-item label="申请人">
<el-input v-model="approveForm.applicantName" disabled />
</el-form-item>
<el-form-item label="处理结果" required>
<el-radio-group v-model="approveForm.result">
<el-radio label="同意">同意</el-radio>
<el-radio label="驳回">驳回</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="approveForm.result === '驳回' ? '驳回原因' : '审批意见'">
<el-input
v-model="approveForm.comment"
type="textarea"
:rows="3"
:placeholder="approveForm.result === '驳回' ? '请输入驳回原因' : '请输入审批意见(选填)'"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="approveDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmApprove">确 定</el-button>
</template>
</el-dialog>
</basic-container>
</template>
<script>
import { getTodoList, approve, reject, getTodoCount } from '@/api/workflow';
export default {
data() {
return {
form: {},
loading: true,
todoCount: 0,
page: {
pageSize: 10,
currentPage: 1,
total: 0,
},
query: {},
data: [],
// 审批弹窗
approveDialogVisible: false,
approveForm: {
id: '',
processName: '',
applicantName: '',
result: '同意',
comment: '',
},
// Avue 表格配置
option: {
height: 'auto',
calcHeight: 32,
tip: false,
searchShow: true,
searchMenuSpan: 6,
border: true,
index: true,
selection: false,
editBtn: false,
addBtn: false,
viewBtn: false,
delBtn: false,
menuWidth: 140,
dialogClickModal: false,
column: [
{
label: '流程名称',
prop: 'processName',
overHidden: true,
},
{
label: '申请人',
prop: 'tCreateUser',
width: 100,
},
{
label: '到达时间',
prop: 'tArrivalTime',
width: 180,
},
{
label: '提交时间',
prop: 'tSubmitTime',
width: 180,
},
{
label: '当前节点',
prop: 'currentApprovalNode',
width: 140,
},
{
label: '阅知状态',
prop: 'pendingSubStatus',
slot: true,
width: 100,
},
],
},
};
},
methods: {
searchReset() {
this.query = {};
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
refreshChange() {
this.onLoad(this.page, this.query);
},
onLoad(page, params = {}) {
this.loading = true;
const query = {
...this.query,
...params,
};
getTodoList(page.currentPage, page.pageSize, query).then(res => {
const data = res.data.data;
this.page.total = data.total;
this.data = data.records;
this.loading = false;
});
// 获取待办数量
getTodoCount().then(res => {
this.todoCount = res.data.data;
});
},
// 审批操作(统一弹窗)
handleApprove(row) {
this.approveForm = {
id: row.id,
taskId: row.taskId,
instanceCode: row.instanceCode,
processName: row.processName,
applicantName: row.tCreateUser,
result: '同意',
comment: '',
};
this.approveDialogVisible = true;
},
confirmApprove() {
if (this.approveForm.result === '驳回' && !this.approveForm.comment) {
this.$message.warning('请输入驳回原因');
return;
}
const api = this.approveForm.result === '同意' ? approve : reject;
api(this.approveForm).then(res => {
if (res.data.code === 200) {
this.$message.success(this.approveForm.result === '同意' ? '审批成功' : '驳回成功');
this.approveDialogVisible = false;
this.refreshChange();
}
});
},
// 查看详情
handleView(row) {
this.$message.info(`查看流程详情: ${row.processName}`);
// TODO: 后续接入流程详情页面
},
},
};
</script>
<style scoped>
.workflow-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 10px;
}
.workflow-title {
margin: 0;
font-size: 18px;
font-weight: 500;
}
</style>