25 changed files with 694 additions and 176 deletions
@ -1,13 +1,81 @@
|
||||
<!doctype html> |
||||
<html lang="en"> |
||||
<head> |
||||
|
||||
<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" /> |
||||
<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"></div> |
||||
</head> |
||||
|
||||
<body> |
||||
<div id="app"> |
||||
<span class="loader"></span> |
||||
</div> |
||||
</div> |
||||
<script type="module" src="/src/main.ts"></script> |
||||
</body> |
||||
</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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
Loading…
Reference in new issue