| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- <template>
- <div>
- <ele-modal
- :visible.sync="visible"
- title="作业票管理"
- width="80vw"
- append-to-body
- :close-on-click-modal="false"
- @close="handleClose"
- >
- <div>
- <!-- 搜索区域 -->
- <div class="search-area">
- <seek-page
- ref="seekPage"
- :seekList="seekList"
- @search="handleSearch"
- :formLength="4"
- />
- </div>
- <!-- 列表视图 -->
- <div class="list-view">
- <!--
- v-if="work.id":el-dialog 首次打开才会渲染弹窗内容,表格也是此时才挂载。
- 表格挂载后由自身 mounted 触发首次加载,避免首次打开时 $refs.table 还不存在而不发请求。
- -->
- <ele-pro-table
- v-if="work.id"
- ref="table"
- row-key="id"
- :columns="columns"
- :datasource="datasource"
- :cache-key="cacheKeyUrl"
- max-height="500px"
- @columns-change="handleColumnChange"
- @selection-change="handleSelectionChange"
- >
- <template v-slot:toolbar>
- <div class="toolbar-left">
- <el-button
- type="primary"
- size="small"
- icon="el-icon-plus"
- @click="handleAdd"
- >申请作业票</el-button
- >
- <el-button
- type="danger"
- size="small"
- icon="el-icon-delete"
- :disabled="!selectedRows.length"
- @click="handleBatchDelete"
- >批量删除</el-button
- >
- </div>
- </template>
- <template v-slot:workType="{ row }">
- <span>{{ getWorkTypeName(row.workType) }}</span>
- </template>
- <template v-slot:status="{ row }">
- <el-tag :type="getStatusType(row.status)" size="small">
- {{ row.statusName || getStatusName(row.status) }}
- </el-tag>
- </template>
- <template v-slot:action="{ row }">
- <div class="action-links">
- <el-link
- type="primary"
- :underline="false"
- @click="handleView(row)"
- >查看</el-link
- >
- <el-link
- type="primary"
- :underline="false"
- @click="handleEdit(row)"
- >编辑</el-link
- >
- <el-link
- v-if="canSubmit(row)"
- type="success"
- :underline="false"
- :disabled="submittingId === row.id"
- @click="handleSubmit(row)"
- >提交</el-link
- >
- <el-link
- v-if="!isDraft(row)"
- type="primary"
- :underline="false"
- @click="handleFlow(row)"
- >流程</el-link
- >
- <el-link
- type="danger"
- :underline="false"
- @click="handleDelete(row)"
- >删除</el-link
- >
- </div>
- </template>
- </ele-pro-table>
- </div>
- </div>
- <add ref="addRef" @reload="reloadTable"> </add>
- </ele-modal>
- <!-- 审批流程(审批记录 + 高亮流程图) -->
- <ele-modal
- title="审批流程"
- :visible.sync="flowVisible"
- width="90%"
- append-to-body
- :close-on-click-modal="false"
- :maxable="true"
- >
- <flow v-if="flowVisible" :id="currentProcessInstanceId" />
- </ele-modal>
- </div>
- </template>
- <script>
- import tabMixins from '@/mixins/tableColumnsMixin';
- import dictMixins from '@/mixins/dictMixins';
- import dictEnum from '@/enum/dict';
- import add from './add.vue';
- import flow from '@/views/bpm/processInstance/detail.vue';
- import {
- ticketPage,
- ticketRemove,
- ticketSubmit,
- ticketGetById
- } from '@/api/dangerousWork';
- import {
- TICKET_STATUS,
- DRAFT_STATUS,
- REJECTED_STATUS,
- canSubmitStatus,
- canDeleteStatus,
- canEditStatus,
- getProcessDefinitionKey,
- getProcessInstanceId
- } from './constants';
- import { buildWorkTypeOptions, resolveWorkTypeLabel } from './workTypes';
- // 仅待提交(草稿)、已驳回状态的作业票可以删除
- const DELETE_TIP = '仅待提交(草稿)或已驳回状态的作业票可以删除';
- export default {
- mixins: [tabMixins, dictMixins],
- components: { add, flow },
- data() {
- return {
- visible: false,
- // 所属的危险作业
- work: {},
- // 表格选中的作业票
- selectedRows: [],
- // 正在提交审批的作业票 id
- submittingId: null,
- // 审批流程弹窗
- flowVisible: false,
- // 当前查看的流程实例 id
- currentProcessInstanceId: '',
- columns: [
- {
- columnKey: 'selection',
- type: 'selection',
- width: 50,
- align: 'center',
- fixed: 'left',
- // 只有待提交(草稿)、已驳回状态的作业票允许勾选,避免误删审批中或已通过的票
- selectable: (row) => this.canDelete(row)
- },
- {
- columnKey: 'index',
- label: '序号',
- type: 'index',
- width: 60,
- align: 'center'
- },
- {
- prop: 'workType',
- label: '作业类型',
- align: 'center',
- minWidth: 120,
- slot: 'workType'
- },
- {
- prop: 'applyDeptName',
- label: '申请单位',
- align: 'center',
- minWidth: 120,
- showOverflowTooltip: true
- },
- {
- prop: 'applicantName',
- label: '申请人',
- align: 'center',
- minWidth: 100
- },
- {
- prop: 'workLocation',
- label: '作业地点',
- align: 'center',
- minWidth: 140,
- showOverflowTooltip: true
- },
- {
- prop: 'startTime',
- label: '作业开始时间',
- align: 'center',
- minWidth: 160
- },
- {
- prop: 'endTime',
- label: '作业结束时间',
- align: 'center',
- minWidth: 160
- },
- {
- prop: 'status',
- label: '状态',
- align: 'center',
- minWidth: 100,
- slot: 'status'
- },
- {
- prop: 'createTime',
- label: '申请时间',
- align: 'center',
- minWidth: 160
- },
- {
- columnKey: 'action',
- label: '操作',
- width: 220,
- align: 'center',
- slot: 'action'
- }
- ],
- cacheKeyUrl: 'ehs-dangerousWorkTicket-list'
- };
- },
- computed: {
- seekList() {
- return [
- {
- label: '作业类型:',
- value: 'workType',
- type: 'select',
- width: 220,
- placeholder: '请选择作业类型',
- planList: this.workTypeList
- },
- {
- label: '申请人:',
- value: 'applicantName',
- type: 'input',
- width: 220,
- placeholder: '请输入申请人'
- },
- {
- label: '状态:',
- value: 'status',
- type: 'select',
- width: 220,
- placeholder: '请选择状态',
- planList: Object.keys(TICKET_STATUS).map((key) => ({
- label: TICKET_STATUS[key],
- value: Number(key)
- }))
- }
- ];
- },
- // 作业类型下拉(字典 ehs_hazardous_work_type)
- // 与申请作业票弹窗共用同一套构建逻辑,保证列表展示与申请页选择一致
- workTypeList() {
- return buildWorkTypeOptions(this.dict[dictEnum['危险作业类型']]);
- }
- },
- created() {
- this.requestDict('危险作业类型');
- },
- watch: {
- work: {
- immediate: true,
- handler(val) {
- // 首次打开弹窗时表格尚未挂载(el-dialog 懒渲染),由表格自身 mounted 触发加载;
- // 这里只处理弹窗已打开、切换所属危险作业的场景
- if (val?.id && this.$refs.table) {
- this.$refs.table.reload({ page: 1 });
- }
- }
- }
- },
- methods: {
- // 打开弹窗,work 可以是危险作业对象或危险作业id
- open(work) {
- this.work = work && work.id ? work : { id: work };
- // 重置上一次打开时残留的查询条件,保证界面条件与本次请求参数一致
- this.$refs.seekPage?.reset();
- this.visible = true;
- },
- handleAdd() {
- this.$refs.addRef.open(this.work);
- },
- // 查看作业票(详情由弹窗内部调 getById 查询,列表行数据不含作业票内容)
- handleView(row) {
- this.$refs.addRef.openTicket(row, this.work, 'view');
- },
- // 编辑作业票:待提交(草稿)与已驳回状态允许编辑
- handleEdit(row) {
- if (!this.canEdit(row)) {
- this.$message.warning('仅待提交(草稿)或已驳回状态的作业票可以编辑');
- return;
- }
- this.$refs.addRef.openTicket(row, this.work, 'edit');
- },
- // 提交作业票并发起审批流:审批流编码按作业类型取
- // 待提交(草稿)与已驳回状态都允许提交,已驳回的为重新提交
- handleSubmit(row) {
- if (!this.canSubmit(row)) {
- this.$message.warning('该作业票当前状态不可提交');
- return;
- }
- const processDefinitionKey = getProcessDefinitionKey(
- row.workType,
- this.getWorkTypeName(row.workType)
- );
- if (!processDefinitionKey) {
- this.$message.warning(
- '该作业类型暂未接入审批流,请联系管理员配置审批流编码'
- );
- return;
- }
- this.$confirm(
- Number(row?.status) === REJECTED_STATUS
- ? '该作业票已被驳回,重新提交后将重新发起审批流,确认提交吗?'
- : '提交后将发起审批流,作业票内容不可再修改,确认提交吗?',
- '提示',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }
- )
- .then(() => {
- this.submittingId = row.id;
- return ticketSubmit({ id: row.id, processDefinitionKey })
- .then(async (data) => {
- this.$message.success('提交成功,已发起审批');
- this.reloadTable();
- // 提交后自动打开审批流程:优先用响应里的流程实例 id,没有则回查作业票详情
- // 已驳回重新提交时,行数据里是上一轮审批的实例 id,必须回查详情取最新一轮
- const processInstanceId =
- getProcessInstanceId(data) ||
- (await this.resolveProcessInstanceId(row, true));
- if (processInstanceId) {
- this.openFlow(processInstanceId);
- }
- })
- .finally(() => {
- this.submittingId = null;
- });
- })
- .catch(() => {});
- },
- // 查看审批流程
- async handleFlow(row) {
- const processInstanceId = await this.resolveProcessInstanceId(row);
- if (!processInstanceId) {
- this.$message.warning('该作业票暂无审批流程');
- return;
- }
- this.openFlow(processInstanceId);
- },
- // 打开审批流程弹窗
- openFlow(processInstanceId) {
- this.currentProcessInstanceId = processInstanceId;
- this.flowVisible = true;
- },
- // 取作业票的流程实例 id:优先用列表行数据,没有再查作业票详情
- // @param preferDetail 为 true 时直接查详情(行数据里的实例 id 可能是上一轮审批的)
- async resolveProcessInstanceId(row, preferDetail = false) {
- if (!preferDetail) {
- const fromRow = getProcessInstanceId(row);
- if (fromRow) {
- return fromRow;
- }
- }
- try {
- const detail = await ticketGetById(row.id, {
- showErrorToast: false
- });
- return getProcessInstanceId(detail);
- } catch (e) {
- return '';
- }
- },
- handleClose() {
- this.work = {};
- this.visible = false;
- },
- handleSearch(where) {
- this.reloadTable({ page: 1, where });
- },
- // 表格可能尚未挂载(弹窗首次打开时),此处统一做存在性判断
- reloadTable(option) {
- this.$refs.table && this.$refs.table.reload(option);
- },
- // 作业类型名称:后端字典值 -> 内置作业类型名称 -> 原始编码
- getWorkTypeName(workType) {
- return (
- resolveWorkTypeLabel(workType, this.dict[dictEnum['危险作业类型']]) ||
- '-'
- );
- },
- // 作业票状态名称
- getStatusName(status) {
- return TICKET_STATUS[status] || '-';
- },
- getStatusType(status) {
- const typeMap = {
- 1: 'info',
- 2: 'warning',
- 3: 'success',
- 4: 'danger'
- };
- return typeMap[status] || 'info';
- },
- handleDelete(row) {
- if (!this.canDelete(row)) {
- this.$message.warning(DELETE_TIP);
- return;
- }
- this.$confirm(
- Number(row?.status) === REJECTED_STATUS
- ? '确认删除该已驳回的作业票吗?删除后不可恢复。'
- : '确认删除该草稿作业票吗?删除后不可恢复。',
- '提示',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }
- )
- .then(() => {
- ticketRemove([row.id]).then(() => {
- this.$message.success('删除成功');
- this.clearSelection();
- this.reloadTable();
- });
- })
- .catch(() => {});
- },
- // 批量删除:仅允许删除待提交(草稿)、已驳回状态的作业票
- handleBatchDelete() {
- const rows = this.selectedRows || [];
- if (!rows.length) {
- this.$message.warning('请至少选择一条数据');
- return;
- }
- if (rows.some((row) => !this.canDelete(row))) {
- this.$message.warning(DELETE_TIP);
- return;
- }
- this.$confirm(
- `确认删除选中的 ${rows.length} 条作业票吗?删除后不可恢复。`,
- '提示',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }
- )
- .then(() => {
- ticketRemove(rows.map((row) => row.id)).then(() => {
- this.$message.success('删除成功');
- this.clearSelection();
- this.reloadTable();
- });
- })
- .catch(() => {});
- },
- // 是否草稿(待提交)状态:草稿没有审批流实例,因此不展示「流程」入口
- isDraft(row) {
- return Number(row?.status) === DRAFT_STATUS;
- },
- // 是否允许编辑:待提交(草稿)与已驳回都允许
- canEdit(row) {
- return canEditStatus(row?.status);
- },
- // 是否允许提交(发起审批):待提交(草稿)与已驳回都允许
- canSubmit(row) {
- return canSubmitStatus(row?.status);
- },
- // 是否允许删除:待提交(草稿)与已驳回都允许
- canDelete(row) {
- return canDeleteStatus(row?.status);
- },
- // 多选
- handleSelectionChange(rows) {
- this.selectedRows = rows || [];
- },
- // 清空选中项
- clearSelection() {
- this.selectedRows = [];
- this.$refs.table && this.$refs.table.clearSelection();
- },
- async datasource({ page, limit, where }) {
- const body = {
- ...where,
- workId: this.work.id,
- pageNum: page,
- size: limit
- };
- // 去掉未填写的查询条件,避免空字符串传给后端类型转换报错
- Object.keys(body).forEach((key) => {
- if (
- body[key] === '' ||
- body[key] === null ||
- body[key] === undefined
- ) {
- delete body[key];
- }
- });
- return await ticketPage(body);
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .search-area {
- margin-bottom: 15px;
- padding-bottom: 15px;
- border-bottom: 1px solid #ebeef5;
- }
- .list-view {
- ::v-deep .ele-pro-table {
- .el-table {
- border: 1px solid #ebeef5;
- border-radius: 4px;
- }
- }
- }
- .action-links {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- gap: 8px;
- .el-link {
- font-size: 12px;
- }
- }
- </style>
|