Files
supervision-test-frontend-repo/src/views/dashboard/ProcessPortal/components/MyWork.vue
T
ye1023 7acbaf9d18 yxk-20260714
首页的待阅列表,增加直接已阅的按钮
2026-07-14 14:54:48 +08:00

473 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="my-work">
<div class="my-work__header">
<h2 class="my-work__title">我的工作</h2>
<div class="my-work__actions">
<div class="my-work__tabs">
<div class="my-work__tab" :class="{ 'my-work__tab--active': activeTab === 'todo' }" @click="activeTab = 'todo'">
待办
<span class="my-work__badge my-work__badge--danger">{{ todoTotal }}</span>
</div>
<div class="my-work__tab" :class="{ 'my-work__tab--active': activeTab === 'cc' }" @click="activeTab = 'cc'">待阅</div>
</div>
<ReloadOutlined class="my-work__refresh" :class="{ 'my-work__refresh--spinning': refreshing }" @click="handleRefresh" />
<a class="my-work__more" @click="goMore">
更多
<RightOutlined class="my-work__more-icon" />
</a>
</div>
</div>
<div class="my-work__body">
<template v-if="activeTab === 'todo'">
<BasicTable @register="registerTodoTable">
<template #headerCell="{ column }">
<HeaderCell v-if="column.dataIndex !== 'action'" :column="column" />
<span v-else class="action-header">
操作
<Tooltip placement="topLeft" :overlayStyle="{ maxWidth: '300px' }">
<template #title>
<div>
<b>转办</b>将任务转交他人办理对方办完后流程继续流转到下一环节不再回到本人<br />
<b>委托</b>将任务委托他人代办对方办完后任务会回到本人由本人继续完成流程
</div>
</template>
<QuestionCircleOutlined class="action-header__icon" />
</Tooltip>
</span>
</template>
<template #action="{ record }">
<TableAction :actions="getTodoTableAction(record)" />
</template>
</BasicTable>
</template>
<template v-if="activeTab === 'cc'">
<BasicTable @register="registerCcTable">
<template #action="{ record }">
<TableAction :actions="getCcTableAction(record)" />
</template>
</BasicTable>
</template>
</div>
<task-handle-modal @register="registerHandleModal" @success="reloadTodo" />
<SingleDeptUserModal title="选择委托人" @register="registerEntrusterModal" @selected="selectedEntruster" />
<SingleDeptUserModal title="选择转办人" @register="registerComplaintModal" @selected="selectedComplaint" />
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { BasicTable, TableAction } from '/@/components/Table';
import HeaderCell from '/@/components/Table/src/components/HeaderCell.vue';
import { useListPage } from '/@/hooks/system/useListPage';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { RightOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons-vue';
import { Tooltip } from 'ant-design-vue';
import TaskHandleModal from '/@/views/super/bpm/process/personalOffice/myHandleTask/modal/TaskHandleModal.vue';
import SingleDeptUserModal from '/@/components/semri/singleDeptUserComponent/SingleDeptUserModal.vue';
import { getTaskInfoForHistory } from '/@/views/super/bpm/process/personalOffice/myHandleTask/useTaskList';
import { todoColumns } from '../data/todo.data';
import { ccColumns } from '../data/cc.data';
import {
list as todoList,
taskNodeInfo,
taskEntrust,
taskClaim,
taskComplaint,
} from '/@/views/super/bpm/process/personalOffice/myHandleTask/task.handle.api';
import { list as ccList, markRead } from '/@/views/super/bpm/process/personalOffice/myCcTask/task.cc.api';
const router = useRouter();
const { createMessage } = useMessage();
const activeTab = ref<'todo' | 'cc'>('todo');
const todoTotal = ref(0);
const refreshing = ref(false);
const emit = defineEmits(['refresh']);
const { tableContext: todoContext } = useListPage({
designScope: 'process-portal-todo',
pagination: true,
tableProps: {
api: (params) => todoList('run', params),
columns: todoColumns,
showIndexColumn: true,
showTableSetting: false,
canResize: false,
scroll: { x: 900 },
actionColumn: {
dataIndex: 'action',
fixed: 'right',
width: 180,
},
useSearchForm: false,
},
});
const [registerTodoTable, { reload: reloadTodo }] = todoContext;
const { tableContext: ccContext } = useListPage({
designScope: 'process-portal-cc',
pagination: true,
tableProps: {
api: (params) => ccList({ ...params, isRead: 0 }),
columns: ccColumns,
showIndexColumn: true,
showTableSetting: false,
canResize: false,
scroll: { x: 1050 },
actionColumn: { dataIndex: 'action', fixed: 'right', width: 150 },
useSearchForm: false,
},
});
const [registerCcTable, { reload: reloadCc }] = ccContext;
onMounted(() => {
fetchTodoTotal();
});
async function fetchTodoTotal() {
try {
const res = await todoList('run', { pageNo: 1, pageSize: 1 });
todoTotal.value = res?.total ?? 0;
} catch (e) {
todoTotal.value = 0;
}
}
async function handleRefresh() {
refreshing.value = true;
try {
if (activeTab.value === 'todo') {
reloadTodo();
} else {
reloadCc();
}
await fetchTodoTotal();
emit('refresh');
} finally {
refreshing.value = false;
}
}
function goMore() {
if (activeTab.value === 'todo') {
router.push('/task/myTodoTaskInfo');
} else {
router.push('/task/myUnreadCcTaskList');
}
}
const [registerHandleModal, { openModal: openHandleModal }] = useModal();
const [registerEntrusterModal, { openModal: openEntrusterModal }] = useModal();
const [registerComplaintModal, { openModal: openComplaintModal }] = useModal();
async function handleProcess(record) {
let { formData, formUrl } = await getTaskNodeInfo(record);
formData['PROCESS_TAB_TYPE'] = 'run';
openHandleModal(true, { formData, formUrl, title: '流程办理' });
}
async function getTaskNodeInfo(record) {
let params = { taskId: record.id };
const result = await taskNodeInfo('run', params);
let procInsId = record.processInstanceId || (result.records ? result.records.BPM_INST_ID : '');
let realDataId = (result.records && result.records.business_id) || result.dataId;
let realTableName = (result.records && result.records.business_tablename) || result.tableName;
let formData: any = {
taskId: record.id,
taskDefKey: result.taskDefKey,
procInsId: procInsId,
dataId: realDataId,
tableName: realTableName,
processDataId: result.dataId,
processTableName: result.tableName,
permissionList: result.permissionList,
subPermissionList: result.subPermissionList,
vars: result.records,
};
let tempFormUrl = result.formUrl;
//节点配置表单URL,VUE组件类型对应的拓展参数
if (tempFormUrl && tempFormUrl.indexOf('?') != -1 && !isURL(tempFormUrl) && tempFormUrl.indexOf('{{DOMAIN_URL}}') == -1) {
tempFormUrl = result.formUrl.split('?')[0];
let qv: any = getQueryVariable(result.formUrl);
if (qv.edit == 1) {
formData['disabled'] = false;
}
formData.extendUrlParams = qv;
}
//如果没有taskId参数,程序自动追加,用于设计器表单节点权限
if (tempFormUrl != null && tempFormUrl.indexOf('{{DOMAIN_URL}}/desform/') != -1 && tempFormUrl.indexOf('taskId') == -1) {
tempFormUrl = tempFormUrl.trim();
if (tempFormUrl.endsWith('?')) {
tempFormUrl = tempFormUrl + 'taskId=' + result.taskDefKey;
} else {
tempFormUrl = tempFormUrl + '&taskId=' + result.taskDefKey;
}
}
return { formData, formUrl: tempFormUrl };
}
function isURL(s) {
return /^http[s]?:\/\/.*/.test(s);
}
function getQueryVariable(url) {
if (!url) return;
let t,
n,
r,
i = url.split('?')[1],
s = {};
(t = i.split('&')), (r = null), (n = null);
for (let o in t) {
let u = t[o].indexOf('=');
u !== -1 && ((r = t[o].substr(0, u)), (n = t[o].substr(u + 1)), (s[r] = n));
}
return s;
}
async function handleClaim(record) {
await taskClaim({ taskId: record.id });
await reloadTodo();
await fetchTodoTotal();
}
function handleSelectEntruster(record) {
openEntrusterModal(true, { taskId: record.id });
}
function handleComplaintEntruster(record) {
openComplaintModal(true, { taskId: record.id });
}
async function selectedEntruster(params) {
await taskEntrust(params);
await reloadTodo();
}
async function selectedComplaint(params) {
await taskComplaint(params);
await reloadTodo();
}
async function showCcHistory(record) {
let { formData, formUrl } = await getTaskInfoForHistory(record);
formData['PROCESS_TAB_TYPE'] = 'history';
openHandleModal(true, { formData, formUrl, title: '流程历史' });
}
async function handleCcRead(record) {
if (!record?.ccId) {
createMessage.warning('缺少抄送记录ID,无法标记已阅');
return;
}
await markRead(record.ccId);
await reloadCc();
}
function getTodoTableAction(record) {
let arr = [];
if (record.taskAssigneeId && record.taskAssigneeId != '') {
arr.push({ label: '办理', onClick: handleProcess.bind(null, record) });
} else {
arr.push({
label: '签收',
popConfirm: {
title: '确定签收吗?',
placement: 'left',
confirm: handleClaim.bind(null, record),
},
});
}
arr.push({ label: '转办', onClick: handleComplaintEntruster.bind(null, record) });
arr.push({ label: '委托', onClick: handleSelectEntruster.bind(null, record) });
return arr;
}
function getCcTableAction(record) {
return [
{ label: '查看详情', onClick: showCcHistory.bind(null, record) },
{ label: '已阅', onClick: handleCcRead.bind(null, record) },
];
}
</script>
<style lang="less" scoped>
.my-work {
background: #fff;
border-radius: 12px;
border: 1px solid #f0f0f0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
margin-bottom: 16px;
overflow: hidden;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px;
border-bottom: 1px solid #f0f0f0;
}
&__title {
font-size: 15px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
margin: 0;
}
&__refresh {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
transition: color 0.2s;
&:hover {
color: @primary-color;
}
&--spinning {
animation: spin 0.8s linear infinite;
color: @primary-color;
cursor: default;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
&__actions {
display: flex;
align-items: center;
gap: 16px;
}
&__tabs {
display: flex;
align-items: center;
gap: 4px;
padding: 3px;
background: #f5f5f5;
border-radius: 8px;
}
&__tab {
padding: 5px 14px;
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.45);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
user-select: none;
display: flex;
align-items: center;
gap: 6px;
&:hover {
color: rgba(0, 0, 0, 0.65);
}
&--active {
background: #fff;
color: rgba(0, 0, 0, 0.85);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
}
&__badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 5px;
font-size: 11px;
font-weight: 600;
border-radius: 9px;
line-height: 1;
&--danger {
background: #ff4d4f;
color: #fff;
}
}
&__more {
font-size: 13px;
color: #1890ff;
cursor: pointer;
display: flex;
align-items: center;
gap: 2px;
white-space: nowrap;
&:hover {
color: #40a9ff;
}
}
&__more-icon {
font-size: 10px;
}
&__body {
padding: 12px 20px;
.action-header {
display: inline-flex;
align-items: center;
gap: 4px;
&__icon {
color: rgba(0, 0, 0, 0.45);
cursor: help;
font-size: 14px;
&:hover {
color: @primary-color;
}
}
}
:deep(.ant-table) {
table {
table-layout: fixed;
width: 100%;
}
.ant-table-thead > tr > th {
background: #fafafa;
font-weight: 600;
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
padding: 10px 12px;
white-space: nowrap;
}
.ant-table-tbody > tr > td {
padding: 10px 12px;
font-size: 13px;
}
.ant-table-tbody > tr:hover > td {
background: #f0f7ff;
}
.ant-table-cell {
white-space: nowrap;
}
}
}
}
</style>