hcm前端2版

This commit is contained in:
2025-12-22 16:54:11 +08:00
parent 007c450f63
commit 646b55048b
3 changed files with 863 additions and 0 deletions

View File

@@ -0,0 +1,368 @@
<template>
<div class="hrm-page">
<section class="panel-grid">
<el-card class="metal-panel" shadow="hover">
<div slot="header" class="panel-header">
<div class="header-left">
<span>流程任务</span>
<el-radio-group v-model="mode" size="small" @change="fetchList">
<el-radio-button label="todo">我的待办</el-radio-button>
<el-radio-button label="mine">我发起的</el-radio-button>
<el-radio-button label="all">全部</el-radio-button>
</el-radio-group>
<el-select
v-model="query.status"
placeholder="状态"
clearable
size="mini"
style="width: 140px"
@change="fetchList"
>
<el-option label="待办" value="pending" />
<el-option label="完成" value="done" />
<el-option label="拒绝" value="rejected" />
<el-option label="撤回" value="withdrawn" />
</el-select>
</div>
<div class="actions-inline">
<el-button size="mini" icon="el-icon-refresh" @click="fetchList">刷新</el-button>
</div>
</div>
<el-table :data="list" v-loading="loading" height="680" stripe @row-click="openDetail">
<el-table-column label="任务ID" prop="taskId" width="100" />
<el-table-column label="实例" prop="instId" width="100" />
<el-table-column label="业务类型" prop="bizType" min-width="100" />
<el-table-column label="节点" prop="nodeId" min-width="90" />
<el-table-column label="办理人" prop="assigneeUserId" min-width="120" />
<el-table-column label="状态" prop="status" min-width="100">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.status)">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="到期" prop="expireTime" min-width="150">
<template slot-scope="scope">{{ formatDate(scope.row.expireTime) }}</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" width="220" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" @click.stop="openDetail(scope.row)">详情</el-button>
<el-button
v-if="scope.row.status === 'pending'"
size="mini"
type="text"
@click.stop="openAction(scope.row, 'approve')"
>通过</el-button>
<el-button
v-if="scope.row.status === 'pending'"
size="mini"
type="text"
@click.stop="openAction(scope.row, 'reject')"
>驳回</el-button>
<el-button
size="mini"
type="text"
@click.stop="openAction(scope.row, 'withdraw')"
>撤回</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</section>
<el-drawer
title="任务详情"
:visible.sync="detailVisible"
size="60%"
append-to-body
>
<div v-if="detailTask" class="detail-wrap">
<el-descriptions :column="3" size="small" border class="mb12">
<el-descriptions-item label="任务ID">{{ detailTask.taskId }}</el-descriptions-item>
<el-descriptions-item label="实例">{{ detailTask.instId }}</el-descriptions-item>
<el-descriptions-item label="业务类型">{{ detailTask.bizType }}</el-descriptions-item>
<el-descriptions-item label="节点">{{ detailTask.nodeId }}</el-descriptions-item>
<el-descriptions-item label="办理人">{{ detailTask.assigneeUserId }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="statusType(detailTask.status)">{{ detailTask.status }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="到期">{{ formatDate(detailTask.expireTime) }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ detailTask.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<el-tabs value="form">
<el-tab-pane label="表单数据" name="form">
<el-table :data="formData" v-loading="formLoading" size="mini">
<el-table-column label="字段" prop="fieldName" min-width="140" />
<el-table-column label="值" prop="fieldValue" min-width="220" show-overflow-tooltip />
<el-table-column label="展示标签" prop="fieldLabel" min-width="160" show-overflow-tooltip />
</el-table>
</el-tab-pane>
<el-tab-pane label="流转历史" name="history">
<el-table :data="actionList" v-loading="actionLoading" size="mini">
<el-table-column label="动作" prop="action" min-width="120" />
<el-table-column label="办理人" prop="actionUserId" min-width="120" />
<el-table-column label="时间" prop="createTime" min-width="160">
<template slot-scope="scope">{{ formatDate(scope.row.createTime) }}</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="200" show-overflow-tooltip />
</el-table>
</el-tab-pane>
</el-tabs>
</div>
<div v-else class="placeholder">请选择任务查看详情</div>
</el-drawer>
<el-dialog
:title="actionDialogTitle"
:visible.sync="actionDialogVisible"
width="520px"
append-to-body
>
<el-form :model="actionForm" label-width="100px" size="small">
<el-form-item label="意见">
<el-input v-model="actionForm.remark" type="textarea" :rows="3" />
</el-form-item>
<template v-if="detailTask && detailTask.bizType === 'seal' && actionType === 'approve'">
<el-divider>盖章选填仅用印业务</el-divider>
<el-form-item label="待盖章文件">
<el-input v-model="actionForm.stampBo.targetFileUrl" placeholder="OSS URL" />
</el-form-item>
<el-form-item label="章图片">
<el-input v-model="actionForm.stampBo.stampImageUrl" placeholder="OSS URL" />
</el-form-item>
<el-form-item label="页码">
<el-input-number v-model="actionForm.stampBo.pageNo" :min="1" />
</el-form-item>
<el-form-item label="坐标 (x,y)">
<div class="coord-row">
<el-input-number v-model="actionForm.stampBo.xPx" :min="0" />
<el-input-number v-model="actionForm.stampBo.yPx" :min="0" />
</div>
</el-form-item>
<el-form-item label="尺寸 (可选)">
<div class="coord-row">
<el-input-number v-model="actionForm.stampBo.widthPx" :min="1" />
<el-input-number v-model="actionForm.stampBo.heightPx" :min="1" />
</div>
</el-form-item>
</template>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="actionDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="actionSubmitting" @click="submitAction">提交</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listFlowTask,
listTodoFlowTask,
approveFlowTask,
rejectFlowTask,
withdrawFlowTask,
listFlowAction,
listFlowFormData
} from '@/api/hrm'
export default {
name: 'HrmFlowTask',
data() {
return {
mode: 'todo',
query: { status: undefined, pageNum: 1, pageSize: 50 },
list: [],
loading: false,
detailVisible: false,
detailTask: null,
actionList: [],
actionLoading: false,
formData: [],
formLoading: false,
actionDialogVisible: false,
actionDialogTitle: '',
actionType: '',
actionSubmitting: false,
actionForm: {
remark: '',
stampBo: {
targetFileUrl: '',
stampImageUrl: '',
pageNo: 1,
xPx: 0,
yPx: 0,
widthPx: undefined,
heightPx: undefined
}
}
}
},
created() {
this.fetchList()
},
methods: {
statusType(status) {
if (!status) return 'info'
const map = { pending: 'warning', done: 'success', rejected: 'danger', withdrawn: 'info' }
return map[status] || 'info'
},
formatDate(val) {
if (!val) return ''
const d = new Date(val)
const p = n => (n < 10 ? `0${n}` : n)
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
},
fetchList() {
this.loading = true
const userId = this.$store?.state?.user?.userId
const params = { ...this.query }
let req
if (this.mode === 'todo' && userId) {
req = listTodoFlowTask(userId)
} else if (this.mode === 'mine' && userId) {
req = listFlowTask({ ...params, startUserId: userId })
} else {
req = listFlowTask(params)
}
req
.then(res => {
this.list = res.rows || res || []
})
.finally(() => {
this.loading = false
})
},
openDetail(row) {
this.detailTask = row
this.detailVisible = true
this.loadActions(row)
this.loadFormData(row)
},
loadActions(row) {
if (!row) return
if (row.instId && !/^\d+$/.test(row.instId)) {
this.$message.warning('实例ID需为数字已跳过加载动作')
return
}
this.actionLoading = true
listFlowAction({ instId: row.instId, pageNum: 1, pageSize: 100 })
.then(res => {
this.actionList = res.rows || res || []
})
.finally(() => {
this.actionLoading = false
})
},
loadFormData(row) {
if (!row) return
if (row.instId && !/^\d+$/.test(row.instId)) {
this.$message.warning('实例ID需为数字已跳过加载表单数据')
return
}
this.formLoading = true
listFlowFormData({ instId: row.instId, pageNum: 1, pageSize: 100 })
.then(res => {
this.formData = res.rows || res || []
})
.finally(() => {
this.formLoading = false
})
},
openAction(row, type) {
this.detailTask = row
this.actionType = type
const titleMap = { approve: '审批通过', reject: '驳回', withdraw: '撤回' }
this.actionDialogTitle = titleMap[type] || '操作'
this.actionForm = {
remark: '',
stampBo: {
targetFileUrl: '',
stampImageUrl: '',
pageNo: 1,
xPx: 0,
yPx: 0,
widthPx: undefined,
heightPx: undefined
}
}
this.actionDialogVisible = true
},
submitAction() {
if (!this.detailTask || !this.actionType) return
this.actionSubmitting = true
const payload = { remark: this.actionForm.remark }
if (this.actionType === 'approve' && this.detailTask.bizType === 'seal') {
// 仅当用户填了必要字段时传 stampBo
const sb = this.actionForm.stampBo
if (sb.targetFileUrl && sb.stampImageUrl && sb.pageNo && sb.xPx != null && sb.yPx != null) {
payload.stampBo = { ...sb }
}
}
const apiMap = {
approve: approveFlowTask,
reject: rejectFlowTask,
withdraw: withdrawFlowTask
}
apiMap[this.actionType](this.detailTask.taskId, payload)
.then(() => {
this.$message.success('操作成功')
this.actionDialogVisible = false
this.fetchList()
this.loadActions(this.detailTask)
})
.finally(() => {
this.actionSubmitting = false
})
}
}
}
</script>
<style lang="scss" scoped>
.hrm-page {
padding: 16px 20px 32px;
background: #f8f9fb;
}
.panel-grid {
display: grid;
grid-template-columns: 1fr;
}
.metal-panel {
border: 1px solid #d7d9df;
border-radius: 10px;
background: #fff;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #303133;
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.actions-inline {
display: flex;
gap: 8px;
align-items: center;
}
.detail-wrap {
padding-right: 4px;
}
.coord-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.placeholder {
color: #a0a3ad;
padding: 12px;
}
.mb12 {
margin-bottom: 12px;
}
</style>