Files
xgy-oa/klp-ui/src/views/wms/seal/seal.vue
Joshi 1207072092 feat(wms): 新增用印审批功能
- 在审批API中添加按业务ID查询审批信息的方法
- 配置用印详情页面路由,支持通过业务ID查看用印详情
- 修改待办列表,为用印类型申请隐藏同意驳回按钮
- 在待办列表数据中添加业务ID字段,完善申请类型映射
- 更新审批服务接口和实现类,添加queryByBizId方法
- 重构用印详情页面,集成审批信息加载和权限校验逻辑
- 更新领域模型中的申请类型枚举,添加用印类型支持
- 完善审批任务服务,支持用印申请详情查询和申请人姓名显示
2026-03-19 15:30:28 +08:00

345 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="request-page">
<el-row :gutter="20">
<el-col :span="8">
<el-card class="form-card" shadow="never">
<div slot="header" class="card-header">
<span>用印申请</span>
</div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" size="small" class="metal-form">
<div class="form-summary">
<div class="summary-left">
<div class="summary-title">发起用印</div>
<div class="summary-sub">请完善信息后提交系统会推送部门负责人审批</div>
</div>
<div class="summary-right">
<div class="summary-item">
<div class="k">申请人</div>
<div class="v">{{ currentApplicantText }}</div>
</div>
<div class="summary-item">
<div class="k">用印类型</div>
<div class="v">{{ form.sealType || '-' }}</div>
</div>
</div>
</div>
<el-form-item label="审批部门" prop="deptId">
<el-select v-model="form.deptId" placeholder="请选择审批部门" filterable @change="onDeptChange" style="width: 100%">
<el-option
v-for="item in deptOptions"
:key="item.deptId"
:label="item.deptName + (item.leaderNickName ? '(负责人:' + item.leaderNickName + '' : '(无负责人)')"
:value="item.deptId"
/>
</el-select>
<div class="hint-text">系统将根据部门负责人自动创建审批任务</div>
</el-form-item>
<el-form-item label="用印类型" prop="sealType">
<el-select
v-model="form.sealType"
filterable
allow-create
default-first-option
clearable
placeholder="选择或输入用印类型"
style="width: 100%"
>
<el-option
v-for="dictItem in (dict.type.hrm_stamp_image || [])"
:key="dictItem.value"
:label="dictItem.label"
:value="dictItem.label"
/>
</el-select>
<div class="hint-text">优先从字典选择若字典未配置可直接输入</div>
</el-form-item>
<div class="block-title">用途与材料</div>
<el-form-item label="用途说明" prop="purpose">
<el-input v-model="form.purpose" type="textarea" :rows="4" placeholder="请说明用印用途、对象、份数等" show-word-limit maxlength="200" />
</el-form-item>
<el-form-item label="待盖章PDF" prop="applyFileIds">
<file-upload
v-model="form.applyFileIds"
:limit="1"
:file-size="50"
:file-type="['pdf']"
/>
<div class="hint-text">仅支持 PDF单个文件不超过 50MB</div>
</el-form-item>
<el-form-item label="盖章页码" prop="pageNo" v-if="form.applyFileIds">
<el-input-number
v-model="form.pageNo"
:min="1"
:max="999"
controls-position="right"
placeholder="请输入需要盖章的页码从第1页开始"
style="width: 100%"
/>
<div class="hint-text">请输入需要盖章的页码从第1页开始计数</div>
</el-form-item>
<el-form-item label="需要回执" prop="receiptRequired">
<el-switch v-model="form.receiptRequired" :active-value="1" :inactive-value="0" />
<div class="hint-text">开启后盖章完成通常需要回传回执文件</div>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="可选:补充说明" show-word-limit maxlength="200" />
</el-form-item>
<div class="form-actions">
<el-button @click="$router.back()">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submit">提交申请</el-button>
</div>
</el-form>
</el-card>
</el-col>
<el-col :span="16">
<el-card class="list-card" shadow="never">
<div slot="header" class="card-header">
<span>我的用印申请</span>
<div class="actions">
<el-button size="mini" icon="el-icon-refresh" @click="getList">刷新</el-button>
</div>
</div>
<el-table v-loading="listLoading" :data="sealList" border>
<el-table-column label="状态" align="center" width="100">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="用印类型" align="center" prop="sealType" />
<el-table-column label="用途说明" align="center" prop="purpose" show-overflow-tooltip />
<el-table-column label="申请时间" align="center" prop="createTime" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="goDetail(scope.row)">查看</el-button>
<el-button v-if="canPreviewReceipt(scope.row)" size="mini" type="text" @click="previewReceipt(scope.row)">预览</el-button>
<el-button v-if="canPreviewReceipt(scope.row)" size="mini" type="text" @click="downloadReceipt(scope.row)">下载</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { addSealReq, listSealReq } from '@/api/wms/seal'
import { listDept } from '@/api/wms/dept'
import FileUpload from '@/components/FileUpload'
export default {
name: 'WmsSealRequest',
dicts: ['hrm_stamp_image'],
components: { FileUpload },
data() {
return {
submitting: false,
listLoading: false,
deptOptions: [],
sealList: [],
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
createBy: this.$store.getters.name
},
form: {
empId: this.$store.getters.id,
deptId: '',
sealType: '',
purpose: '',
applyFileIds: '',
pageNo: 1,
receiptRequired: 0,
remark: ''
},
rules: {
deptId: [{ required: true, message: '请选择审批部门', trigger: 'change' }],
sealType: [{ required: true, message: '请选择/输入用印类型', trigger: 'change' }],
purpose: [{ required: true, message: '请填写用途说明', trigger: 'blur' }],
applyFileIds: [{ required: true, message: '请上传 PDF 附件', trigger: 'change' }],
pageNo: [{ required: true, message: '请输入盖章页码', trigger: 'blur' }],
receiptRequired: [{ required: true, message: '请选择是否需要回执', trigger: 'change' }]
}
}
},
computed: {
currentApplicantText() {
const user = this.$store?.state?.user || {}
return user.nickName || user.userName || '当前用户'
}
},
created() {
this.loadDeptList()
this.getList()
},
methods: {
async loadDeptList() {
try {
const res = await listDept()
this.deptOptions = res.data || []
} catch (e) {
this.deptOptions = []
}
},
onDeptChange() {},
statusText(status) {
const map = { running: '审批中', draft: '草稿', approved: '已通过', rejected: '已驳回', canceled: '已撤销', pending: '待审批' }
return map[status] || status || '-'
},
statusType(status) {
const map = { running: 'warning', draft: 'info', approved: 'success', rejected: 'danger', canceled: 'info', pending: 'warning' }
return map[status] || 'info'
},
getList() {
this.listLoading = true
listSealReq(this.queryParams)
.then(res => {
this.sealList = res.rows || []
this.total = res.total || 0
})
.finally(() => {
this.listLoading = false
})
},
goDetail(row) {
// if (row.applyType === 'seal') {
this.$router.push({
path: `/wms/seal/sealDetail/${row.bizId}`
})
return
// }
},
canPreviewReceipt(row) {
return row.status === 'approved' && row.receiptFileIds
},
previewReceipt(row) {
if (row.receiptFileIds) {
window.open(row.receiptFileIds, '_blank')
}
},
downloadReceipt(row) {
if (row.receiptFileIds) {
window.open(row.receiptFileIds, '_blank')
}
},
async submit() {
try {
await this.$refs.formRef.validate()
this.submitting = true
let remark = this.form.remark || ''
if (this.form.pageNo) {
remark = remark ? `${remark}\n[盖章页码:第${this.form.pageNo}页]` : `[盖章页码:第${this.form.pageNo}页]`
}
const payload = {
empId: this.form.empId,
deptId: this.form.deptId,
sealType: this.form.sealType,
purpose: this.form.purpose,
applyFileIds: this.form.applyFileIds,
receiptRequired: this.form.receiptRequired,
remark,
status: 'pending'
}
await addSealReq(payload)
this.$message.success('提交成功')
this.getList()
} finally {
this.submitting = false
}
}
}
}
</script>
<style lang="scss" scoped>
.request-page {
padding: 16px 20px 32px;
background: transparent;
}
.form-card {
max-width: 980px;
margin: 0 auto;
border: 1px solid #eef0f4;
border-radius: 10px;
background: #ffffff;
box-shadow: none;
}
.list-card {
border: 1px solid #eef0f4;
border-radius: 10px;
box-shadow: none;
background: #ffffff;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #2b2f36;
}
.actions {
display: flex;
gap: 8px;
}
.metal-form { padding-right: 8px; }
.block-title {
margin: 18px 0 10px;
padding-left: 10px;
font-weight: 600;
color: #2f3440;
border-left: 2px solid #cbd1db;
}
.hint-text {
margin-top: 6px;
font-size: 12px;
color: #8a8f99;
line-height: 1.4;
}
.form-summary {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
padding: 12px 12px;
margin-bottom: 12px;
border: 1px solid #eef0f4;
border-radius: 10px;
background: transparent;
}
.summary-title { font-size: 15px; font-weight: 700; color: #2b2f36; }
.summary-sub { margin-top: 4px; font-size: 12px; color: #8a8f99; }
.summary-right { display: flex; gap: 16px; }
.summary-item .k { font-size: 12px; color: #8a8f99; }
.summary-item .v { margin-top: 2px; font-weight: 600; color: #2b2f36; }
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
@media (max-width: 1200px) {
.summary-right { display: none; }
}
</style>