Browse Source

信件主流程

master
wxc 2 years ago
parent
commit
05d092f2b9
  1. 90
      index.html
  2. 2
      package.json
  3. 1
      src/assets/icons/return.svg
  4. 7
      src/components/MailLevel.vue
  5. 69
      src/layout/components/Aside.vue
  6. 2
      src/layout/components/Header.vue
  7. 2
      src/main.ts
  8. 11
      src/style/public.scss
  9. 2
      src/style/theme.scss
  10. 2
      src/utils/request.ts
  11. 2
      src/views/Login.vue
  12. 2
      src/views/home/components/MailTable.vue
  13. 10
      src/views/work/Todo.vue
  14. 230
      src/views/work/components/MailDialog.vue
  15. 95
      src/views/work/components/MailReturen.vue
  16. 51
      src/views/work/components/MailReturnDetail.vue
  17. 40
      src/views/work/components/RemainingTime.vue
  18. 2
      src/views/work/components/ReviewComments.vue
  19. 17
      src/views/work/components/templates/Comments.vue
  20. 85
      src/views/work/components/templates/Countersign.vue
  21. 88
      src/views/work/components/templates/CountersignForm.vue
  22. 36
      src/views/work/components/templates/MailApprovalDetail.vue
  23. 4
      src/views/work/components/templates/MailTypeForm.vue
  24. 9
      src/views/work/components/templates/MainContactInfo.vue
  25. 11
      vite.config.ts

90
index.html

@ -1,13 +1,81 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" /> <head>
<link rel="icon" type="image/svg+xml" href="/favicon.png" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="icon" type="image/svg+xml" href="/favicon.png" />
<title>局长信箱</title> <meta name="viewport"
</head> content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<body> <title>局长信箱</title>
<div id="app"></div> </head>
<script type="module" src="/src/main.ts"></script>
</body> <body>
</html> <div id="app">
<span class="loader"></span>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
<style>
.loader {
border-style: solid;
box-sizing: border-box;
border-width: 40PX 60PX 30PX 60PX;
border-color: #3760C9 #96DDFC #96DDFC #36BBF7;
animation: envFloating 1s ease-in infinite alternate;
position: fixed;
top: 50%;
right: 50%;
transform: translate(-50%, -50%);
}
.loader:after {
content: "";
position: absolute;
right: 62PX;
top: -40PX;
height: 70PX;
width: 50PX;
background-image:
linear-gradient(#162582 45PX, transparent 0),
linear-gradient(#162582 45PX, transparent 0),
linear-gradient(#162582 45PX, transparent 0);
background-repeat: no-repeat;
background-size: 30PX 4PX;
background-position: 0PX 11PX, 8PX 35PX, 0PX 60PX;
animation: envDropping 0.75s linear infinite;
}
@keyframes envFloating {
0% {
transform: translate(-2PX, -5PX)
}
100% {
transform: translate(0, 5PX)
}
}
@keyframes envDropping {
0% {
background-position: 100PX 11PX, 115PX 35PX, 105PX 60PX;
opacity: 1;
}
50% {
background-position: 0PX 11PX, 20PX 35PX, 5PX 60PX;
}
60% {
background-position: -30PX 11PX, 0PX 35PX, -10PX 60PX;
}
75%,
100% {
background-position: -30PX 11PX, -30PX 35PX, -30PX 60PX;
opacity: 0;
}
}
</style>
</html>

2
package.json

@ -22,6 +22,8 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.5.2", "@vitejs/plugin-vue": "^4.5.2",
"amfe-flexible": "^2.2.1",
"postcss-pxtorem": "^6.1.0",
"sass": "^1.69.7", "sass": "^1.69.7",
"unplugin-auto-import": "^0.17.3", "unplugin-auto-import": "^0.17.3",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",

1
src/assets/icons/return.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1708250857142" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11005" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M594.7 400h-150v53h163.8c45.7 0 83.2 37.4 83.2 83.2v27.7c0 45.7-37.4 83.2-83.2 83.2H444.7v53h150c82.5 0 150-67.5 150-150 0-82.6-67.5-150.1-150-150.1zM512 80C273.4 80 80 273.4 80 512s193.4 432 432 432 432-193.4 432-432S750.6 80 512 80z m0 811c-209.3 0-379-169.7-379-379s169.7-379 379-379 379 169.7 379 379-169.7 379-379 379z" p-id="11006"></path><path d="M331.9 425.1l132.3-99.5v199z" p-id="11007"></path></svg>

After

Width:  |  Height:  |  Size: 719 B

7
src/components/MailLevel.vue

@ -38,12 +38,17 @@ watch(() => props.value, (val) => {
content: ''; content: '';
width: 10px; width: 10px;
height: 10px; height: 10px;
background-color: #14C104;
border: 1px solid #6CFF5E;
border-radius: 50%; border-radius: 50%;
background-color: #6CFF5E;
position: absolute; position: absolute;
left: 0; left: 0;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
} }
&[type=danger]::before {
background-color: #FF0000;
border-color: #FFBFBF;
}
} }
</style> </style>

69
src/layout/components/Aside.vue

@ -13,7 +13,7 @@
<span>{{ route.meta.title }}</span> <span>{{ route.meta.title }}</span>
</section> </section>
<el-button link size="small" v-if="route.children?.length"> <el-button link size="small" v-if="route.children?.length">
<template #icon > <template #icon>
<icon name="el-icon-ArrowDownBold" /> <icon name="el-icon-ArrowDownBold" />
</template> </template>
</el-button> </el-button>
@ -27,22 +27,22 @@
" "
:expand="route.meta.isExpand" :expand="route.meta.isExpand"
> >
<div v-for="item in route.children" > <div v-for="item in route.children">
<a <a
:key="item.name" :key="item.name"
v-if="!item.meta.hidden" v-if="!item.meta.hidden"
@click.stop=" @click.stop="
router.push({ router.push({
name: item.name, name: item.name,
}) })
" "
class="flex center v-center" class="flex center v-center"
>
> <span v-if="!item.meta.hidden"
>{{ item.meta.title }}
<span v-if="!item.meta.hidden">{{ item.meta.title }} </span> </span>
</a> </a>
</div> </div>
</div> </div>
</a> </a>
</nav> </nav>
@ -61,11 +61,14 @@ const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const routes = computed(() => userStore.routes); const routes = computed(() => userStore.routes);
const asideCollapse = ref(false); const asideCollapse = ref(false);
onMounted(() => {
document.getElementsByTagName("body")[0]
.style.setProperty("--aside-width", "15.6vw");
})
function handleAsideCollapse() { watch(asideCollapse, (val) => {
if (asideCollapse.value) { if (!val) {
document document
.getElementsByTagName("body")[0] .getElementsByTagName("body")[0]
.style.setProperty("--aside-width", "15.6vw"); .style.setProperty("--aside-width", "15.6vw");
@ -74,25 +77,25 @@ function handleAsideCollapse() {
.getElementsByTagName("body")[0] .getElementsByTagName("body")[0]
.style.setProperty("--aside-width", "7.3vw"); .style.setProperty("--aside-width", "7.3vw");
} }
});
function handleAsideCollapse() {
asideCollapse.value = !asideCollapse.value; asideCollapse.value = !asideCollapse.value;
} }
function handleMenuClick(route) { function handleMenuClick(route) {
console.log(route) console.log(route);
if (route.meta.type === "C") { if (route.meta.type === "C") {
console.log(route.name);
if (route.path === "/datascreen") {
console.log(route.name) const url = router.resolve({ path: "/datascreen/" }).href;
if(route.path==='/datascreen'){ window.open(url, "_blank");
const url = router.resolve({path: '/datascreen/'}).href } else {
window.open(url, '_blank') router.push({
}else{ name: route.name,
router.push({ });
name: route.name, return;
});
return;
} }
} }
if (route.children && route.children.length) { if (route.children && route.children.length) {
route.meta.isExpand = !route.meta.isExpand; route.meta.isExpand = !route.meta.isExpand;

2
src/layout/components/Header.vue

@ -10,7 +10,7 @@
<div class="message"></div> <div class="message"></div>
</div> </div>
<div class="flex v-center gap-16"> <div class="flex v-center gap-16">
<section class="flex gap"> <section class="flex gap" :title="userStore.userInfo.roleName">
<icon name="el-icon-UserFilled" :size="29" color="#586EFF" /> <icon name="el-icon-UserFilled" :size="29" color="#586EFF" />
<span>{{ userStore.userInfo.deptName + ' ' + userStore.userInfo.name }}</span> <span>{{ userStore.userInfo.deptName + ' ' + userStore.userInfo.name }}</span>
</section> </section>

2
src/main.ts

@ -5,6 +5,8 @@ import store from './stores'
import App from './App.vue' import App from './App.vue'
import install from './install' import install from './install'
import 'amfe-flexible'
import './permission' import './permission'
import './style/index.scss' import './style/index.scss'
// 注册图标 // 注册图标

11
src/style/public.scss

@ -1,6 +1,5 @@
body { body {
font-size: 14px;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
color: #333; color: #333;
@ -128,6 +127,9 @@ body {
.ml-16 { .ml-16 {
margin-left: 16px; margin-left: 16px;
} }
.ml-20 {
margin-left: 20px;
}
.mr-4 { .mr-4 {
margin-right: 4px; margin-right: 4px;
@ -140,6 +142,9 @@ body {
.mr-10 { .mr-10 {
margin-right: 10px; margin-right: 10px;
} }
.mr-16 {
margin-right: 16px;
}
.mr-18 { .mr-18 {
margin-right: 18px; margin-right: 18px;
} }
@ -186,6 +191,9 @@ body {
.mb-16 { .mb-16 {
margin-bottom: 16px; margin-bottom: 16px;
} }
.mb-18 {
margin-bottom: 18px;
}
.mb-20 { .mb-20 {
margin-bottom: 20px; margin-bottom: 20px;
@ -242,7 +250,6 @@ body {
.col { .col {
display: flex; display: flex;
width: 280px;
&.short { &.short {
width: 140px; width: 140px;
} }

2
src/style/theme.scss

@ -8,7 +8,7 @@
'base': #064D00, 'base': #064D00,
), ),
'danger': ( 'danger': (
'base': #C40606, 'base': #F60000,
), ),
) )
); );

2
src/utils/request.ts

@ -82,7 +82,7 @@ function ajax(url: string, options: Options) {
if (res.code === 401) { if (res.code === 401) {
message = "未授权登陆" message = "未授权登陆"
} }
// feedback.msgError(message || '未知错误') feedback.msgError(message || '未知错误')
reject(res) reject(res)
} }

2
src/views/Login.vue

@ -175,7 +175,7 @@ function lockLogin() {
box-sizing: border-box; box-sizing: border-box;
h1 { h1 {
color: var(--primary-color); color: var(--primary-color);
font-size: 32p; font-size: 32px;
margin-top: 0; margin-top: 0;
margin-bottom: 32px; margin-bottom: 32px;
} }

2
src/views/home/components/MailTable.vue

@ -1,5 +1,5 @@
<template> <template>
<el-tabs v-model="activeName" @tab-click="handleClick"> <el-tabs v-model="activeName">
<el-tab-pane label="我的待办" name="todo"> <el-tab-pane label="我的待办" name="todo">
<el-table :data="todos" style="width: 100%" stripe> <el-table :data="todos" style="width: 100%" stripe>
<el-table-column <el-table-column

10
src/views/work/Todo.vue

@ -232,7 +232,7 @@
<el-button <el-button
type="primary" type="primary"
link link
@click="handleMail(row.mailId)" @click="handleMail(row)"
>立即处理</el-button >立即处理</el-button
> >
</template> </template>
@ -249,7 +249,7 @@
</div> </div>
<MailDialog v-model:show="showModel" :mail-id="activeMailId" @update="getList" /> <MailDialog v-model:show="showModel" :mail-id="activeMailId" :work-type="activeWorkType" @update="getList" />
<CreateMailSelfDialog v-model="showCreateMailSelf" @close="closeCreateMailSelf" /> <CreateMailSelfDialog v-model="showCreateMailSelf" @close="closeCreateMailSelf" />
</template> </template>
@ -283,10 +283,12 @@ const closeCreateMailSelf = () => {
const todos = ref([]); const todos = ref([]);
const showModel = ref(false); const showModel = ref(false);
const activeMailId = ref(""); const activeMailId = ref("");
const activeWorkType = ref("")
function handleMail(mailId) { function handleMail(row) {
showModel.value = true; showModel.value = true;
activeMailId.value = mailId; activeMailId.value = row.mailId;
activeWorkType.value = row.workType
} }
function getList() { function getList() {

230
src/views/work/components/MailDialog.vue

@ -64,18 +64,23 @@
ref="leftContainerRef" ref="leftContainerRef"
> >
<div class="timer flex center"> <div class="timer flex center">
<el-progress <div
type="dashboard"
:percentage="percentage"
:stroke-width="16"
:width="210"
color="#2B45E5"
class="timer"
v-if="mail.flowRemainingTime > 0" v-if="mail.flowRemainingTime > 0"
><RemainingTime style="height: 210px"
v-model:time="mail.flowRemainingTime" >
/> <el-progress
</el-progress> type="dashboard"
:percentage="percentage"
:stroke-width="16"
:width="240"
:color="colors"
class="timer"
>
<RemainingTime
v-model:time="mail.flowRemainingTime"
/>
</el-progress>
</div>
<div <div
v-else v-else
class="error flex column center v-center" class="error flex column center v-center"
@ -106,12 +111,10 @@
style="padding-bottom: 20px" style="padding-bottom: 20px"
> >
<label>信件等级</label> <label>信件等级</label>
<span>{{ <mail-level
getDictLable( :value="mail.mailLevel"
dictData.mail_level, :list="dictData.mail_level"
mail.mailLevel />
)
}}</span>
</div> </div>
</div> </div>
<div class="flow"> <div class="flow">
@ -170,6 +173,13 @@
</el-col> </el-col>
<el-col :span="19" style="height: 100%"> <el-col :span="19" style="height: 100%">
<el-scrollbar max-height="100%" class="main-container"> <el-scrollbar max-height="100%" class="main-container">
<template v-if="webComponents.indexOf('Comments') > -1">
<Comments :approvals="approvals" />
</template>
<MailReturnDetail
v-if="mailReturns.length"
:mailReturns="mailReturns"
/>
<template <template
v-if="webComponents.indexOf('MainContactInfo') > -1" v-if="webComponents.indexOf('MainContactInfo') > -1"
> >
@ -240,15 +250,31 @@
webComponents.indexOf('MailApprovalDetail') > -1 webComponents.indexOf('MailApprovalDetail') > -1
" "
> >
<MailApprovalDetail :mail="mail" /> <MailApprovalDetail
:mail="mail"
v-model:data="requestData"
/>
</template> </template>
<template v-if="webComponents.indexOf('Comments') > -1">
<Comments :approvals="approvals" /> <template
v-if="
webComponents.indexOf('CountersignForm') > -1 &&
workType === 'dept_countersign'
"
>
<CountersignForm
v-model:data="requestData"
:mail="mail"
ref="countersignFormRef"
/>
</template> </template>
<template <template
v-if="webComponents.indexOf('CountersignForm') > -1" v-if="
mail.countersigns?.length &&
workType !== 'dept_countersign'
"
> >
<CountersignForm /> <Countersign :mail="mail" />
</template> </template>
</el-scrollbar> </el-scrollbar>
</el-col> </el-col>
@ -257,15 +283,29 @@
<footer class="flex between"> <footer class="flex between">
<div></div> <div></div>
<div v-if="!disabled && !completionBtnFlag"> <div v-if="!disabled && !completionBtnFlag">
<el-button <template v-for="action in actions" :key="action.key">
v-for="action in actions" <el-button
:key="action.key" :type="action.btnType"
:type="action.btnType" :plain="action.btnPlain"
:plain="action.btnPlain" size="large"
size="large" @click="handleAction(action.key)"
@click="handleAction(action.key)" v-if="
>{{ action.btnLabel }}</el-button action.key !== 'submitCountersign' ||
> workType === 'dept_countersign'
"
>{{ action.btnLabel }}</el-button
>
<el-button
v-else
size="large"
:disabled="
mail.countersignCompleted < mail.countersignTotal
"
>会签中{{
`(${mail.countersignCompleted} / ${mail.countersignTotal})`
}}</el-button
>
</template>
</div> </div>
<el-button type="primary" size="large" v-if="completionBtnFlag" <el-button type="primary" size="large" v-if="completionBtnFlag"
>认定办结</el-button >认定办结</el-button
@ -299,6 +339,12 @@
:mail="mail" :mail="mail"
@submit="(key) => handleAction(key)" @submit="(key) => handleAction(key)"
/> />
<MailReturen
v-model:show="returnShow"
v-model:data="requestData"
@submit="(key) => handleAction(key)"
/>
</template> </template>
<script setup> <script setup>
import MainContactInfo from "./templates/MainContactInfo.vue"; import MainContactInfo from "./templates/MainContactInfo.vue";
@ -311,11 +357,14 @@ import VerifyForm from "./templates/VerifyForm.vue";
import MailApprovalDetail from "./templates/MailApprovalDetail.vue"; import MailApprovalDetail from "./templates/MailApprovalDetail.vue";
import Comments from "./templates/Comments.vue"; import Comments from "./templates/Comments.vue";
import CountersignForm from "./templates/CountersignForm.vue"; import CountersignForm from "./templates/CountersignForm.vue";
import Countersign from "./templates/Countersign.vue";
import MailReturnDetail from "./MailReturnDetail.vue";
import ApplicationCompleted from "./ApplicationCompleted.vue"; import ApplicationCompleted from "./ApplicationCompleted.vue";
import ReviewComments from "./ReviewComments.vue"; import ReviewComments from "./ReviewComments.vue";
import ConfirmedCompletion from "./ConfirmedCompletion.vue"; import ConfirmedCompletion from "./ConfirmedCompletion.vue";
import InitiateCountersign from "./InitiateCountersign.vue"; import InitiateCountersign from "./InitiateCountersign.vue";
import MailReturen from "./MailReturen.vue";
import RemainingTime from "./RemainingTime.vue"; import RemainingTime from "./RemainingTime.vue";
@ -332,6 +381,7 @@ const deptSelectFormRef = ref();
const ContactWriterFormRef = ref(); const ContactWriterFormRef = ref();
const InterviewWriterFormRef = ref(); const InterviewWriterFormRef = ref();
const verifyFormRef = ref(); const verifyFormRef = ref();
const countersignFormRef = ref();
const loading = ref(true); const loading = ref(true);
const mail = ref({}); const mail = ref({});
@ -342,6 +392,7 @@ const actions = ref([]);
const percentage = ref(100); const percentage = ref(100);
const percentageLableHtml = ref(""); const percentageLableHtml = ref("");
const approvals = ref([]); const approvals = ref([]);
const mailReturns = ref([]);
const isFav = ref(false); const isFav = ref(false);
const steps = ref([ const steps = ref([
@ -389,6 +440,10 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
workType: {
type: String,
default: "processing",
},
}); });
const emits = defineEmits(["update:show", "update"]); const emits = defineEmits(["update:show", "update"]);
@ -415,6 +470,8 @@ function getDetail() {
isFav.value = data.isFav; isFav.value = data.isFav;
flowNode.value = data.flowNode; flowNode.value = data.flowNode;
approvals.value = data.approvals; approvals.value = data.approvals;
mailReturns.value = data.mailReturns;
console.log("mailReturns", mailReturns.value);
if (data.flowNode.webComponents) { if (data.flowNode.webComponents) {
webComponents.value = data.flowNode.webComponents webComponents.value = data.flowNode.webComponents
.split(",") .split(",")
@ -444,8 +501,10 @@ const messageRef = ref();
const approvedShow = ref(false); const approvedShow = ref(false);
const completionShow = ref(false); const completionShow = ref(false);
const countersignShow = ref(false); const countersignShow = ref(false);
const returnShow = ref(false);
// //
const completionBtnFlag = ref(false); const completionBtnFlag = ref(false);
// //
watch( watch(
() => requestData.value.mailFirstCategory, () => requestData.value.mailFirstCategory,
@ -480,20 +539,30 @@ async function handleAction(key) {
countersignShow.value = true; countersignShow.value = true;
return; return;
} }
// 退
if (key === "return") {
returnShow.value = true;
return;
}
if (mailTypeFormRef.value) { if (mailTypeFormRef.value) {
await mailTypeFormRef.value.validate(); await mailTypeFormRef.value.validate();
} }
if (deptSelectFormRef.value) { if (key !== "returnSubmit") {
await deptSelectFormRef.value.validate(); if (deptSelectFormRef.value) {
} await deptSelectFormRef.value.validate();
if (ContactWriterFormRef.value) { }
await ContactWriterFormRef.value.validate(); if (ContactWriterFormRef.value) {
} await ContactWriterFormRef.value.validate();
if (InterviewWriterFormRef.value) { }
await InterviewWriterFormRef.value.validate(); if (InterviewWriterFormRef.value) {
await InterviewWriterFormRef.value.validate();
}
if (verifyFormRef.value) {
await verifyFormRef.value.validate();
}
} }
if (verifyFormRef.value) { if (countersignFormRef.value) {
await verifyFormRef.value.validate(); await countersignFormRef.value.validate();
} }
// //
if (key === "applicationCompleted") { if (key === "applicationCompleted") {
@ -511,9 +580,11 @@ async function handleAction(key) {
// //
emits("update"); emits("update");
if ( if (
flowNode.value.key.indexOf("sign") > -1 || (flowNode.value.key.indexOf("sign") > -1 ||
flowNode.value.key === "contact_writer" || flowNode.value.key === "contact_writer" ||
flowNode.value.key === "interview_writer" flowNode.value.key === "interview_writer" ||
flowNode.value.key === "countersign") &&
key !== "returnSubmit"
) { ) {
getDetail(); getDetail();
if (flowNode.value.key.indexOf("sign") > -1) { if (flowNode.value.key.indexOf("sign") > -1) {
@ -539,6 +610,7 @@ function handleFav() {
isFav.value = false; isFav.value = false;
}); });
} }
watch;
} }
const completeShow = ref(false); const completeShow = ref(false);
@ -547,6 +619,27 @@ const flowMaxHeight = ref(200);
const leftContainerRef = ref(); const leftContainerRef = ref();
const mailInfoRef = ref(); const mailInfoRef = ref();
const flowHeaderRef = ref(); const flowHeaderRef = ref();
watch(
() => mail.value.flowRemainingTime,
(val) => {
if (val === 0) {
mail.value.flowRemainingTimePercentage = 0;
return;
}
if (val > 0) {
mail.value.flowRemainingTimePercentage = parseInt(
val / mail.value.flowLimitedTime
);
}
}
);
const colors = [
{ color: "#F30000", percentage: 30 },
{ color: "#E56D2B", percentage: 60 },
{ color: "#2B45E5", percentage: 100 },
];
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.dialog-header { .dialog-header {
@ -614,24 +707,6 @@ const flowHeaderRef = ref();
height: 190px; height: 190px;
width: 100%; width: 100%;
} }
div {
font-size: 18px;
vertical-align: bottom;
:deep() {
.error {
font-size: 50px;
color: #ff4242;
}
.large {
font-size: 56px;
color: var(--large-font-color);
}
}
}
p {
font-size: 14px;
color: #333;
}
} }
.flow { .flow {
.flow-header { .flow-header {
@ -686,7 +761,7 @@ const flowHeaderRef = ref();
h1 { h1 {
font-size: 24px; font-size: 24px;
font-weight: 500; font-weight: 500;
color: #162582; color: var(--primary-color);
} }
main { main {
@ -698,4 +773,33 @@ main {
footer { footer {
padding: 0 20px; padding: 0 20px;
} }
:deep() {
h2 {
font-size: 24px;
font-weight: 500;
color: var(--primary-color);
}
h3 {
font-size: 16px;
font-weight: 500;
color: var(--primary-color);
}
.content {
font-size: 16px;
padding: 12px;
color: #333;
}
.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> </style>

95
src/views/work/components/MailReturen.vue

@ -0,0 +1,95 @@
<template>
<el-dialog v-model="visible" width="50vw" title="信件退回">
<el-form
label-position="top"
:model="form"
:rules="rules"
ref="formRef"
style="height: 50vh"
>
<el-form-item label="退回原因" prop="reason">
<el-input
v-model="form.reason"
type="textarea"
:rows="3"
placeholder="请输入退回原因"
></el-input>
</el-form-item>
</el-form>
<footer class="flex end">
<el-button type="primary" size="large" @click="submit"
>信件退回</el-button
>
</footer>
</el-dialog>
</template>
<script setup>
const visible = ref(false);
const form = reactive({});
const rules = {
reason: [
{
required: true,
message: "请填写退回原因",
},
],
};
const formRef = ref();
const props = defineProps({
show: {
type: Boolean,
default: false,
},
data: {
type: Object,
default: {},
}
});
const selectLeaderVisible = ref(false)
const leaderType = ref('all')
watch(() => props.flowKey, (val) => {
if (val === 'second_approval' || val === 'second_deputy_approval') {
selectLeaderVisible.value = true
leaderType.value = val === 'second_deputy_approval' ? 'leader' : 'deputy'
}
if (val === 'second_approval') {
leaderType.value = 'deputy'
}
if (val === 'second_deputy_approval') {
leaderType.value = 'leader'
}
})
onMounted(() => {
if (props.data) {
Object.assign(form, props.data);
}
});
const emits = defineEmits(["update:show", "update:data", "submit"]);
watch(visible, (val) => {
emits("update:show", val);
});
watch(
() => props.show,
(val) => {
visible.value = val;
if (val) {
}
}
);
function submit() {
formRef.value.validate((valid) => {
if (valid) {
//
const data = { ...props.data, ...form };
emits("update:data", data);
emits("submit", "returnSubmit");
visible.value = false;
}
});
}
</script>
<style lang="scss" scoped>
</style>

51
src/views/work/components/MailReturnDetail.vue

@ -0,0 +1,51 @@
<template>
<div v-for="item in mailReturns" :key="item.key" class="box">
<div class="flex mb-10">
<div class="danger flex v-center">
<icon name="el-icon-Warning" :size="20" />
<span class="ml-4">信件已退回</span>
</div>
<div class="col">
<label class="ml-20">退回单位</label>
<span>{{ item.handlerDeptName }}</span>
</div>
</div>
<div class="col">
<label class="mt-8">退回理由</label>
<div class="content">{{ item.reason }}</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
mailReturns: {
type: Array,
default: () => [],
},
});
watch(
() => props.mailReturns,
() => {
console.log("watch", props.mailReturns);
}
);
</script>
<style lang="scss" scoped>
.box {
border: 1px solid #ffe3e3;
padding: 20px;
margin-bottom: 20px;
.danger {
color: #ff1414;
font-weight: bold;
}
.content {
background-color: #FEEDED;
width: calc(100% - 100px);
}
}
.col {
width: auto;
}
</style>

40
src/views/work/components/RemainingTime.vue

@ -1,6 +1,6 @@
<template> <template>
<div class="remaining-container text-center" :countdown="countdownFlag"> <div class="remaining-container text-center" :countdown="countdownFlag">
<div class="time-val" > <div class="time-val">
<template v-if="state.day !== 0"> <template v-if="state.day !== 0">
<span class="number">{{ state.day }}</span> <span class="number">{{ state.day }}</span>
<span></span> <span></span>
@ -34,7 +34,7 @@ const emit = defineEmits(["update:time"]);
const countdownFlag = ref(true); const countdownFlag = ref(true);
let timeVal = props.time; let timeVal = props.time;
debugger
const state = reactive({ const state = reactive({
day: 0, day: 0,
hour: 0, hour: 0,
@ -42,22 +42,18 @@ const state = reactive({
second: 0, second: 0,
}); });
let timerFlag = false;
updateState(); updateState();
setRemainingTimeout(); setRemainingInterval();
function updateState() { function updateState() {
if (timeVal === 0) {
state.day = 0;
state.hour = 0;
state.minute = 0;
state.second = 0;
}
if (timeVal > 0) { if (timeVal > 0) {
countdownFlag.value = true; countdownFlag.value = true;
getState(timeVal); getState(timeVal);
} }
// timeVal 0 // timeVal 0
if (timeVal < 0) { if (timeVal <= 0) {
countdownFlag.value = false; countdownFlag.value = false;
getState(-timeVal); getState(-timeVal);
} }
@ -82,24 +78,20 @@ watch(
(newVal) => { (newVal) => {
timeVal = props.time; timeVal = props.time;
updateState(); updateState();
setRemainingInterval();
setRemainingTimeout();
} }
); );
function setRemainingTimeout() {
function setRemainingInterval() {
// //
if (props.time && props.time < 3600 && props.time > -3600) { if (props.time && props.time < 3600 && props.time > -3600 && !timerFlag) {
setTimeout(() => { timerFlag = true;
if (countdownFlag.value) { setInterval(() => {
timeVal--; timeVal--;
} else { emit("update:time", timeVal);
timeVal++; }, 1000);
} }
emit("update:time", timeVal);
}, 1000);
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

2
src/views/work/components/ReviewComments.vue

@ -1,5 +1,5 @@
<template> <template>
<el-dialog v-model="visible" width="50vw" title="提交审批" v-if="visible"> <el-dialog v-model="visible" width="50vw" title="提交审批">
<el-form <el-form
label-position="top" label-position="top"
:model="form" :model="form"

17
src/views/work/components/templates/Comments.vue

@ -9,21 +9,28 @@
:approved="item.approved" :approved="item.approved"
> >
<div class="flex center mb-20 relative comments-header"> <div class="flex center mb-20 relative comments-header">
<icon
name="local-icon-return"
:size="41"
color="#FF0606"
v-if="item.returnFlag"
/>
<icon <icon
name="el-icon-CircleCheck" name="el-icon-CircleCheck"
:size="41" :size="41"
color="var(--primary-color)" color="var(--primary-color)"
v-if="item.approved" v-else-if="item.approved"
/> />
<div class="icon" v-else></div> <div class="icon" v-else></div>
</div> </div>
<h1 class="text-center mb-20"> <h1 class="text-center mb-20">
{{ item.approved ? "审批通过" : "待审批" }} <span v-if="item.returnFlag" style="color: #FF0606">退回整改</span>
<span v-else>{{ item.approved ? "审批通过" : "待审批" }}</span>
</h1> </h1>
<h2 class="text-center mb-20"> <h2 class="text-center mb-20">
{{ item.handlerDeptName }} {{ item.handlerName }} {{ item.handlerDeptName }} {{ item.handlerName }}
</h2> </h2>
<div v-if="item.approved"> <div v-if="item.approved" :danger="item.returnFlag" style="padding: 8px">
<h3>审批意见</h3> <h3>审批意见</h3>
<p>{{ item.comment }}</p> <p>{{ item.comment }}</p>
<h4 class="text-right">{{ item.createTime }}</h4> <h4 class="text-right">{{ item.createTime }}</h4>
@ -83,6 +90,7 @@ header {
color: #666; color: #666;
font-weight: 500; font-weight: 500;
margin-bottom: 8px; margin-bottom: 8px;
margin-top: 0;
} }
h4 { h4 {
font-size: 12px; font-size: 12px;
@ -92,6 +100,9 @@ header {
p { p {
color: #333; color: #333;
} }
div[danger=true] {
background-color: #FEEDED;
}
.icon { .icon {
width: 22px; width: 22px;
height: 22px; height: 22px;

85
src/views/work/components/templates/Countersign.vue

@ -0,0 +1,85 @@
<template>
<h2>部门会签</h2>
<div class="flex mb-18">
<div class="col">
<label>会签发起人</label>
<span>{{
mail.countersignPromoterName +
" " +
mail.countersignPromoterDeptName
}}</span>
</div>
<div class="col" style="width: auto">
<label>会签部门</label>
<span
v-for="item in mail.countersigns"
:key="item.id"
class="mr-16"
>
<span>{{ item.deptName }}</span>
<span>(</span>
<span v-if="item.comments" style="color: #009706">已提交</span>
<span v-else style="color: #999">未提交</span>
<span>)</span>
</span>
</div>
</div>
<div class="mb-18">
<div class="col">
<label>会签的具体要求</label>
<span>{{ mail.countersignRequirement }}</span>
</div>
</div>
<template v-for="item in mail.countersigns" :key="item.id">
<el-divider />
<h3 class="flex between">
<span>
<span style="margin-right: 52px">{{
`${item.deptName} 的会签意见`
}}</span>
<span v-if="!item.comments" style="color: #999">未提交</span>
</span>
<span>{{ item.updateTime }}</span>
</h3>
<div v-if="item.comments">
<div class="content mb-18">{{ item.comments }}</div>
<div class="col">
<label>相关材料</label>
<div class="flex gap file-box" v-if="JSON.parse(item.attachments).length">
<div
v-for="(item, index) in JSON.parse(item.attachments)"
:key="index"
class="item pointer"
>
<template
v-if="item.type && item.type.indexOf('image') > -1"
>
<img
:src="`${VITE_API_URL}/api/file/stream/${item.filepath}`"
/>
</template>
</div>
</div>
<div>
<el-empty :image-size="40" style="--el-empty-padding: 0;" description="无附件" />
</div>
</div>
</div>
</template>
</template>
<script setup>
const props = defineProps({
mail: {
type: Object,
default: {},
},
});
</script>
<style lang="scss" scoped>
.col {
label {
width: 144px;
text-align: right;
}
}
</style>

88
src/views/work/components/templates/CountersignForm.vue

@ -1,9 +1,95 @@
<template> <template>
<h2>部门会签</h2> <h2>部门会签</h2>
<div class="flex mb-18">
<div class="col">
<label>会签发起人</label>
<span>{{ mail.countersignPromoterName + ' ' + mail.countersignPromoterDeptName }}</span>
</div>
<div class="col" style="width: auto">
<label>会签部门</label>
<span v-for="item in mail.countersigns" :key="item.id" class="mr-16">
<span>{{ item.deptName }}</span>
<span>(</span>
<span v-if="item.comments" style="color: #009706">已提交</span>
<span v-else style="color: #999">未提交</span>
<span>)</span>
</span>
</div>
</div>
<div class="mb-18">
<div class="col">
<label>会签的具体要求</label>
<span>{{ mail.countersignRequirement }}</span>
</div>
</div>
<el-form :label-width="144" ref="formRef" :model="form" :rules="rules">
<el-form-item label="会签意见" prop="comments">
<el-input type="textarea" v-model="form.comments" :autosize="{ minRows: 4 }" placeholder="请输入本部门对信件内容提出的专业意见。" />
</el-form-item>
<div class="flex">
<div style="width: 50%">
<el-form-item label="上传材料">
<Upload v-model="form.attachments" />
</el-form-item>
</div>
<div style="width: 50%">
<div class="col" style="width: 100%">
<label style="color: #FF0E00">材料说明</label>
<div>请上传专业意见相关材料</div>
</div>
</div>
</div>
</el-form>
</template> </template>
<script setup> <script setup>
const form = reactive({})
const formRef = ref()
const rules = {
comments: [
{
required: true,
message: "请填写会签意见"
}
]
};
const props = defineProps({
data: {
type: Object,
default: {},
},
mail: {
type: Object,
default: {},
}
});
const emits = defineEmits(["update:data"]);
function validate() {
return new Promise((resolve, reject) => {
formRef.value.validate((valid) => {
if (valid) {
const data = { ...props.data, ...form };
emits("update:data", data);
resolve(true);
} else {
reject();
}
});
});
}
defineExpose({
validate,
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.col {
label {
width: 144px;
text-align: right;
}
}
</style> </style>

36
src/views/work/components/templates/MailApprovalDetail.vue

@ -27,11 +27,11 @@
> >
<div <div
class="flex" class="flex"
v-for="item in mail.verifyReportedPolices" v-for="(item, index) in mail.verifyReportedPolices"
:key="item.empNo" :key="item.empNo"
> >
<div class="col"> <div class="col">
<label>被举报人1</label> <label>被举报人{{ index + 1 }}</label>
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
</div> </div>
<div class="col"> <div class="col">
@ -52,7 +52,7 @@
<div class="content">{{ mail.content }}</div> <div class="content">{{ mail.content }}</div>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="领导接访情况" name="4"> <el-collapse-item title="领导接访情况" name="4">
<div class="flex mb-10"> <div class="flex mb-18">
<div class="col"> <div class="col">
<label>主单位签收时长</label> <label>主单位签收时长</label>
<span>{{}}</span> <span>{{}}</span>
@ -62,7 +62,7 @@
<span>{{}}</span> <span>{{}}</span>
</div> </div>
</div> </div>
<div class="flex mb-10"> <div class="flex mb-18">
<div class="col"> <div class="col">
<label>接访形式</label> <label>接访形式</label>
<span>{{ <span>{{
@ -89,12 +89,10 @@
</div> </div>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="核查办理情况" name="5"> <el-collapse-item title="核查办理情况" name="5">
<div class="flex mb-10"> <div class="flex mb-18">
<div class="col"> <div class="col">
<label>是否属实</label> <label>是否属实</label>
<span>{{ <span>{{ mail.verifyIsTrue }}</span>
getDictLable(dictData.verifyIsTrue, mail.interviewType)
}}</span>
</div> </div>
<div class="col"> <div class="col">
<label>是否需要问责</label> <label>是否需要问责</label>
@ -103,7 +101,7 @@
}}</span> }}</span>
</div> </div>
</div> </div>
<div class="flex mb-10"> <div class="flex mb-18">
<div class="col" style="width: 100%"> <div class="col" style="width: 100%">
<label>查证属实问题</label> <label>查证属实问题</label>
<span <span
@ -113,7 +111,7 @@
> >
</div> </div>
</div> </div>
<div class="flex mb-10"> <div class="flex mb-18">
<div class="col"> <div class="col">
<label>核办结果</label> <label>核办结果</label>
<span>{{ mail.verifyFeedback }}</span> <span>{{ mail.verifyFeedback }}</span>
@ -127,13 +125,13 @@
<span <span
v-for="(item, index) in mail.verifyPunish" v-for="(item, index) in mail.verifyPunish"
:key="index" :key="index"
>{{ getDictLable(dictData.verify_punish, item) }}</span >{{ item }}</span
> >
</div> </div>
</div> </div>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="结果反馈情况" name="7"> <el-collapse-item title="结果反馈情况" name="7">
<div class="flex mb-10"> <div class="flex mb-18">
<div class="col"> <div class="col">
<label>群众反应事项解决情况</label> <label>群众反应事项解决情况</label>
<span>{{ mail.verifyIsResolved ? "已解决" : "" }}</span> <span>{{ mail.verifyIsResolved ? "已解决" : "" }}</span>
@ -202,20 +200,12 @@ const activeNames = ref(["1", "2", "3", "4", "5", "6", "7", "8"]);
--el-collapse-header-font-size: 16px; --el-collapse-header-font-size: 16px;
} }
.col { .col {
width: 25%;
label { label {
width: 144px; width: 144px;
text-align: right; text-align: right;
} }
} }
.content {
font-size: 16px;
padding: 12px;
color: #333;
}
.file-box {
img {
width: 80px;
height: 80px;
}
}
</style> </style>

4
src/views/work/components/templates/MailTypeForm.vue

@ -4,7 +4,7 @@
<el-form :label-width="150" :model="form" :rules="rules" ref="formRef"> <el-form :label-width="150" :model="form" :rules="rules" ref="formRef">
<div class="flex gap-20"> <div class="flex gap-20">
<div style="width: 50%"> <div style="width: 50%">
<el-form-item label="信件分类" prop="mailCategoryId"> <el-form-item label="信件分类" prop="mailCategory">
<mail-category-select <mail-category-select
v-model="form.mailCategoryName" v-model="form.mailCategoryName"
@change="handleCategoryChange" @change="handleCategoryChange"
@ -12,7 +12,7 @@
</el-form-item> </el-form-item>
</div> </div>
<div style="width: 50%"> <div style="width: 50%">
<el-form-item label="信件等级" prop="mailLevel"> <el-form-item label="信件等级" prop="mailLevel" v-if="form.mailCategory !== '感谢信'">
<el-select <el-select
v-model="form.mailLevel" v-model="form.mailLevel"
placeholder="" placeholder=""

9
src/views/work/components/templates/MainContactInfo.vue

@ -7,7 +7,7 @@
</div> </div>
<div class="col"> <div class="col">
<label>信件来源</label> <label>信件来源</label>
<span>{{ mail.source }}</span> <span>{{ getDictLable(dictData.mail_source, mail.source) }}</span>
</div> </div>
</div> </div>
<h2>来信人信息</h2> <h2>来信人信息</h2>
@ -46,11 +46,14 @@
<div class="img-box" v-for="(item, index) in mail.attachments" :key="index" :style="{ backgroundImage: `url(${VITE_API_URL}/api/file/stream/${item.filepath})`}"></div> <div class="img-box" v-for="(item, index) in mail.attachments" :key="index" :style="{ backgroundImage: `url(${VITE_API_URL}/api/file/stream/${item.filepath})`}"></div>
</div> </div>
<div> <div>
<el-empty :image-size="40" style="--el-empty-padding: 0;" v-if="mail.attachments.length === 0" /> <el-empty :image-size="40" style="--el-empty-padding: 0;" v-if="mail.attachments.length === 0" />
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { useDictData } from "@/hooks/useDictOptions";
import { getDictLable } from "@/utils/util";
const { dictData } = useDictData(["mail_source"]);
const { const {
VITE_API_URL VITE_API_URL
} = process.env } = process.env
@ -61,6 +64,8 @@ defineProps({
default: () => ({}) default: () => ({})
} }
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
h2 { h2 {

11
vite.config.ts

@ -8,6 +8,7 @@ import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import postCssPxToRem from 'postcss-pxtorem'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default ({ mode }) => defineConfig({ export default ({ mode }) => defineConfig({
@ -43,7 +44,7 @@ export default ({ mode }) => defineConfig({
// 配置路劲在你的src里的svg存放文件 // 配置路劲在你的src里的svg存放文件
iconDirs: [fileURLToPath(new URL('./src/assets/icons', import.meta.url))], iconDirs: [fileURLToPath(new URL('./src/assets/icons', import.meta.url))],
symbolId: 'local-icon-[name]' symbolId: 'local-icon-[name]'
}), })
], ],
resolve: { resolve: {
// https://cn.vitejs.dev/config/#resolve-alias // https://cn.vitejs.dev/config/#resolve-alias
@ -61,6 +62,14 @@ export default ({ mode }) => defineConfig({
additionalData: `@use "src/style/theme.scss" as *;` additionalData: `@use "src/style/theme.scss" as *;`
}, },
}, },
// postcss: {
// plugins: [
// postCssPxToRem({
// rootValue: 192,
// propList: ['*'],
// })
// ]
// }
} }
}) })

Loading…
Cancel
Save