局长信箱-内网端(前端)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

955 lines
32 KiB

<template>
<el-dialog
v-model="visible"
:show-close="false"
width="84vw"
top="2vh"
class="dialog-header-nopadding"
style="--el-dialog-padding-primary: 10px; margin-bottom: 2vh"
>
<template #header="{ close, titleId, titleClass }">
<header class="flex between v-center dialog-header">
<div class="ml-16">
<span class="mr-8 second">受理编号</span>
<span>{{ mail.id }}</span>
</div>
<div class="flex step-box">
<div
class="step flex center v-center"
v-for="(label, index) in steps"
:key="index"
:active="activeStep === index"
:completed="index < activeStep"
>
<span class="bloder">{{ index + 1 }}</span>
<span>{{ label }}</span>
</div>
</div>
<div class="flex">
<!-- #FA8695 -->
<el-button
type="primary"
size="large"
:class="isFav ? 'fav-btn active' : 'fav-btn'"
@click="handleFav"
>{{ isFav ? "已收藏" : "收藏" }}
<template #icon>
<icon
:name="
isFav
? 'el-icon-StarFilled'
: 'el-icon-star'
"
:size="22"
/>
</template>
</el-button>
<el-button
link
circle
size="large"
@click="close"
class="close-btn"
>
<template #icon>
<icon
name="el-icon-close"
:size="26"
:color="'#fff'"
/>
</template>
</el-button>
</div>
</header>
</template>
<main v-loading="loading">
<el-row :gutter="20" style="height: 100%">
<el-col :span="5" style="height: 100%">
<div
style="height: 100%; padding: 0 20px 0 40px"
ref="leftContainerRef"
>
<div class="timer flex center" v-if="flowNode.key !== 'completion'">
<div
v-if="mail.flowRemainingTime > 0"
style="height: 210px"
>
<el-progress
type="dashboard"
:percentage="percentage"
:stroke-width="16"
:width="240"
:color="colors"
class="timer"
>
<RemainingTime
v-model:time="mail.flowRemainingTime"
/>
<span
style="font-size: 12px; color: #ff0000"
v-if="mail.extensionFlag"
>已延期1{{ mail.extensionDays }}天</span
>
</el-progress>
</div>
<div
v-else
class="error flex column center v-center"
>
<RemainingTime
v-model:time="mail.flowRemainingTime"
/>
</div>
</div>
<div ref="mailInfoRef">
<div class="col mb-10" v-if="mail.mailCategory">
<label style="width: 56px">信件分类</label>
<span style="width: calc(100% - 74px)">
<span v-if="mail.mailFirstCategory">{{
mail.mailFirstCategory
}}</span>
<span v-if="mail.mailSecondCategory">{{
" > " + mail.mailSecondCategory
}}</span>
<span v-if="mail.mailThreeCategory">{{
" > " + mail.mailThreeCategory
}}</span>
</span>
</div>
<div
class="col"
v-if="mail.mailLevel"
style="padding-bottom: 20px"
>
<label>信件等级</label>
<mail-level
:value="mail.mailLevel"
:list="dictData.mail_level"
/>
</div>
</div>
<div class="flow">
<header
class="flex between v-center flow-header"
ref="flowHeaderRef"
>
<span>
<span class="second mr-8">总耗时</span>
<span style="color: var(--primary-color)">{{ getTotalTime() }}</span>
</span>
<el-button
type="primary"
size="small"
plain
:disabled="true"
>节点流程图</el-button
>
</header>
<el-scrollbar :max-height="flowMaxHeight">
<div class="flow-container">
<div v-for="item in flows" :key="item.id">
<div class="mb-4 mt-4 flow-info">
<span class="second mr-8">{{
item.createTime
}}</span>
<span class="mr-8">{{
getFlowHandler(item)
}}</span>
<span class="primary">{{
item.flowAfterName
}}</span>
</div>
<div
v-if="item.flowKey !== 'first_sign'"
class="flow-time"
>
<span class="second mr-8"
>用时</span
>
<span class="primary">{{
formatTimeText(
item.consumingTime
)
}}</span>
</div>
</div>
<div
class="text-center mt-20"
v-if="!flows.length"
style="color: #999"
>
</div>
</div>
</el-scrollbar>
</div>
</div>
</el-col>
<el-col :span="19" style="height: 100%">
<el-scrollbar max-height="100%" class="main-container">
<MailReturnDetail
v-if="mailReturns.length && workType === 'processing'"
:mailReturns="mailReturns"
/>
<template v-if="webComponents.indexOf('Comments') > -1">
<Comments :approvals="approvals" />
</template>
<template
v-if="webComponents.indexOf('MainContactInfo') > -1"
>
<MainContactInfo :mail="mail" />
</template>
<template
v-if="webComponents.indexOf('MailTypeForm') > -1 && !disabled"
>
<MailTypeForm
ref="mailTypeFormRef"
v-model:data="requestData"
:mailId="mailId"
/>
</template>
<template
v-if="webComponents.indexOf('DeptSelectForm') > -1 && !disabled"
>
<DeptSelectForm
ref="deptSelectFormRef"
v-model:data="requestData"
:secondaryResponsibleFlag="
mail.secondaryResponsibleFlag
"
:flowKey="flowNode.key"
:mail="mail"
/>
</template>
<ExtensionDetail
:mail="mail"
v-if="
mail.extensionState &&
mail.extensionState !== 'completion'
"
/>
<template
v-if="
webComponents.indexOf('ThreeHandling') > -1"
>
<ThreeHandling
v-model:data="requestData"
:mail="mail"
:limitedTime="flowNode.limitedTime"
ref="threeHandlingRef"
v-if="!disabled && workType === 'processing'"
/>
<ThreeHandlingDetail
v-else
:mail="mail"
/>
</template>
<template
v-if="
webComponents.indexOf('MailApprovalDetail') >
-1 || workType === 'extension_approval'
"
>
<MailApprovalDetail
:mail="mail"
v-model:data="requestData"
/>
</template>
<template
v-if="
webComponents.indexOf('CountersignForm') > -1 &&
workType === 'dept_countersign'
"
>
<CountersignForm
v-model:data="requestData"
:mail="mail"
ref="countersignFormRef"
/>
</template>
<template
v-if="
mail.countersigns?.length &&
workType !== 'dept_countersign'
"
>
<Countersign :mail="mail" />
</template>
<template
v-if="webComponents.indexOf('CoHandling') > -1"
>
<CoHandling
v-model:data="requestData"
ref="coHandlingRef"
/>
</template>
<template
v-if="webComponents.indexOf('CompletionDetail') > -1"
>
<CompletionDetail
:mail="mail"
/>
</template>
<div style="height: 20px"></div>
</el-scrollbar>
</el-col>
</el-row>
</main>
<footer class="flex between">
<div></div>
<div v-if="!disabled && !completionBtnFlag">
<template v-if="workType === 'processing' || workType === 'co_organizers'">
<template v-for="action in actions" :key="action.key">
<el-button
:type="action.btnType"
:plain="action.btnPlain"
size="large"
@click="handleAction(action.key)"
v-if="
action.key !== 'applyExtension' ||
(mail.extensionState !== 'applying' &&
mail.extensionState !== 'completion')
"
:disabled="loading ||
(mail.extensionState === 'applying' &&
action.key === 'applicationCompleted')
"
>{{ action.btnLabel }}</el-button
>
</template>
</template>
<el-button
v-if="
workType === 'processing' &&
flowNode.key === 'countersign'
"
size="large"
:disabled="
mail.countersignCompleted < mail.countersignTotal
"
>会签中{{
`(${mail.countersignCompleted} / ${mail.countersignTotal})`
}}</el-button
>
<template v-if="workType === 'dept_countersign'">
<el-button
size="large"
type="primary"
@click="handleAction('submitCountersign')"
>提交会签</el-button
>
</template>
<template v-if="workType === 'extension_approval'">
<el-button
size="large"
type="danger"
@click="extensionApprovalReturnShow = true"
>审批驳回</el-button
>
<el-button
size="large"
type="primary"
@click="handleExtensionApprovalSubmit"
>审批通过</el-button
>
</template>
</div>
<el-button type="primary" size="large" v-if="completionBtnFlag" @click="handleAction('confirmedCompletion')"
>办结处理</el-button
>
</footer>
</el-dialog>
<Message ref="messageRef" />
<ApplicationCompleted
v-model="completeShow"
v-model:data="requestData"
:mail="mail"
@submit="(key) => handleAction(key)"
@close="completeShow = false"/>
<ReviewComments
v-model="approvedShow"
v-model:data="requestData"
:mail="mail"
@submit="(key) => handleAction(key)"
@close="approvedShow = false"
/>
<ConfirmedCompletion
v-model:show="completionShow"
v-model:data="requestData"
:mail="mail"
@submit="(key) => handleAction(key)"
/>
<InitiateCountersign
v-model:show="countersignShow"
v-model:data="requestData"
:mail="mail"
@submit="(key) => handleAction(key)"
/>
<MailReturn
v-model="returnShow"
v-model:data="requestData"
:mail="mail"
@submit="(key) => handleAction(key)"
@close="returnShow = false"
/>
<ApplyExtension
v-model:show="applyExtensionShow"
v-model:data="requestData"
@submit="(key) => handleAction(key)"
/>
<ExtensionApprovalReturn
v-model="extensionApprovalReturnShow"
v-model:data="requestData"
@submit="() => handleExtensionApproval(true)"
@close="extensionApprovalReturnShow = false"
/>
</template>
<script setup>
import MainContactInfo from "./templates/MainContactInfo.vue";
import MailTypeForm from "./templates/MailTypeForm.vue";
import DeptSelectForm from "./templates/DeptSelectForm.vue";
import ThreeHandling from "./templates/ThreeHandling.vue";
import MailApprovalDetail from "./templates/MailApprovalDetail.vue";
import Comments from "./templates/Comments.vue";
import CountersignForm from "./templates/CountersignForm.vue";
import Countersign from "./templates/Countersign.vue";
import ExtensionDetail from "./templates/ExtensionDetail.vue";
import CoHandling from "./templates/CoHandling.vue";
import ThreeHandlingDetail from "./templates/ThreeHandlingDetail.vue";
import CompletionDetail from "./templates/CompletionDetail.vue";
import MailReturnDetail from "./MailReturnDetail.vue";
import ApplicationCompleted from "./ApplicationCompleted.vue";
import ReviewComments from "./ReviewComments.vue";
import ConfirmedCompletion from "./ConfirmedCompletion.vue";
import InitiateCountersign from "./InitiateCountersign.vue";
import MailReturn from "./MailReturn.vue";
import ApplyExtension from "./ApplyExtension.vue";
import ExtensionApprovalReturn from "./ExtensionApprovalReturn.vue";
import RemainingTime from "./RemainingTime.vue";
import { getMailFlowDetail, flowNext, extensionApproval } from "@/api/mail";
import { getMailDetail } from "@/api/work";
import { addFav, delFav } from "@/api/work/fav";
import { useDictData } from "@/hooks/useDictOptions";
import feedback from "@/utils/feedback";
import { timeDiffSeconds, formatTimeText, getDictLable } from "@/utils/util";
const { dictData } = useDictData(["mail_level"]);
const loading = ref(true);
const mail = ref({});
const flows = ref([]);
const flowNode = ref({});
const webComponents = ref([]);
const actions = ref([]);
const percentage = ref(100);
const percentageLableHtml = ref("");
const approvals = ref([]);
const mailReturns = ref([]);
const isFav = ref(false);
const steps = ref([
"信件签收",
"联系群众",
"接访群众",
"正在处理",
"办结审批",
"认定办结",
]);
const activeStep = ref(0);
watch(flowNode, (val) => {
if (val.key === "contact_writer") {
activeStep.value = 1;
}
if (val.key === "interview_writer") {
activeStep.value = 2;
}
if (val.key === "verify") {
activeStep.value = 3;
}
if (
val.key === "three_leader_approval" ||
val.key === "second_approval" ||
val.key === "second_deputy_approval" ||
val.key === "second_leader_approval" ||
val.key === "second_reporting" ||
val.key === "first_approval" ||
val.key === "countersign"
) {
activeStep.value = 4;
}
if (val.key === "completion") {
activeStep.value = 5;
}
});
const props = defineProps({
show: {
type: Boolean,
default: false,
},
mailId: {
type: String,
default: "",
},
workId: {
type: Number,
default: 0,
},
disabled: {
type: Boolean,
default: false,
},
workType: {
type: String,
default: "processing",
},
});
const emits = defineEmits(["update:show", "update"]);
const visible = ref(props.show);
watch(visible, (val) => {
emits("update:show", val);
});
watch(
() => props.show,
(val) => {
visible.value = val;
}
);
async function getDetail() {
loading.value = true;
// 信件详情
let data;
if (props.workId) {
data = await getMailDetail({
mailId: props.mailId,
workId: props.workId,
});
} else {
data = await getMailFlowDetail(props.mailId);
}
loading.value = false;
mail.value = data.mail;
flows.value = data.flows;
isFav.value = data.isFav;
flowNode.value = data.flowNode;
approvals.value = data.approvals;
mailReturns.value = data.mailReturns;
if (data.flowNode.webComponents) {
webComponents.value = data.flowNode.webComponents
.split(",")
.map((item) => item.trim());
}
actions.value = data.actions;
nextTick(() => {
flowMaxHeight.value =
leftContainerRef.value.offsetHeight -
(mailInfoRef.value?.offsetHeight || 0) -
flowHeaderRef.value.offsetHeight -
240;
});
}
watch(
() => props.mailId,
(val) => {
getDetail();
requestData.value = {}
}
);
const requestData = ref({});
const messageRef = ref();
const approvedShow = ref(false);
const completionShow = ref(false);
const countersignShow = ref(false);
const returnShow = ref(false);
// 办结按钮
const completionBtnFlag = ref(false);
const applyExtensionShow = ref(false);
const extensionApprovalReturnShow = ref(false);
// 监听信件类目变化
watch(
() => requestData.value.mailFirstCategory,
() => {
if (
requestData.value.mailFirstCategory === "无效类" ||
requestData.value.mailFirstCategory === "终止类" ||
requestData.value.mailFirstCategory === "感谢信类"
) {
completionBtnFlag.value = true;
} else {
completionBtnFlag.value = false;
}
}
);
const mailTypeFormRef = ref();
const deptSelectFormRef = ref();
const threeHandlingRef = ref();
const countersignFormRef = ref();
const coHandlingRef = ref();
async function handleAction(key) {
if (!key) {
return;
}
// 申请办结
if (key === "approved") {
approvedShow.value = true;
return;
}
// 认定办结
if (key === "approvedCompletion") {
completionShow.value = true;
return;
}
// 发起会签
if (key === "initiateCountersign") {
countersignShow.value = true;
return;
}
// 信件退回
if (key === "return") {
returnShow.value = true;
return;
}
// 申请延期
if (key === "applyExtension") {
applyExtensionShow.value = true;
return;
}
if (mailTypeFormRef.value) {
await mailTypeFormRef.value.validate();
}
if (coHandlingRef.value) {
await coHandlingRef.value.validate();
requestData.value.workId = props.workId;
}
if (key !== "returnSubmit") {
if (deptSelectFormRef.value) {
await deptSelectFormRef.value.validate();
}
if (key !== "applyExtensionSubmit" && key !== "verify") {
if (threeHandlingRef.value) {
await threeHandlingRef.value.validate();
}
}
}
if (countersignFormRef.value) {
await countersignFormRef.value.validate();
}
// 申请办结
if (key === "applicationCompleted") {
completeShow.value = true;
return;
}
const requestBody = {
mailId: props.mailId,
nextActionKey: key,
flowKey: flowNode.value.key,
data: requestData.value,
};
loading.value = true;
flowNext(requestBody).then(() => {
// 办结 会签
if (completionBtnFlag.value || props.workType === 'dept_countersign') {
emits("update");
feedback.msgSuccess("操作成功");
visible.value = false;
return
}
if (key === "applyExtensionSubmit") {
emits("update");
getDetail();
messageRef.value.showMessage("已成功申请延期");
return;
}
if (key === "save") {
loading.value = false;
messageRef.value.showMessage("保存成功");
return;
}
// 刷新
emits("update");
if (
key !== "returnSubmit" &&
(flowNode.value.key.indexOf("sign") > -1 ||
flowNode.value.key === "contact_writer" ||
flowNode.value.key === "interview_writer")
) {
getDetail();
if (flowNode.value.key.indexOf("sign") > -1) {
messageRef.value.showMessage("信件签收成功");
} else {
messageRef.value.showMessage("保存成功,进入下一步");
}
} else {
if (key === "countersign") {
getDetail();
}
feedback.msgSuccess("操作成功");
visible.value = false;
}
}).catch(() => {
loading.value = false;
emits("update");
});
}
// 信件收藏
function handleFav() {
if (!isFav.value) {
isFav.value = true;
addFav(props.mailId)
} else {
isFav.value = false;
delFav(props.mailId)
}
}
const completeShow = ref(false);
const flowMaxHeight = ref(200);
const leftContainerRef = ref();
const mailInfoRef = ref();
const flowHeaderRef = ref();
watch(
() => mail.value.flowRemainingTime,
(val) => {
if (val === 0) {
percentage.value = 0;
return;
}
if (val > 0) {
percentage.value = parseInt(
(val / mail.value.flowLimitedTime) * 100
);
}
}
);
const colors = [
{ color: "#F30000", percentage: 30 },
{ color: "#E56D2B", percentage: 60 },
{ color: "#2B45E5", percentage: 100 },
];
function handleExtensionApproval(returnFlag) {
extensionApproval({
mailId: props.mailId,
comment: requestData.value.comment,
returnFlag,
}).then((data) => {
// 刷新
emits("update");
feedback.msgSuccess("操作成功");
visible.value = false;
});
}
function getTotalTime() {
if (!flows.value.length) {
return ''
}
const sum = flows.value.map(item => item.consumingTime).reduce((a, b) => a + b)
return formatTimeText(sum);
}
function handleExtensionApprovalSubmit() {
feedback.confirm('申请延期审批通过').then(() => {
handleExtensionApproval(false)
})
}
function getFlowHandler(item) {
if (item.handlerRoleId === 1 || item.handlerRoleId === 2 || item.handlerRoleId === 3) {
return `${item.handlerDeptName}专班 ${item.handlerName}`
}
return `${item.handlerDeptName} ${item.handlerName}`
}
</script>
<style lang="scss" scoped>
.dialog-header {
--dialog-header-font-color: #acb7ff;
--dialog-header-font-size: 16px;
background-color: var(--primary-color);
color: #fff;
padding: 1em;
font-size: var(--dialog-header-font-size);
.second {
color: var(--dialog-header-font-color);
}
.step-box {
.step {
--setp-background-color: #3a4dc1;
--setp-border-color: #4b60e4;
--setp-font-color: var(--dialog-header-font-color);
--setp-font-size: var(--dialog-header-font-size);
padding-left: 30px;
padding-right: 16px;
&::after {
display: none;
}
&:first-child {
padding-left: 16px;
}
&[active="true"] {
--setp-background-color: #ff4242;
--setp-border-color: #ff7474;
--setp-font-color: #fff;
}
.bloder {
font-size: 24px;
font-weight: 700;
margin-right: 8px;
}
span {
z-index: 2;
}
}
}
.fav-btn {
--el-font-size-base: 18px;
--el-button-bg-color: #283aac;
--el-button-hover-bg-color: #c20921;
--el-button-hover-border-color: #fa8695;
&.active {
--el-button-bg-color: #c20921;
}
&:focus {
background-color: var(--el-button-bg-color);
}
}
.close-btn:hover {
:deep() {
.el-icon {
color: #c20921;
}
}
}
}
.timer {
--large-font-color: var(--primary-color);
--default-font-color: #999;
color: var(--default-font-color);
.error {
--large-font-color: #fff;
--default-font-color: #fff;
background: linear-gradient(180deg, #ff7158 0%, #f40000 100%);
border: 4px solid #ffcdcd;
border-radius: 11px;
box-sizing: border-box;
margin-bottom: 20px;
height: 190px;
width: 100%;
}
}
.flow {
.flow-header {
background: #f5f6ff;
padding: 8px;
font-size: 12px;
}
.flow-container {
background-color: #eff0f5;
padding: 12px 8px;
min-height: 120px;
font-size: 12px;
.second {
color: #999;
}
.primary {
color: var(--primary-color);
font-weight: 500;
}
.flow-time {
display: inline-flex;
padding: 6px;
margin-left: 6px;
background-color: #f5f6ff;
border-left: 2px solid #00d050;
padding-left: 20px;
}
.flow-info {
position: relative;
padding-left: 20px;
&::before {
display: block;
content: "";
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #bfbfbf;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
}
& > div:first-child .flow-info::before {
width: 12px;
height: 12px;
background-color: var(--primary-color);
}
}
}
main {
height: calc(96vh - 180px);
}
.main-container {
padding: 0 20px;
}
footer {
padding: 10px 20px 0;
}
:deep() {
h2 {
font-size: 24px;
font-weight: 500;
color: var(--primary-color);
margin: 12px 0;
}
h3 {
font-size: 16px;
font-weight: 500;
color: var(--primary-color);
}
.content {
font-size: 16px;
padding: 8px;
color: #333;
white-space: pre-wrap;
}
.file-box {
img {
width: 80px;
height: 80px;
}
}
.el-button.is-disabled {
--el-button-disabled-bg-color: #b0b0b0;
--el-button-disabled-border-color: #b0b0b0;
--el-button-disabled-text-color: #fff;
}
}
</style>