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>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>局长信箱</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>局长信箱</title>
</head>
<body>
<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": {
"@vitejs/plugin-vue": "^4.5.2",
"amfe-flexible": "^2.2.1",
"postcss-pxtorem": "^6.1.0",
"sass": "^1.69.7",
"unplugin-auto-import": "^0.17.3",
"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: '';
width: 10px;
height: 10px;
background-color: #14C104;
border: 1px solid #6CFF5E;
border-radius: 50%;
background-color: #6CFF5E;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
&[type=danger]::before {
background-color: #FF0000;
border-color: #FFBFBF;
}
}
</style>

69
src/layout/components/Aside.vue

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

2
src/layout/components/Header.vue

@ -10,7 +10,7 @@
<div class="message"></div>
</div>
<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" />
<span>{{ userStore.userInfo.deptName + ' ' + userStore.userInfo.name }}</span>
</section>

2
src/main.ts

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

11
src/style/public.scss

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

2
src/style/theme.scss

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

2
src/utils/request.ts

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

2
src/views/Login.vue

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

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

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

10
src/views/work/Todo.vue

@ -232,7 +232,7 @@
<el-button
type="primary"
link
@click="handleMail(row.mailId)"
@click="handleMail(row)"
>立即处理</el-button
>
</template>
@ -249,7 +249,7 @@
</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" />
</template>
@ -283,10 +283,12 @@ const closeCreateMailSelf = () => {
const todos = ref([]);
const showModel = ref(false);
const activeMailId = ref("");
const activeWorkType = ref("")
function handleMail(mailId) {
function handleMail(row) {
showModel.value = true;
activeMailId.value = mailId;
activeMailId.value = row.mailId;
activeWorkType.value = row.workType
}
function getList() {

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

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

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

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

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

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

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

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

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

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

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

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

@ -7,7 +7,7 @@
</div>
<div class="col">
<label>信件来源</label>
<span>{{ mail.source }}</span>
<span>{{ getDictLable(dictData.mail_source, mail.source) }}</span>
</div>
</div>
<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>
<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>
</template>
<script setup>
import { useDictData } from "@/hooks/useDictOptions";
import { getDictLable } from "@/utils/util";
const { dictData } = useDictData(["mail_source"]);
const {
VITE_API_URL
} = process.env
@ -61,6 +64,8 @@ defineProps({
default: () => ({})
}
})
</script>
<style lang="scss" scoped>
h2 {

11
vite.config.ts

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

Loading…
Cancel
Save