Merge branch 'developer' of ssh://code.cyweb.top:30033/scj/zhxy/v3/cloud-ui into developer
This commit is contained in:
@@ -1,28 +1,48 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="dataForm.id ? '编辑' : '新增'"
|
||||
v-model="visible"
|
||||
<el-dialog
|
||||
:title="dataForm.id ? '编辑' : '新增'"
|
||||
v-model="visible"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-form-item label="代理名称" prop="agentName">
|
||||
<el-input
|
||||
v-model="dataForm.agentName"
|
||||
placeholder="请输入代理名称"
|
||||
<el-input
|
||||
v-model="dataForm.agentName"
|
||||
placeholder="请输入代理名称"
|
||||
clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人" prop="contactPerson">
|
||||
<el-input
|
||||
v-model="dataForm.contactPerson"
|
||||
placeholder="请输入联系人"
|
||||
clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input
|
||||
v-model="dataForm.contactPhone"
|
||||
placeholder="请输入联系电话"
|
||||
clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="登录用户名" prop="username">
|
||||
<el-input
|
||||
v-model="dataForm.username"
|
||||
placeholder="请输入登录用户名(不填则自动生成)"
|
||||
clearable
|
||||
:disabled="!!dataForm.id" />
|
||||
<div class="form-tip" v-if="!dataForm.id">新增时自动创建系统用户,默认密码:Aa123456,角色:招标代理</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="dataForm.remark"
|
||||
type="textarea"
|
||||
<el-input
|
||||
v-model="dataForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
placeholder="请输入备注"
|
||||
clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -48,6 +68,9 @@ const formRef = ref();
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
agentName: '',
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
username: '',
|
||||
remark: '',
|
||||
});
|
||||
const visible = ref(false);
|
||||
@@ -64,6 +87,9 @@ const openDialog = async (type: string, rowData?: any) => {
|
||||
visible.value = true;
|
||||
dataForm.id = '';
|
||||
dataForm.agentName = '';
|
||||
dataForm.contactPerson = '';
|
||||
dataForm.contactPhone = '';
|
||||
dataForm.username = '';
|
||||
dataForm.remark = '';
|
||||
|
||||
nextTick(() => {
|
||||
@@ -76,6 +102,9 @@ const openDialog = async (type: string, rowData?: any) => {
|
||||
Object.assign(dataForm, {
|
||||
id: res.data.id || '',
|
||||
agentName: res.data.agentName || '',
|
||||
contactPerson: res.data.contactPerson || '',
|
||||
contactPhone: res.data.contactPhone || '',
|
||||
username: res.data.username || '',
|
||||
remark: res.data.remark || '',
|
||||
});
|
||||
}
|
||||
@@ -124,5 +153,10 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -75,7 +75,25 @@
|
||||
<span style="margin-left: 4px">代理名称</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="300" show-overflow-tooltip>
|
||||
<el-table-column prop="contactPerson" label="联系人" width="120" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><User /></el-icon>
|
||||
<span style="margin-left: 4px">联系人</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="140" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><Phone /></el-icon>
|
||||
<span style="margin-left: 4px">联系电话</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="登录用户名" width="140" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
<span style="margin-left: 4px">登录用户名</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><EditPen /></el-icon>
|
||||
<span style="margin-left: 4px">备注</span>
|
||||
@@ -206,7 +224,7 @@ import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { getPage, delObj, getAgentSummary } from "/@/api/finance/purchaseagent";
|
||||
import { deptTree } from '/@/api/admin/dept';
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { List, Document, EditPen, Clock, Search } from '@element-plus/icons-vue'
|
||||
import { List, Document, EditPen, Clock, Search, User, Phone, UserFilled } from '@element-plus/icons-vue'
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px" >
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收方式" prop="acceptType">
|
||||
<el-radio-group v-model="form.acceptType" :disabled="readonly">
|
||||
<el-radio label="1" :disabled="!canFill">填写履约验收评价表</el-radio>
|
||||
<el-radio label="2">上传履约验收评价表</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="!canFill" class="el-form-item__tip">金额≥30万,仅支持上传模版</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收日期" prop="acceptDate">
|
||||
<el-date-picker
|
||||
@@ -24,140 +15,14 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 填报方式:验收内容表格 -->
|
||||
<template v-if="form.acceptType === '1' && canFill">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收内容" prop="acceptContents">
|
||||
<el-table :data="form.acceptContents" border size="small" class="accept-content-table">
|
||||
<el-table-column prop="itemName" label="验收项" >
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<span>{{row.itemName}}</span>
|
||||
<el-input
|
||||
v-if="row.type === 'input'"
|
||||
v-model="row.remark"
|
||||
placeholder="请输入"
|
||||
size="small"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isQualified" label="合格/不合格" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-radio-group v-model="row.isQualified" size="small" :disabled="readonly">
|
||||
<el-radio label="1">合格</el-radio>
|
||||
<el-radio label="0">不合格</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<!-- 上传方式:先模版下载,再上传 -->
|
||||
<template v-if="form.acceptType === '2'">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="模版下载">
|
||||
<el-link type="primary" :href="lyysTemplateUrl" :download="lyysTemplateDownloadName" target="_blank">
|
||||
<el-icon><Download /></el-icon>
|
||||
{{ lyysTemplateLabel }}
|
||||
</el-link>
|
||||
<!-- <div class="el-form-item__tip">下载后填写并上传;模板文件:templates/lyys-template-{{ projectTypeKey }}.docx</div>-->
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="履约验收模版" prop="templateFileIds">
|
||||
<UploadFile
|
||||
v-model="templateFileIdsStr"
|
||||
:limit="1"
|
||||
:data="{ purchaseId: purchaseId || '', fileType: '110' }"
|
||||
upload-file-url="/purchase/purchasingfiles/upload"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<!-- 上传履约验收模版 -->
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收地点" prop="acceptAddress">
|
||||
<el-input v-model="form.acceptAddress" placeholder="请输入验收地点" :disabled="readonly" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="存在问题及改进意见" prop="question">
|
||||
<el-input
|
||||
v-model="form.question"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入存在问题及改进意见"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 验收小组 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="验收小组" prop="acceptTeam">
|
||||
<div class="team-list">
|
||||
<div v-for="(m, idx) in form.acceptTeam" :key="idx" class="team-row">
|
||||
<el-select
|
||||
v-model="m.roleType"
|
||||
placeholder="身份"
|
||||
size="small"
|
||||
style="width: 130px; margin-right: 8px"
|
||||
:disabled="readonly"
|
||||
@change="(val) => onRoleChange(idx, val as string)"
|
||||
>
|
||||
<el-option label="组长(校内)" value="LEADER_IN" />
|
||||
<el-option label="组长(校外)" value="LEADER_OUT" />
|
||||
<el-option label="组员(校内)" value="MEMBER_IN" />
|
||||
<el-option label="组员(校外)" value="MEMBER_OUT" />
|
||||
</el-select>
|
||||
<template v-if="m.roleType === 'LEADER_IN' || m.roleType === 'MEMBER_IN'">
|
||||
<org-selector
|
||||
v-model:orgList="m.userList"
|
||||
type="user"
|
||||
:multiple="false"
|
||||
@update:orgList="(list: any[]) => onTeamUserChange(idx, list)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input v-model="m.name" placeholder="姓名" size="small" style="width:120px" :disabled="readonly" />
|
||||
<el-input v-model="m.deptName" placeholder="单位/部门" size="small" style="width:160px" :disabled="readonly" />
|
||||
</template>
|
||||
<el-button v-if="!readonly && form.acceptTeam.length > 3" type="danger" link size="small" @click="removeTeam(idx)">删除</el-button>
|
||||
</div>
|
||||
<el-button v-if="!readonly" type="primary" link size="small" @click="addTeam">+ 增加成员</el-button>
|
||||
</div>
|
||||
<div class="el-form-item__tip">
|
||||
至少3人,且为单数
|
||||
<template v-if="(previousBatchesTeams || []).length > 0">
|
||||
;
|
||||
<span class="copy-from-inline">
|
||||
从往期带入
|
||||
<el-select
|
||||
v-model="copyFromBatch"
|
||||
placeholder="同第N期"
|
||||
clearable
|
||||
size="small"
|
||||
style="width: 100px"
|
||||
@change="onCopyFromBatch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in (previousBatchesTeams || [])"
|
||||
:key="item.batch"
|
||||
:label="`同第${item.batch}期`"
|
||||
:value="item.batch"
|
||||
/>
|
||||
</el-select>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<el-form-item label="履约验收文件" prop="templateFileIds">
|
||||
<upload-file v-model="templateFiles" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ purchaseId: purchaseId || '', fileType: '110' }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="readonly" />
|
||||
<el-link v-if="!readonly" type="primary" :href="lyysTemplateUrl" :download="lyysTemplateDownloadName" target="_blank" style="margin-top: 8px; display: inline-flex; align-items: center;">
|
||||
<el-icon><Download /></el-icon>
|
||||
<span style="margin-left: 4px;">下载{{ lyysTemplateLabel }}</span>
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
@@ -171,37 +36,32 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Download } from '@element-plus/icons-vue'
|
||||
import UploadFile from "/@/components/Upload/index.vue";
|
||||
|
||||
/** 项目类型 A:货物 B:工程 C:服务,与 public/templates/lyys-template-A|B|C.docx 对应 */
|
||||
/** 项目类型 A:货物 B:工程 C:服务 */
|
||||
const LYYS_TEMPLATE_MAP: Record<string, { label: string }> = {
|
||||
A: { label: '履约验收表模板(货物)' },
|
||||
B: { label: '履约验收表模板(工程)' },
|
||||
C: { label: '履约验收表模板(服务)' },
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
canFill: boolean
|
||||
readonly?: boolean
|
||||
purchaseId?: string
|
||||
/** 项目类型 A:货物 B:工程 C:服务,用于模版下载 */
|
||||
projectType?: string
|
||||
acceptanceItems?: any[]
|
||||
batchNum?: number
|
||||
previousBatchesTeams?: { batch: number; team: any[] }[]
|
||||
}>(),
|
||||
{ readonly: false, canFill: true, purchaseId: '', projectType: 'A', batchNum: 1, previousBatchesTeams: () => [] }
|
||||
)
|
||||
const props = defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
readonly?: boolean
|
||||
purchaseId?: string
|
||||
/** 项目类型 A:货物 B:工程 C:服务,用于模版下载 */
|
||||
projectType?: string
|
||||
batchNum?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const templateFileIdsStr = ref('')
|
||||
const copyFromBatch = ref<number | null>(null)
|
||||
// 文件对象数组,用于上传组件显示(包含id和fileTitle)
|
||||
const templateFiles = ref<any[]>([])
|
||||
|
||||
const projectTypeKey = computed(() => (props.projectType === 'A' || props.projectType === 'B' || props.projectType === 'C' ? props.projectType : 'A'))
|
||||
const lyysTemplateLabel = computed(() => LYYS_TEMPLATE_MAP[projectTypeKey.value]?.label || LYYS_TEMPLATE_MAP.A.label)
|
||||
@@ -209,162 +69,81 @@ const lyysTemplateDownloadName = computed(() => `${lyysTemplateLabel.value}.docx
|
||||
const lyysTemplateUrl = computed(() => `/templates/lyys-template-${projectTypeKey.value}.docx`)
|
||||
|
||||
const form = reactive({
|
||||
acceptType: '1',
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: '',
|
||||
acceptContents: [] as any[],
|
||||
acceptTeam: [
|
||||
{ name: '', deptCode: '', deptName: '', teacherNo: '', roleType: '' },
|
||||
{ name: '', deptCode: '', deptName: '', teacherNo: '', roleType: '' },
|
||||
{ name: '', deptCode: '', deptName: '', teacherNo: '', roleType: '' },
|
||||
] as any[],
|
||||
templateFileIds: [] as string[],
|
||||
acceptAddress: '',
|
||||
question: '',
|
||||
remark: '',
|
||||
...props.modelValue,
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
Object.assign(form, val || {})
|
||||
// 金额≥30万时,强制为上传方式
|
||||
if (!props.canFill && form.acceptType === '1') {
|
||||
form.acceptType = '2'
|
||||
// 从外部数据初始化(仅在挂载和 modelValue 引用变化时执行)
|
||||
const initData = () => {
|
||||
const val = props.modelValue
|
||||
if (!val) return
|
||||
|
||||
form.acceptType = '2'
|
||||
form.acceptDate = val.acceptDate || ''
|
||||
form.remark = val.remark || ''
|
||||
|
||||
// 处理文件数据:支持 _templateFiles 数组或 templateFileIds
|
||||
if (val._templateFiles && Array.isArray(val._templateFiles)) {
|
||||
templateFiles.value = val._templateFiles.map((f: any) => ({
|
||||
id: f.id,
|
||||
fileTitle: f.fileTitle,
|
||||
name: f.fileTitle || '',
|
||||
url: '',
|
||||
}))
|
||||
form.templateFileIds = val._templateFiles.map((f: any) => f.id)
|
||||
} else if (val.templateFileIds) {
|
||||
if (typeof val.templateFileIds === 'string') {
|
||||
const ids = val.templateFileIds.split(',').filter(Boolean)
|
||||
templateFiles.value = ids.map((id: string) => ({ id: id.trim(), name: '', url: '' }))
|
||||
form.templateFileIds = ids
|
||||
} else if (Array.isArray(val.templateFileIds)) {
|
||||
templateFiles.value = val.templateFileIds.map((item: any) => {
|
||||
if (typeof item === 'string') return { id: item, name: '', url: '' }
|
||||
return { id: item.id, fileTitle: item.fileTitle, name: item.fileTitle || '', url: '' }
|
||||
})
|
||||
form.templateFileIds = val.templateFileIds.map((item: any) => typeof item === 'string' ? item : item.id).filter(Boolean)
|
||||
}
|
||||
} else {
|
||||
templateFiles.value = []
|
||||
form.templateFileIds = []
|
||||
}
|
||||
// 编辑回显:校内成员根据 name/deptCode/deptName/teacherNo 构建 userList 供 org-selector 显示
|
||||
if (Array.isArray(form.acceptTeam)) {
|
||||
form.acceptTeam.forEach((m: any) => {
|
||||
const isIn = m.roleType === 'LEADER_IN' || m.roleType === 'MEMBER_IN'
|
||||
if (isIn && (m.name || m.teacherNo) && !(m.userList && m.userList.length)) {
|
||||
m.userList = buildUserListFromTeamItem(m)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 挂载时初始化
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
|
||||
// 监听文件变化,更新 form.templateFileIds
|
||||
watch(templateFiles, (files) => {
|
||||
if (Array.isArray(files)) {
|
||||
form.templateFileIds = files.map((f: any) => f.id).filter(Boolean)
|
||||
} else {
|
||||
form.templateFileIds = []
|
||||
}
|
||||
}, { deep: true })
|
||||
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
|
||||
// 金额≥30万时,默认选中上传方式
|
||||
watch(() => props.canFill, (val) => {
|
||||
if (!val && form.acceptType === '1') {
|
||||
form.acceptType = '2'
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => props.acceptanceItems, (items) => {
|
||||
if (items?.length && form.acceptContents.length === 0) {
|
||||
form.acceptContents = items.map((it: any) => ({
|
||||
configId: it.id,
|
||||
itemName: it.itemName,
|
||||
type: it.type,
|
||||
isQualified: '1',
|
||||
remark: '',
|
||||
}))
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(templateFileIdsStr, (s) => {
|
||||
const arr = s ? s.split(',').map((x: string) => x.trim()).filter(Boolean) : []
|
||||
if (JSON.stringify(form.templateFileIds) !== JSON.stringify(arr)) {
|
||||
form.templateFileIds = arr
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => form.templateFileIds, (arr) => {
|
||||
if (Array.isArray(arr) && arr.length) templateFileIdsStr.value = arr.join(',')
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
const addTeam = () => {
|
||||
form.acceptTeam.push({ name: '', deptCode: '', deptName: '', teacherNo: '', roleType: '' })
|
||||
}
|
||||
|
||||
const removeTeam = (idx: number) => {
|
||||
form.acceptTeam.splice(idx, 1)
|
||||
}
|
||||
|
||||
const onCopyFromBatch = (n: number | null) => {
|
||||
if (!n) return
|
||||
const item = props.previousBatchesTeams?.find((x) => x.batch === n)
|
||||
if (item?.team?.length) {
|
||||
form.acceptTeam = item.team.map((m: any) => ({
|
||||
name: m.name || '',
|
||||
deptCode: m.deptCode || '',
|
||||
deptName: m.deptName || '',
|
||||
teacherNo: m.teacherNo || '',
|
||||
roleType: m.roleType || '',
|
||||
userList: buildUserListFromTeamItem(m),
|
||||
}))
|
||||
}
|
||||
copyFromBatch.value = null
|
||||
}
|
||||
|
||||
const rules: FormRules = {
|
||||
acceptType: [{ required: true, message: '请选择验收方式', trigger: 'change' }],
|
||||
acceptDate: [{ required: true, message: '请选择验收日期', trigger: 'change' }],
|
||||
}
|
||||
|
||||
const onRoleChange = (idx: number, val: string) => {
|
||||
const isLeader = val === 'LEADER_IN' || val === 'LEADER_OUT'
|
||||
if (isLeader) {
|
||||
const hasOtherLeader = form.acceptTeam.some((m, i) =>
|
||||
i !== idx && (m.roleType === 'LEADER_IN' || m.roleType === 'LEADER_OUT')
|
||||
)
|
||||
if (hasOtherLeader) {
|
||||
// 只能有一个组长
|
||||
form.acceptTeam[idx].roleType = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 从接口返回的成员项构建 org-selector 所需的 userList(编辑回显用) */
|
||||
const buildUserListFromTeamItem = (t: any) => {
|
||||
if (!t?.name && !t?.teacherNo) return []
|
||||
return [
|
||||
{
|
||||
id: t.teacherNo || t.name,
|
||||
name: t.name || '',
|
||||
type: 'user',
|
||||
deptCode: t.deptCode,
|
||||
deptName: t.deptName,
|
||||
teacherNo: t.teacherNo,
|
||||
realName: t.name,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const onTeamUserChange = (idx: number, list: any[]) => {
|
||||
const m = form.acceptTeam[idx]
|
||||
if (!m) return
|
||||
if (list && list.length) {
|
||||
const u = list[0]
|
||||
m.name = u.name || u.realName || ''
|
||||
m.deptCode = u.deptCode || u.commonDeptCode || ''
|
||||
m.deptName = u.deptName || u.commonDeptName || ''
|
||||
m.teacherNo = u.teacherNo ?? u.username ?? u.id ?? ''
|
||||
m.userList = list
|
||||
} else {
|
||||
m.name = ''
|
||||
m.deptCode = ''
|
||||
m.deptName = ''
|
||||
m.teacherNo = ''
|
||||
m.userList = []
|
||||
}
|
||||
}
|
||||
|
||||
const validate = () => formRef.value?.validate()
|
||||
|
||||
defineExpose({ validate, form })
|
||||
// 获取当前表单数据(供父组件调用)
|
||||
const getFormData = () => ({
|
||||
acceptType: form.acceptType,
|
||||
acceptDate: form.acceptDate,
|
||||
templateFileIds: [...form.templateFileIds],
|
||||
remark: form.remark,
|
||||
})
|
||||
|
||||
defineExpose({ validate, form, getFormData, initData })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.copy-from-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.copy-from-inline :deep(.el-select) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -59,14 +59,11 @@
|
||||
v-show="String(b.batch) === activeTab"
|
||||
:key="b.id"
|
||||
:ref="(el) => setBatchFormRef(b.batch, el)"
|
||||
v-model="batchForms[b.batch]"
|
||||
:can-fill="canFillForm"
|
||||
:model-value="batchForms[b.batch]"
|
||||
:readonly="false"
|
||||
:purchase-id="String(purchaseId)"
|
||||
:project-type="acceptProjectType"
|
||||
:acceptance-items="acceptanceItems"
|
||||
:batch-num="b.batch"
|
||||
:previous-batches-teams="getPreviousBatchesTeams(b.batch)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,26 +80,6 @@
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
<!-- 仅填写履约验收评价表模式可导出;单期 docx,全部期数 zip -->
|
||||
<el-dropdown
|
||||
v-if="showExportDropdown"
|
||||
split-button
|
||||
type="primary"
|
||||
@click="handleDownloadTemplate"
|
||||
@command="handleDownloadTemplateCommand"
|
||||
>
|
||||
导出履约验收评价表
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="current" :disabled="!canExportCurrentBatch">
|
||||
下载当前期({{ activeTab }}期)
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="all" :disabled="!canExportAllBatches">
|
||||
下载全部期数(zip)
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button
|
||||
v-if="mainTab === 'common' || batches.length === 0"
|
||||
type="primary"
|
||||
@@ -125,17 +102,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, nextTick } from 'vue'
|
||||
import { ref, reactive, computed, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { handleBlobFile } from '/@/utils/other'
|
||||
import {
|
||||
saveCommonConfig as apiSaveCommonConfig,
|
||||
getCommonConfigWithBatches,
|
||||
updateBatch,
|
||||
canFillForm as apiCanFillForm,
|
||||
getAcceptanceItems,
|
||||
getDetail,
|
||||
downloadPerformanceAcceptanceTemplate,
|
||||
} from '/@/api/purchase/purchasingAccept'
|
||||
import AcceptCommonForm from './AcceptCommonForm.vue'
|
||||
import AcceptBatchForm from './AcceptBatchForm.vue'
|
||||
@@ -148,8 +121,6 @@ const saving = ref(false)
|
||||
const purchaseId = ref<string | number>('')
|
||||
const applyInfo = ref<any>(null)
|
||||
const rowProjectType = ref<string>('A')
|
||||
const canFillForm = ref(true)
|
||||
const acceptanceItems = ref<any[]>([])
|
||||
const batches = ref<any[]>([])
|
||||
const mainTab = ref('common')
|
||||
const activeTab = ref('1')
|
||||
@@ -160,7 +131,7 @@ const commonForm = ref<Record<string, any>>({})
|
||||
/** 每次打开自增,用于强制 AcceptCommonForm 重新挂载,确保公共信息彻底清空 */
|
||||
const openToken = ref(0)
|
||||
const batchForms = reactive<Record<number, any>>({})
|
||||
/** 记录哪些期已保存到服务器,用于控制“下一期可填”:只有上一期已保存才允许填下一期 */
|
||||
/** 记录哪些期已保存到服务器,用于控制”下一期可填”:只有上一期已保存才允许填下一期 */
|
||||
const batchSavedFlags = ref<Record<number, boolean>>({})
|
||||
|
||||
const setBatchFormRef = (batch: number, el: any) => {
|
||||
@@ -175,28 +146,6 @@ const activeBatchId = computed(() => {
|
||||
/** 项目类型 A:货物 B:工程 C:服务,用于批次表单模版下载 */
|
||||
const acceptProjectType = computed(() => applyInfo.value?.projectType || rowProjectType.value || 'A')
|
||||
|
||||
/** 当前期是否为「填写履约验收评价表」模式,仅此模式可导出 */
|
||||
const canExportCurrentBatch = computed(() => batchForms[Number(activeTab.value)]?.acceptType === '1')
|
||||
/** 是否存在任一批次为填写模式(可导出全部期数 zip) */
|
||||
const canExportAllBatches = computed(() =>
|
||||
batches.value.some((b: any) => batchForms[b.batch]?.acceptType === '1')
|
||||
)
|
||||
/** 仅在有批次且至少一期为填写模式时显示导出下拉 */
|
||||
const showExportDropdown = computed(
|
||||
() => mainTab.value === 'batch' && batches.value.length > 0 && (canExportCurrentBatch.value || canExportAllBatches.value)
|
||||
)
|
||||
|
||||
const getPreviousBatchesTeams = (batchNum: number) => {
|
||||
const list: { batch: number; team: any[] }[] = []
|
||||
for (let n = 1; n < batchNum; n++) {
|
||||
const team = batchForms[n]?.acceptTeam
|
||||
if (Array.isArray(team) && team.length > 0) {
|
||||
list.push({ batch: n, team })
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/** 是否允许编辑该期:第 1 期始终可编辑;第 N 期仅当第 1~N-1 期均已保存后才可编辑 */
|
||||
const canEditBatch = (batch: number) => {
|
||||
if (batch === 1) return true
|
||||
@@ -220,15 +169,11 @@ const loadData = async () => {
|
||||
const currentId = String(purchaseId.value)
|
||||
loading.value = true
|
||||
try {
|
||||
const [configRes, canFillRes] = await Promise.all([
|
||||
getCommonConfigWithBatches(currentId),
|
||||
apiCanFillForm(currentId),
|
||||
])
|
||||
const configRes = await getCommonConfigWithBatches(currentId)
|
||||
// 防止快速切换:若已打开其他申请单,忽略本次结果
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
|
||||
const config = configRes?.data
|
||||
canFillForm.value = !!canFillRes?.data
|
||||
|
||||
if (config?.common) {
|
||||
applyInfo.value = config.common
|
||||
@@ -245,13 +190,6 @@ const loadData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const projectType = applyInfo.value?.projectType || rowProjectType.value || 'A'
|
||||
const typeMap: Record<string, string> = { A: 'A', B: 'B', C: 'C' }
|
||||
const at = typeMap[projectType] || 'A'
|
||||
const itemsRes = await getAcceptanceItems(at)
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
acceptanceItems.value = itemsRes?.data || []
|
||||
|
||||
if (config?.batches?.length) {
|
||||
batches.value = config.batches.sort((a: any, b: any) => (a.batch || 0) - (b.batch || 0))
|
||||
activeTab.value = String(batches.value[0]?.batch || '1')
|
||||
@@ -280,51 +218,32 @@ const loadBatchDetails = async () => {
|
||||
const res = await getDetail(String(purchaseId.value), b.batch)
|
||||
const d = res?.data
|
||||
if (d?.accept) {
|
||||
// 仅当该期在服务端有验收日期(且验收方式)时才视为已保存,避免空结构被当成“已填”
|
||||
const hasSaved = !!(d.accept.acceptDate && d.accept.acceptType)
|
||||
// 仅当该期在服务端有验收日期时才视为已保存
|
||||
const hasSaved = !!d.accept.acceptDate
|
||||
batchSavedFlags.value[b.batch] = hasSaved
|
||||
const itemMap = (acceptanceItems.value || []).reduce((acc: any, it: any) => {
|
||||
acc[it.id] = it
|
||||
return acc
|
||||
}, {})
|
||||
// 优先使用 templateFiles(包含id和fileTitle),否则降级使用 templateFileIds
|
||||
let fileIdsStr = ''
|
||||
if (d.accept.templateFiles && d.accept.templateFiles.length > 0) {
|
||||
// 使用 templateFiles,格式为 {id: string, fileTitle: string}[]
|
||||
fileIdsStr = d.accept.templateFiles.map((f: any) => f.id).join(',')
|
||||
} else if (d.accept.templateFileIds) {
|
||||
// 降级使用 templateFileIds
|
||||
const fileIds = d.accept.templateFileIds
|
||||
fileIdsStr = Array.isArray(fileIds) ? fileIds.join(',') : (fileIds || '')
|
||||
}
|
||||
batchForms[b.batch] = {
|
||||
acceptType: d.accept.acceptType || '1',
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: d.accept.acceptDate || '',
|
||||
acceptAddress: d.accept.acceptAddress || '',
|
||||
question: d.accept.question || '',
|
||||
remark: d.accept.remark || '',
|
||||
templateFileIds: d.accept.templateFileIds || [],
|
||||
acceptContents: (d.contents || []).map((c: any) => {
|
||||
const cfg = itemMap[c.configId]
|
||||
return {
|
||||
configId: c.configId,
|
||||
itemName: cfg?.itemName || '',
|
||||
type: cfg?.type,
|
||||
isQualified: c.isQualified || '1',
|
||||
remark: c.remark || '',
|
||||
}
|
||||
}),
|
||||
acceptTeam: (d.team || []).map((t: any) => ({
|
||||
name: t.name,
|
||||
deptCode: t.deptCode,
|
||||
deptName: t.deptName,
|
||||
teacherNo: t.teacherNo || '',
|
||||
roleType: t.roleType || '',
|
||||
})),
|
||||
templateFileIds: fileIdsStr,
|
||||
// 保存文件信息用于显示
|
||||
_templateFiles: d.accept.templateFiles || [],
|
||||
}
|
||||
if (batchForms[b.batch].acceptTeam.length < 3) {
|
||||
while (batchForms[b.batch].acceptTeam.length < 3) {
|
||||
batchForms[b.batch].acceptTeam.push({ name: '', deptCode: '', deptName: '', teacherNo: '', roleType: '' })
|
||||
}
|
||||
}
|
||||
if (acceptanceItems.value.length && (!batchForms[b.batch].acceptContents || batchForms[b.batch].acceptContents.length === 0)) {
|
||||
batchForms[b.batch].acceptContents = acceptanceItems.value.map((it: any) => ({
|
||||
configId: it.id,
|
||||
itemName: it.itemName,
|
||||
type: it.type,
|
||||
isQualified: '1',
|
||||
remark: '',
|
||||
}))
|
||||
// 通知子组件初始化数据
|
||||
await nextTick()
|
||||
const batchFormRef = batchFormRefMap.value[b.batch]
|
||||
if (batchFormRef?.initData) {
|
||||
batchFormRef.initData()
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
@@ -374,22 +293,28 @@ const saveCurrentBatch = async () => {
|
||||
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||
if (!b?.id) return
|
||||
const form = batchForms[curBatch]
|
||||
if (!form) return
|
||||
|
||||
if (!form.acceptType) {
|
||||
useMessage().error('请选择验收方式')
|
||||
return
|
||||
}
|
||||
if (!form.acceptDate) {
|
||||
// 从子组件获取表单数据
|
||||
const formData = batchFormRef?.getFormData?.() || batchFormRef?.form
|
||||
if (!formData) return
|
||||
|
||||
if (!formData.acceptDate) {
|
||||
useMessage().error('请选择验收日期')
|
||||
return
|
||||
}
|
||||
|
||||
const team = (form.acceptTeam || []).filter((m: any) => m?.name)
|
||||
if (team.length < 3 || team.length % 2 === 0) {
|
||||
useMessage().error('验收小组至少3人且为单数')
|
||||
return
|
||||
// templateFileIds: 提取ID数组
|
||||
let fileIds: string[] = []
|
||||
if (formData.templateFileIds) {
|
||||
if (Array.isArray(formData.templateFileIds)) {
|
||||
fileIds = formData.templateFileIds.map((item: any) => {
|
||||
if (typeof item === 'string') return item
|
||||
if (item && item.id) return item.id
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
} else if (typeof formData.templateFileIds === 'string') {
|
||||
fileIds = formData.templateFileIds.split(',').map((s: string) => s.trim()).filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
@@ -397,14 +322,10 @@ const saveCurrentBatch = async () => {
|
||||
await updateBatch({
|
||||
id: b.id,
|
||||
purchaseId: String(purchaseId.value),
|
||||
acceptType: form.acceptType,
|
||||
acceptDate: form.acceptDate,
|
||||
acceptAddress: form.acceptAddress,
|
||||
question: form.question,
|
||||
remark: form.remark,
|
||||
templateFileIds: form.templateFileIds || [],
|
||||
acceptContents: form.acceptContents || [],
|
||||
acceptTeam: team,
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: formData.acceptDate,
|
||||
remark: formData.remark,
|
||||
templateFileIds: fileIds,
|
||||
})
|
||||
useMessage().success('保存成功')
|
||||
batchSavedFlags.value[curBatch] = true
|
||||
@@ -421,64 +342,6 @@ const handleClose = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
// 导出履约验收评价表(仅填写模式可导出)
|
||||
const handleDownloadTemplate = async () => {
|
||||
if (!purchaseId.value) {
|
||||
useMessage().error('请先选择采购项目')
|
||||
return
|
||||
}
|
||||
if (!canExportCurrentBatch.value) {
|
||||
useMessage().error('仅填写履约验收评价表模式可导出,请先选择该模式并保存')
|
||||
return
|
||||
}
|
||||
await downloadTemplateForBatch(Number(activeTab.value))
|
||||
}
|
||||
|
||||
const handleDownloadTemplateCommand = async (command: string) => {
|
||||
if (!purchaseId.value) {
|
||||
useMessage().error('请先选择采购项目')
|
||||
return
|
||||
}
|
||||
if (command === 'current') {
|
||||
if (!canExportCurrentBatch.value) {
|
||||
useMessage().error('当前期未选择填写履约验收评价表模式,无法导出')
|
||||
return
|
||||
}
|
||||
await downloadTemplateForBatch(Number(activeTab.value))
|
||||
} else if (command === 'all') {
|
||||
if (!canExportAllBatches.value) {
|
||||
useMessage().error('没有填写模式的分期可导出')
|
||||
return
|
||||
}
|
||||
await downloadAllBatchesAsZip()
|
||||
}
|
||||
}
|
||||
|
||||
const downloadTemplateForBatch = async (batchNum: number) => {
|
||||
try {
|
||||
const response = await downloadPerformanceAcceptanceTemplate(String(purchaseId.value), batchNum)
|
||||
const fileName = `履约验收表-${purchaseId.value}-第${batchNum}期.docx`
|
||||
handleBlobFile(response, fileName)
|
||||
useMessage().success(`第${batchNum}期导出成功`)
|
||||
} catch (error: any) {
|
||||
console.error('导出失败:', error)
|
||||
useMessage().error(error?.msg || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
const downloadAllBatchesAsZip = async () => {
|
||||
try {
|
||||
const response = await downloadPerformanceAcceptanceTemplate(String(purchaseId.value), undefined, true)
|
||||
const fileName = `履约验收表-${purchaseId.value}-全部期数.zip`
|
||||
handleBlobFile(response, fileName)
|
||||
const count = batches.value.filter((b: any) => batchForms[b.batch]?.acceptType === '1').length
|
||||
useMessage().success(`已导出${count}期,请查收 zip 文件`)
|
||||
} catch (error: any) {
|
||||
console.error('导出全部期数失败:', error)
|
||||
useMessage().error(error?.msg || '导出全部期数失败')
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_COMMON_FORM = {
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
@@ -501,8 +364,6 @@ const resetAllToDefault = () => {
|
||||
activeTab.value = '1'
|
||||
batchFormRefMap.value = {}
|
||||
batches.value = []
|
||||
acceptanceItems.value = []
|
||||
canFillForm.value = true
|
||||
Object.keys(batchForms).forEach((k) => delete batchForms[Number(k)])
|
||||
batchSavedFlags.value = {}
|
||||
}
|
||||
|
||||
@@ -91,13 +91,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb12" v-if="!isEntrustCenterChannel || (isFlowEmbed && isPurchaseCenter)">
|
||||
<el-col :span="8" class="mb12" >
|
||||
<el-form-item label="采购方式" prop="purchaseType" :required="!isEntrustCenterChannel">
|
||||
<el-select
|
||||
v-model="dataForm.purchaseType"
|
||||
placeholder="请选择采购方式"
|
||||
clearable
|
||||
:disabled="(isFlowEmbed && isPurchaseCenter) ? false : (isDeptSelfMallLocked || isAutoSelectPurchaseType)"
|
||||
:disabled="(isFlowEmbed && isPurchaseCenter) ? false : (isDeptSelfMallLocked || isAutoSelectPurchaseType || isEntrustCenterChannel)"
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in purchaseTypeDeptList"
|
||||
@@ -211,10 +211,9 @@
|
||||
<el-radio-group v-model="dataForm.purchaseMode" :disabled="schoolUnifiedPurchaseFormDisabled">
|
||||
<el-radio v-for="item in purchaseModeSchoolList" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
<!-- <div v-if="schoolUnifiedPurchaseFormDefault != null" class="template-note mt5"><el-text type="info" size="small">根据预算金额与是否集采由系统自动选择</el-text></div> -->
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb12" v-if="isDeptPurchase || isFlowEmbed">
|
||||
<el-col :span="8" class="mb12" >
|
||||
<el-form-item label="采购方式" prop="purchaseType" :required="!isDeptPurchase">
|
||||
<el-select v-model="dataForm.purchaseType" placeholder="请选择采购方式" clearable :disabled="(isFlowEmbed && isPurchaseCenter) ? false : (isAutoSelectPurchaseTypeUnion || flowFieldDisabled('purchaseType'))" style="width: 100%">
|
||||
<el-option v-for="item in purchaseTypeUnionList" :key="item.value" :label="item.label" :value="item.value" />
|
||||
@@ -228,13 +227,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb12">
|
||||
<el-form-item label="校党委" prop="schoolLeaderUserId" :required="!isDeptPurchase">
|
||||
<el-select v-model="dataForm.schoolLeaderUserId" placeholder="请选择校党委" clearable filterable @change="handleSchoolLeaderChange" style="width: 100%" :disabled="flowFieldDisabled('schoolLeaderUserId')">
|
||||
<el-option v-for="item in schoolLeaderList" :key="item.id" :label="item.name" :value="item.userId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 非特殊情况显示业务分管校领导 -->
|
||||
<el-col :span="8" class="mb12" v-if="!isSpecialCase">
|
||||
<el-form-item label="业务分管校领导" prop="schoolBusinessLeaderUserId" :required="!isDeptPurchase">
|
||||
@@ -243,8 +236,16 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb12">
|
||||
<el-form-item label="校党委" prop="schoolLeaderUserId" v-if="isSpecialCase" :required="!isDeptPurchase && isSpecialCase">
|
||||
<el-select v-model="dataForm.schoolLeaderUserId" placeholder="请选择校党委" clearable filterable @change="handleSchoolLeaderChange" style="width: 100%" :disabled="flowFieldDisabled('schoolLeaderUserId')">
|
||||
<el-option v-for="item in schoolLeaderList" :key="item.id" :label="item.name" :value="item.userId" />
|
||||
</el-select>
|
||||
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 特殊情况显示采购分管校领导 -->
|
||||
<el-col :span="8" class="mb12" v-if="isSpecialCase">
|
||||
<el-col :span="8" class="mb12">
|
||||
<el-form-item label="采购分管校领导" prop="purchaseManagerUserId" :required="!isDeptPurchase">
|
||||
<el-select v-model="dataForm.purchaseManagerUserId" placeholder="请选择采购分管校领导" clearable filterable @change="handlePurchaseManagerChange" style="width: 100%" :disabled="flowFieldDisabled('purchaseManagerUserId')">
|
||||
<el-option v-for="item in purchasingManagerList" :key="item.id" :label="item.name" :value="item.userId" />
|
||||
@@ -408,7 +409,7 @@
|
||||
<li>
|
||||
<strong>委托采购中心采购</strong>:
|
||||
在「部门自行采购」中选择「委托采购中心采购」作为采购途径时,申请阶段隐藏采购方式,由采购中心在审核环节选择;
|
||||
对服务类特殊品目且金额在 5 万 ~ 40 万区间时,采购中心会优先推荐「网上商城(服务网上商城)」方式。
|
||||
对服务类特殊品目,采购中心会优先推荐「网上商城(服务网上商城)」方式。
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
@@ -979,8 +980,9 @@ watch(
|
||||
return;
|
||||
}
|
||||
|
||||
// 部门自行采购 & 采购途径为“委托采购中心采购”且为申请阶段:采购方式隐藏且不设置
|
||||
if (isDeptPurchase.value && isEntrustCenterChannel.value && !isFlowEmbed.value) {
|
||||
// 部门自行采购 & 采购途径为”委托采购中心采购”且为新增申请阶段:采购方式隐藏且不设置
|
||||
// 注意:查看模式和编辑模式不清空已有的采购方式
|
||||
if (isDeptPurchase.value && isEntrustCenterChannel.value && !isFlowEmbed.value && !isViewMode.value && !isEditMode.value) {
|
||||
dataForm.purchaseType = '';
|
||||
return;
|
||||
}
|
||||
@@ -1003,12 +1005,13 @@ watch(
|
||||
}
|
||||
}
|
||||
|
||||
// 部门自行采购 & 采购途径为“委托采购中心采购” & 采购中心审批节点:根据特殊服务类目自动设置网上商城
|
||||
if (isAutoSelectPurchaseType.value && isDeptPurchase.value && isEntrustCenterChannel.value && isFlowEmbed.value && isPurchaseCenter.value) {
|
||||
// 部门自行采购 & 采购途径为”委托采购中心采购” & 采购中心审批节点:默认设置为网上商城
|
||||
if (isDeptPurchase.value && isEntrustCenterChannel.value && isFlowEmbed.value && isPurchaseCenter.value) {
|
||||
const onlineMallOption = purchaseTypeDeptList.value.find(item => item.value === DEPT_PURCHASE_TYPE.ONLINE_MALL);
|
||||
if (onlineMallOption && dataForm.purchaseType !== onlineMallOption.value) {
|
||||
dataForm.purchaseType = onlineMallOption.value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 学校统一采购审批阶段:自动设置网上商城采购方式
|
||||
|
||||
@@ -8,33 +8,37 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 采购文件版本列表:保留原文件,多版本分别显示 -->
|
||||
<el-divider content-position="left">采购文件版本</el-divider>
|
||||
<div v-if="purchaseFileVersions.length" class="file-versions mb-2">
|
||||
<el-table :data="purchaseFileVersions" border size="small" max-height="280">
|
||||
<el-table-column type="index" label="版本" width="70" align="center">
|
||||
<template #default="{ $index }">V{{ $index + 1 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileTitle" label="文件名" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="createBy" label="上传人" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="上传时间" width="165" align="center">
|
||||
<template #default="{ row }">{{ formatCreateTime(row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="handleDownloadVersion(row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="mb-2">可继续上传新版本(保留原文件,格式 doc/docx/pdf,单文件不超过 5MB):</div>
|
||||
<upload-file
|
||||
v-model="implementFileIds"
|
||||
:limit="5"
|
||||
:file-type="['doc', 'docx', 'pdf']"
|
||||
:data="{ fileType: PURCHASE_FILE_TYPE }"
|
||||
upload-file-url="/purchase/purchasingfiles/upload"
|
||||
/>
|
||||
<!-- 分配代理(仅符合条件的申请单显示) -->
|
||||
<template v-if="canAssignAgent">
|
||||
<el-divider content-position="left">分配代理</el-divider>
|
||||
<el-form-item label="分配方式">
|
||||
<el-radio-group v-model="agentMode">
|
||||
<el-radio label="designated">指定代理</el-radio>
|
||||
<el-radio label="random">随机分配</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="agentMode === 'designated'" label="选择代理">
|
||||
<el-select v-model="selectedAgentId" placeholder="请选择招标代理" filterable style="width: 100%" :loading="agentListLoading">
|
||||
<el-option v-for="item in agentList" :key="item.id" :label="item.agentName" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="agentMode === 'random'" label="随机结果">
|
||||
<div class="agent-roller">
|
||||
<span v-if="rollingAgentName" class="rolling">{{ rollingAgentName }}</span>
|
||||
<span v-else-if="assignedAgentName" class="assigned">已分配:{{ assignedAgentName }}</span>
|
||||
<span v-else class="placeholder">点击下方按钮进行随机分配</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="applyRow?.agentName" label="当前代理">
|
||||
<el-tag>{{ applyRow.agentName }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="agentMode === 'designated'">
|
||||
<el-button type="primary" :loading="assignAgentSubmitting" :disabled="!selectedAgentId" @click="handleAssignAgentDesignated">指定代理</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="agentMode === 'random'">
|
||||
<el-button type="primary" :loading="assignAgentSubmitting" :disabled="!!assignedAgentName" @click="handleAssignAgentRandom">随机分配</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 仅部门审核角色显示:采购代表相关 -->
|
||||
<template v-if="isDeptAuditRole">
|
||||
@@ -59,37 +63,25 @@
|
||||
</div>
|
||||
<div class="implement-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<template v-if="implementHasPurchaseFiles && !applyRow?.fileFlowInstId">
|
||||
<el-button type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">保存实施采购</el-button>
|
||||
<el-button v-if="canStartFileFlow" type="success" :loading="startFileFlowSubmitting" @click="handleStartFileFlow">发起采购文件审批</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">确定</el-button>
|
||||
</template>
|
||||
<el-button type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">确定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingImplement">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { ref, computed, onMounted, watch, onUnmounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { implementApply, getApplyFiles, startFileFlow, getDeptMembers, getObj } from '/@/api/finance/purchasingrequisition'
|
||||
import { getDeptMembers, getObj, assignAgent } from '/@/api/finance/purchasingrequisition'
|
||||
import { getPage as getAgentPage } from '/@/api/finance/purchaseagent'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import other from '/@/utils/other'
|
||||
import UploadFile from '/@/components/Upload/index.vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import * as orderVue from '/@/api/order/order-key-vue'
|
||||
|
||||
/** 部门审核角色编码:仅该角色下显示采购代表相关页面和功能,流转至部门审核时需填写采购代表 */
|
||||
const PURCHASE_DEPT_AUDIT_ROLE_CODE = 'PURCHASE_DEPT_AUDIT'
|
||||
/** 采购中心角色编码:可保存/发起实施采购,但不出现采购代表相关内容和接口 */
|
||||
const PURCHASE_CENTER_ROLE_CODE = 'PURCHASE_CENTER'
|
||||
|
||||
const roleCode = computed(() => Session.getRoleCode() || '')
|
||||
const isDeptAuditRole = computed(() => roleCode.value === PURCHASE_DEPT_AUDIT_ROLE_CODE)
|
||||
const isPurchaseCenterRole = computed(() => roleCode.value === PURCHASE_CENTER_ROLE_CODE)
|
||||
/** 可发起采购文件审批:部门审核(需填采购代表)、采购中心(不填采购代表) */
|
||||
const canStartFileFlow = computed(() => isDeptAuditRole.value || isPurchaseCenterRole.value)
|
||||
|
||||
// 与编辑界面一致:支持流程 dynamic-link 传入 currJob/currElTab,申请单 ID 优先取 currJob.orderId
|
||||
const props = defineProps({
|
||||
@@ -102,7 +94,6 @@ const emit = defineEmits(['handleJob'])
|
||||
const isFlowEmbed = computed(() => !!props.currJob)
|
||||
|
||||
const route = useRoute()
|
||||
const PURCHASE_FILE_TYPE = '130'
|
||||
|
||||
/** 申请单 ID(数值,用于 getObj 等):与 add 一致,优先流程 currJob.orderId,否则 route.query.id */
|
||||
const applyId = computed(() => {
|
||||
@@ -122,10 +113,6 @@ const applyIdRaw = computed(() => {
|
||||
})
|
||||
|
||||
const applyRow = ref<any>(null)
|
||||
/** 已有采购文件版本列表(按 createTime 排序,用于展示与提交时一并关联) */
|
||||
const purchaseFileVersions = ref<{ id: string; fileTitle?: string; createBy?: string; createTime?: string; remark?: string }[]>([])
|
||||
/** 本次新上传的采购文件(仅新版本,不与已有版本混在一起) */
|
||||
const implementFileIds = ref<string | string[]>([])
|
||||
const implementType = ref<string>('1')
|
||||
const implementSubmitting = ref(false)
|
||||
|
||||
@@ -133,28 +120,117 @@ const representorMode = ref<'single' | 'multi'>('single')
|
||||
const representorTeacherNo = ref<string>('')
|
||||
const representorsMulti = ref<string[]>([])
|
||||
const deptMembers = ref<any[]>([])
|
||||
const startFileFlowSubmitting = ref(false)
|
||||
|
||||
const implementHasPurchaseFiles = computed(() => {
|
||||
if (purchaseFileVersions.value.length > 0) return true
|
||||
const raw = implementFileIds.value
|
||||
if (Array.isArray(raw)) return raw.length > 0
|
||||
return !!raw
|
||||
/** 分配代理相关 */
|
||||
const agentMode = ref<'designated' | 'random'>('designated')
|
||||
const selectedAgentId = ref<string>('')
|
||||
const agentList = ref<any[]>([])
|
||||
const agentListLoading = ref(false)
|
||||
const assignAgentSubmitting = ref(false)
|
||||
const rollingAgentName = ref<string>('')
|
||||
const assignedAgentName = ref<string>('')
|
||||
let rollInterval: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
/** 是否可以分配代理:委托代理采购 且 (学校统一采购 或 部门自行采购且委托采购中心采购) */
|
||||
const canAssignAgent = computed(() => {
|
||||
// 自行组织采购不需要分配代理
|
||||
if (implementType.value !== '2') return false
|
||||
const row = applyRow.value
|
||||
if (!row) return false
|
||||
return row.purchaseMode === '2' || (row.purchaseMode === '0' && row.purchaseType === '4')
|
||||
})
|
||||
|
||||
function formatCreateTime(t?: string) {
|
||||
if (!t) return '-'
|
||||
const d = new Date(t)
|
||||
return isNaN(d.getTime()) ? t : d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
||||
const loadAgentList = async () => {
|
||||
if (!canAssignAgent.value) return
|
||||
agentListLoading.value = true
|
||||
try {
|
||||
const res = await getAgentPage({ size: 500, current: 1 })
|
||||
const records = res?.data?.records ?? res?.records ?? []
|
||||
agentList.value = Array.isArray(records) ? records : []
|
||||
} catch (_) {
|
||||
agentList.value = []
|
||||
} finally {
|
||||
agentListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleDownloadVersion(file: { remark?: string; fileTitle?: string }) {
|
||||
if (!file?.remark) {
|
||||
useMessage().warning('无法获取文件路径')
|
||||
/** 随机分配代理 - 滚动动画 */
|
||||
const startRollingAnimation = (finalAgentName: string) => {
|
||||
if (agentList.value.length === 0) return
|
||||
|
||||
// 清除之前的动画
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval)
|
||||
rollInterval = null
|
||||
}
|
||||
|
||||
rollingAgentName.value = ''
|
||||
assignedAgentName.value = ''
|
||||
|
||||
let currentIndex = 0
|
||||
const totalDuration = 2000 // 总动画时间 2秒
|
||||
const intervalTime = 80 // 滚动间隔
|
||||
|
||||
rollInterval = setInterval(() => {
|
||||
rollingAgentName.value = agentList.value[currentIndex].agentName
|
||||
currentIndex = (currentIndex + 1) % agentList.value.length
|
||||
}, intervalTime)
|
||||
|
||||
// 2秒后停止并显示最终结果
|
||||
setTimeout(() => {
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval)
|
||||
rollInterval = null
|
||||
}
|
||||
rollingAgentName.value = ''
|
||||
assignedAgentName.value = finalAgentName
|
||||
}, totalDuration)
|
||||
}
|
||||
|
||||
const handleAssignAgentRandom = async () => {
|
||||
const id = applyRow.value?.id ?? applyId.value
|
||||
if (!id) {
|
||||
useMessage().warning('无法获取申请单ID')
|
||||
return
|
||||
}
|
||||
const url = `/purchase/purchasingfiles/download?fileName=${encodeURIComponent(file.remark)}&fileTitle=${encodeURIComponent(file.fileTitle || '采购文件')}`
|
||||
other.downBlobFile(url, {}, file.fileTitle || '采购文件')
|
||||
assignAgentSubmitting.value = true
|
||||
try {
|
||||
const res = await assignAgent(Number(id), 'random')
|
||||
// 后端返回分配结果后,展示滚动动画
|
||||
const finalAgentName = res?.data?.agentName || res?.agentName || ''
|
||||
if (finalAgentName) {
|
||||
startRollingAnimation(finalAgentName)
|
||||
} else {
|
||||
useMessage().success('随机分配代理成功')
|
||||
await loadData()
|
||||
}
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '随机分配代理失败')
|
||||
} finally {
|
||||
assignAgentSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleAssignAgentDesignated = async () => {
|
||||
const id = applyRow.value?.id ?? applyId.value
|
||||
if (!id) {
|
||||
useMessage().warning('无法获取申请单ID')
|
||||
return
|
||||
}
|
||||
if (!selectedAgentId.value) {
|
||||
useMessage().warning('请选择招标代理')
|
||||
return
|
||||
}
|
||||
assignAgentSubmitting.value = true
|
||||
try {
|
||||
await assignAgent(Number(id), 'designated', selectedAgentId.value)
|
||||
useMessage().success('指定代理成功')
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '指定代理失败')
|
||||
} finally {
|
||||
assignAgentSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const isInIframe = () => typeof window !== 'undefined' && window.self !== window.top
|
||||
@@ -173,21 +249,16 @@ const loadData = async () => {
|
||||
}
|
||||
const needDeptMembers = isDeptAuditRole.value
|
||||
try {
|
||||
const idStr = applyIdRaw.value || String(id)
|
||||
const requests: [ReturnType<typeof getObj>, ReturnType<typeof getApplyFiles>, ReturnType<typeof getDeptMembers>?] = [
|
||||
getObj(id),
|
||||
getApplyFiles(idStr)
|
||||
]
|
||||
const requests: [ReturnType<typeof getObj>, ReturnType<typeof getDeptMembers>?] = [getObj(id)]
|
||||
if (needDeptMembers) requests.push(getDeptMembers())
|
||||
const results = await Promise.all(requests)
|
||||
const detailRes = results[0]
|
||||
const filesRes = results[1]
|
||||
const membersRes = needDeptMembers ? results[2] : null
|
||||
const membersRes = needDeptMembers ? results[1] : null
|
||||
|
||||
applyRow.value = detailRes?.data ? { ...detailRes.data, id: detailRes.data.id ?? id } : { id }
|
||||
const row = applyRow.value
|
||||
if (row?.implementType) implementType.value = row.implementType
|
||||
// 回显需求部门初审-采购代表人方式与人员(与发起采购文件审批时保存的一致)
|
||||
// 回显需求部门初审-采购代表人方式与人员
|
||||
if (row?.representorTeacherNo) {
|
||||
representorMode.value = 'single'
|
||||
representorTeacherNo.value = row.representorTeacherNo ?? ''
|
||||
@@ -201,19 +272,17 @@ const loadData = async () => {
|
||||
representorTeacherNo.value = ''
|
||||
representorsMulti.value = []
|
||||
}
|
||||
const list = filesRes?.data || []
|
||||
const purchaseFiles = list.filter((f: any) => f.fileType === PURCHASE_FILE_TYPE)
|
||||
purchaseFileVersions.value = purchaseFiles.map((f: any) => ({
|
||||
id: String(f.id),
|
||||
fileTitle: f.fileTitle || f.file_title || '采购文件',
|
||||
createBy: f.createBy ?? f.create_by ?? '-',
|
||||
createTime: f.createTime || f.create_time,
|
||||
remark: f.remark
|
||||
}))
|
||||
deptMembers.value = needDeptMembers && membersRes?.data ? membersRes.data : []
|
||||
|
||||
// 加载代理列表并回显已分配代理
|
||||
if (canAssignAgent.value) {
|
||||
await loadAgentList()
|
||||
if (row?.agentName) {
|
||||
assignedAgentName.value = row.agentName
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
applyRow.value = { id }
|
||||
purchaseFileVersions.value = []
|
||||
deptMembers.value = []
|
||||
}
|
||||
}
|
||||
@@ -234,27 +303,23 @@ const handleImplementSubmit = async () => {
|
||||
useMessage().warning('请选择实施采购方式')
|
||||
return
|
||||
}
|
||||
const existingIds = purchaseFileVersions.value.map((f) => f.id)
|
||||
const raw = implementFileIds.value
|
||||
const newIds: string[] = Array.isArray(raw)
|
||||
? raw.map((x: any) => (typeof x === 'object' && x?.id ? x.id : x)).filter(Boolean)
|
||||
: raw ? [String(raw)] : []
|
||||
const fileIds = [...existingIds, ...newIds]
|
||||
if (fileIds.length === 0) {
|
||||
useMessage().warning('请至少上传一个采购文件')
|
||||
return
|
||||
// 仅部门审核角色校验采购代表人
|
||||
if (isDeptAuditRole.value) {
|
||||
if (representorMode.value === 'single' && !representorTeacherNo.value) {
|
||||
useMessage().warning('请选择采购代表人')
|
||||
return
|
||||
}
|
||||
if (representorMode.value === 'multi' && !representorsMulti.value?.length) {
|
||||
useMessage().warning('请选择部门多人')
|
||||
return
|
||||
}
|
||||
}
|
||||
// 仅部门审核角色提交采购代表;采购中心保存时不传采购代表
|
||||
const single = isDeptAuditRole.value && representorMode.value === 'single' ? representorTeacherNo.value : undefined
|
||||
const multi = isDeptAuditRole.value && representorMode.value === 'multi' && representorsMulti.value?.length ? representorsMulti.value.join(',') : undefined
|
||||
implementSubmitting.value = true
|
||||
try {
|
||||
await implementApply(id, fileIds, implementType.value, single, multi)
|
||||
// TODO: 调用保存接口
|
||||
useMessage().success('实施采购已保存')
|
||||
implementFileIds.value = []
|
||||
await loadData()
|
||||
postMessage('purchasingimplement:saved')
|
||||
// 流程嵌入场景:通知流程当前 Tab 已保存,避免审批时提示“未保存”
|
||||
// 流程嵌入场景:通知流程当前 Tab 已保存
|
||||
if (isFlowEmbed.value && props.currJob && props.currElTab?.id) {
|
||||
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emit)
|
||||
}
|
||||
@@ -265,40 +330,7 @@ const handleImplementSubmit = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleStartFileFlow = async () => {
|
||||
const row = applyRow.value
|
||||
const id = row?.id ?? applyId.value
|
||||
if (!id) return
|
||||
// 部门审核角色必须填写采购代表;采购中心不填采购代表
|
||||
if (isDeptAuditRole.value) {
|
||||
if (representorMode.value === 'single') {
|
||||
if (!representorTeacherNo.value) {
|
||||
useMessage().warning('请选择采购代表人')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (!representorsMulti.value?.length) {
|
||||
useMessage().warning('请选择部门多人')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
startFileFlowSubmitting.value = true
|
||||
try {
|
||||
const single = isDeptAuditRole.value && representorMode.value === 'single' ? representorTeacherNo.value : undefined
|
||||
const multi = isDeptAuditRole.value && representorMode.value === 'multi' ? representorsMulti.value.join(',') : undefined
|
||||
await startFileFlow(id, single, multi)
|
||||
useMessage().success('已发起采购文件审批流程')
|
||||
postMessage('purchasingimplement:submitSuccess')
|
||||
await loadData()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '发起失败')
|
||||
} finally {
|
||||
startFileFlowSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程嵌入时供 handle.vue 调用的“保存”回调:与页面按钮保存逻辑保持一致 */
|
||||
/** 流程嵌入时供 handle.vue 调用的”保存”回调:与页面按钮保存逻辑保持一致 */
|
||||
async function flowSubmitForm() {
|
||||
await handleImplementSubmit()
|
||||
}
|
||||
@@ -325,6 +357,14 @@ onMounted(async () => {
|
||||
await orderVue.currElTabIsView({}, props.currJob, props.currElTab.id, flowSubmitForm)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理滚动动画定时器
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval)
|
||||
rollInterval = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -352,4 +392,35 @@ onMounted(async () => {
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.agent-roller {
|
||||
padding: 12px 16px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.rolling {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
animation: blink 0.1s infinite;
|
||||
}
|
||||
|
||||
.assigned {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -292,77 +292,72 @@
|
||||
<!-- 实施采购:iframe 嵌入 implement.vue,供列表与流程页面使用 -->
|
||||
<ImplementForm ref="implementFormRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 分配代理弹窗 -->
|
||||
<!-- 采购文件审核弹窗 -->
|
||||
<DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 采购代表弹窗 -->
|
||||
<el-dialog
|
||||
v-model="assignAgentDialogVisible"
|
||||
title="分配代理"
|
||||
width="420px"
|
||||
v-model="representorDialogVisible"
|
||||
title="设置采购代表"
|
||||
width="500px"
|
||||
destroy-on-close
|
||||
@close="assignAgentForm.agentId = ''; assignAgentForm.mode = 'designated'"
|
||||
>
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="分配方式">
|
||||
<el-radio-group v-model="assignAgentForm.mode">
|
||||
<el-radio label="designated">指定</el-radio>
|
||||
<el-radio label="random">随机</el-radio>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择方式">
|
||||
<el-radio-group v-model="representorForm.mode">
|
||||
<el-radio label="single">指定采购代表人</el-radio>
|
||||
<el-radio label="multi">部门多人系统抽取</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="assignAgentForm.mode === 'designated'" label="招标代理">
|
||||
<el-form-item v-if="representorForm.mode === 'single'" label="采购代表人">
|
||||
<el-select
|
||||
v-model="assignAgentForm.agentId"
|
||||
placeholder="请选择招标代理"
|
||||
v-model="representorForm.teacherNo"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:loading="agentListLoading"
|
||||
>
|
||||
<el-option v-for="item in agentList" :key="item.id" :label="item.agentName" :value="item.id" />
|
||||
<el-option v-for="m in representorDeptMembers" :key="m.userId || m.teacherNo || m.id" :label="m.realName || m.name || m.teacherNo" :value="m.userId || m.teacherNo || m.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="assignAgentCurrentRow?.agentName" label="当前代理">
|
||||
<span>{{ assignAgentCurrentRow.agentName }}</span>
|
||||
<el-form-item v-else label="部门多人">
|
||||
<el-select
|
||||
v-model="representorForm.multiIds"
|
||||
multiple
|
||||
placeholder="请选择多人,系统将自动抽取一人"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="m in representorDeptMembers" :key="m.userId || m.teacherNo || m.id" :label="m.realName || m.name || m.teacherNo" :value="m.userId || m.teacherNo || m.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="assignAgentDialogVisible = false">取消</el-button>
|
||||
<el-button
|
||||
v-if="assignAgentForm.mode === 'random'"
|
||||
type="primary"
|
||||
:loading="assignAgentSubmitting"
|
||||
@click="handleAssignAgentRandom"
|
||||
>
|
||||
随机分配
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="primary"
|
||||
:loading="assignAgentSubmitting"
|
||||
:disabled="!assignAgentForm.agentId"
|
||||
@click="handleAssignAgentDesignated"
|
||||
>
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
<el-button @click="representorDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="representorSubmitting" @click="handleSaveRepresentor">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 采购文件审核弹窗 -->
|
||||
<DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingRequisition">
|
||||
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
|
||||
import { ref, reactive, defineAsyncComponent, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { getPage, delObj, submitObj, getArchiveDownloadUrl, getApplyTemplateDownloadUrl, getFileApplyTemplateDownloadUrl, assignAgent } from "/@/api/finance/purchasingrequisition";
|
||||
import { getPage as getAgentPage } from '/@/api/finance/purchaseagent';
|
||||
import { getPage, delObj, submitObj, getArchiveDownloadUrl, getApplyTemplateDownloadUrl, getFileApplyTemplateDownloadUrl, getDeptMembers, saveRepresentor } from "/@/api/finance/purchasingrequisition";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { useAuth } from '/@/hooks/auth';
|
||||
import { getDicts } from '/@/api/admin/dict';
|
||||
import { getTree } from '/@/api/finance/purchasingcategory';
|
||||
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning, DocumentChecked, Edit, Delete, Upload, FolderOpened, Download } from '@element-plus/icons-vue'
|
||||
import { List, Document, DocumentCopy, Search, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning, DocumentChecked, Edit, Delete, Upload, FolderOpened, Download, User } from '@element-plus/icons-vue'
|
||||
import other from '/@/utils/other'
|
||||
import { Session } from '/@/utils/storage'
|
||||
|
||||
// 角色常量
|
||||
const PURCHASE_DEPT_AUDIT_ROLE_CODE = 'PURCHASE_DEPT_AUDIT'
|
||||
const roleCode = computed(() => Session.getRoleCode() || '')
|
||||
const isDeptAuditRole = computed(() => roleCode.value === PURCHASE_DEPT_AUDIT_ROLE_CODE)
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
@@ -391,6 +386,7 @@ const acceptModalRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const docAuditDialogRef = ref()
|
||||
const { hasAuth } = useAuth()
|
||||
/** 审批过程弹窗:是否显示、当前行对应的流程 job(供 Comment 组件用)、类型(申请单/文件) */
|
||||
const showFlowComment = ref(false)
|
||||
const currFlowJob = ref<{ id?: number; flowInstId?: number } | null>(null)
|
||||
@@ -398,72 +394,54 @@ const currFlowCommentType = ref<'apply' | 'file'>('apply')
|
||||
|
||||
const implementFormRef = ref()
|
||||
|
||||
/** 分配代理弹窗 */
|
||||
const assignAgentDialogVisible = ref(false)
|
||||
const assignAgentCurrentRow = ref<any>(null)
|
||||
const assignAgentForm = reactive({ mode: 'designated' as 'designated' | 'random', agentId: '' })
|
||||
const agentList = ref<any[]>([])
|
||||
const agentListLoading = ref(false)
|
||||
const assignAgentSubmitting = ref(false)
|
||||
/** 采购代表弹窗 */
|
||||
const representorDialogVisible = ref(false)
|
||||
const representorCurrentRow = ref<any>(null)
|
||||
const representorForm = reactive({ mode: 'single' as 'single' | 'multi', teacherNo: '', multiIds: [] as string[] })
|
||||
const representorDeptMembers = ref<any[]>([])
|
||||
const representorSubmitting = ref(false)
|
||||
|
||||
const openAssignAgentDialog = async (row: any) => {
|
||||
assignAgentCurrentRow.value = row
|
||||
assignAgentForm.mode = 'designated'
|
||||
assignAgentForm.agentId = ''
|
||||
assignAgentDialogVisible.value = true
|
||||
agentListLoading.value = true
|
||||
const openRepresentorDialog = async (row: any) => {
|
||||
representorCurrentRow.value = row
|
||||
representorForm.mode = 'single'
|
||||
representorForm.teacherNo = ''
|
||||
representorForm.multiIds = []
|
||||
representorDialogVisible.value = true
|
||||
try {
|
||||
const res = await getAgentPage({ size: 500, current: 1 })
|
||||
const records = res?.data?.records ?? res?.records ?? []
|
||||
agentList.value = Array.isArray(records) ? records : []
|
||||
const res = await getDeptMembers()
|
||||
representorDeptMembers.value = res?.data || []
|
||||
} catch (_) {
|
||||
agentList.value = []
|
||||
} finally {
|
||||
agentListLoading.value = false
|
||||
representorDeptMembers.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const handleAssignAgentRandom = async () => {
|
||||
const row = assignAgentCurrentRow.value
|
||||
const handleSaveRepresentor = async () => {
|
||||
const row = representorCurrentRow.value
|
||||
const id = row?.id ?? row?.purchaseId
|
||||
if (id == null || id === '') {
|
||||
useMessage().warning('无法获取申请单ID')
|
||||
return
|
||||
}
|
||||
assignAgentSubmitting.value = true
|
||||
try {
|
||||
await assignAgent(Number(id), 'random')
|
||||
useMessage().success('随机分配代理成功')
|
||||
assignAgentDialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '随机分配代理失败')
|
||||
} finally {
|
||||
assignAgentSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleAssignAgentDesignated = async () => {
|
||||
const row = assignAgentCurrentRow.value
|
||||
const id = row?.id ?? row?.purchaseId
|
||||
if (id == null || id === '') {
|
||||
useMessage().warning('无法获取申请单ID')
|
||||
if (representorForm.mode === 'single' && !representorForm.teacherNo) {
|
||||
useMessage().warning('请选择采购代表人')
|
||||
return
|
||||
}
|
||||
if (!assignAgentForm.agentId) {
|
||||
useMessage().warning('请选择招标代理')
|
||||
if (representorForm.mode === 'multi' && !representorForm.multiIds.length) {
|
||||
useMessage().warning('请选择部门多人')
|
||||
return
|
||||
}
|
||||
assignAgentSubmitting.value = true
|
||||
representorSubmitting.value = true
|
||||
try {
|
||||
await assignAgent(Number(id), 'designated', assignAgentForm.agentId)
|
||||
useMessage().success('指定代理成功')
|
||||
assignAgentDialogVisible.value = false
|
||||
const teacherNo = representorForm.mode === 'single' ? representorForm.teacherNo : undefined
|
||||
const multiIds = representorForm.mode === 'multi' ? representorForm.multiIds.join(',') : undefined
|
||||
await saveRepresentor(Number(id), teacherNo, multiIds)
|
||||
useMessage().success('保存采购代表成功')
|
||||
representorDialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '指定代理失败')
|
||||
useMessage().error(e?.msg || '保存采购代表失败')
|
||||
} finally {
|
||||
assignAgentSubmitting.value = false
|
||||
representorSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +581,7 @@ const handleSubmit = async (row: any) => {
|
||||
/** 操作栏「更多」菜单项配置 */
|
||||
const getActionMenuItems = (row: any) => {
|
||||
const isTemp = row?.status === '-1';
|
||||
return [
|
||||
const items = [
|
||||
{
|
||||
command: 'edit',
|
||||
label: '编辑',
|
||||
@@ -626,18 +604,19 @@ const getActionMenuItems = (row: any) => {
|
||||
command: 'accept',
|
||||
label: '履约验收',
|
||||
icon: DocumentChecked,
|
||||
visible: () => true,
|
||||
visible: () => hasAuth('purchase_accept'),
|
||||
},
|
||||
{
|
||||
command: 'implement',
|
||||
label: '实施采购',
|
||||
icon: Upload
|
||||
icon: Upload,
|
||||
visible: () => hasAuth('purchase_implement'),
|
||||
},
|
||||
{
|
||||
command: 'archive',
|
||||
label: '文件归档',
|
||||
icon: FolderOpened,
|
||||
visible: () => true,
|
||||
visible: () => hasAuth('purchase_archive'),
|
||||
},
|
||||
{
|
||||
command: 'downloadApply',
|
||||
@@ -646,24 +625,31 @@ const getActionMenuItems = (row: any) => {
|
||||
visible: () => true,
|
||||
},
|
||||
{
|
||||
command: 'downloadFileApply',
|
||||
label: '下载文件审批表',
|
||||
icon: Download,
|
||||
visible: () => true,
|
||||
},
|
||||
{
|
||||
command: 'assignAgent',
|
||||
label: '分配代理',
|
||||
icon: Collection,
|
||||
visible: () => row?.purchaseMode === '2' || (row?.purchaseMode === '0' && row?.purchaseType === '4'),
|
||||
},
|
||||
{
|
||||
command: 'docAudit',
|
||||
label: '采购文件审核',
|
||||
icon: DocumentChecked,
|
||||
visible: () => row?.implementType === '2' && row?.agentId,
|
||||
command: 'representor',
|
||||
label: '采购代表',
|
||||
icon: User,
|
||||
visible: () => isDeptAuditRole.value,
|
||||
},
|
||||
// {
|
||||
// command: 'downloadFileApply',
|
||||
// label: '下载文件审批表',
|
||||
// icon: Download,
|
||||
// visible: () => true,
|
||||
// },
|
||||
// {
|
||||
// command: 'docAudit',
|
||||
// label: '采购文件审核',
|
||||
// icon: DocumentChecked,
|
||||
// visible: () => row?.implementType === '2' && row?.agentId,
|
||||
// },
|
||||
];
|
||||
// 过滤出有权限且可见的菜单项
|
||||
return items.filter(item => {
|
||||
if (item.visible === undefined) return true;
|
||||
if (typeof item.visible === 'boolean') return item.visible;
|
||||
if (typeof item.visible === 'function') return item.visible();
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/** 处理更多操作下拉菜单命令 */
|
||||
@@ -693,12 +679,12 @@ const handleMoreCommand = (command: string, row: any) => {
|
||||
case 'downloadFileApply':
|
||||
handleDownloadFileApply(row);
|
||||
break;
|
||||
case 'assignAgent':
|
||||
openAssignAgentDialog(row);
|
||||
break;
|
||||
case 'docAudit':
|
||||
handleDocAudit(row);
|
||||
break;
|
||||
case 'representor':
|
||||
openRepresentorDialog(row);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%"
|
||||
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%;display: none"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table-column type="index" :label="t('jfcomment.index')" width="40"/>
|
||||
<el-table-column prop="flowKey" :label="t('jfcomment.flowKey')" show-overflow-tooltip :width="data.width">
|
||||
@@ -103,7 +103,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle"
|
||||
<pagination style="display: none" @size-change="sizeChangeHandle" @current-change="currentChangeHandle"
|
||||
v-bind="state.pagination"/>
|
||||
</div>
|
||||
<el-timeline style="margin-top: 22px; margin-left: 12px">
|
||||
@@ -116,7 +116,7 @@
|
||||
:timestamp="item.endTime">
|
||||
<div style="align-items: center; display: flex;">
|
||||
<span style="margin-right: 15px">
|
||||
{{ item.jobName }} {{ item.userName }}
|
||||
{{ item.nodeName }} {{ item.userName }}
|
||||
</span>
|
||||
<span style="margin-right: 15px">
|
||||
<convert-role-name :value="item"></convert-role-name>
|
||||
|
||||
Reference in New Issue
Block a user