更新采购申请

This commit is contained in:
吴红兵
2026-03-01 16:00:24 +08:00
parent b1df2277fe
commit efc5dda78a
2 changed files with 324 additions and 58 deletions

View File

@@ -102,6 +102,31 @@ export function sendToAgent(applyId: number | string) {
}); });
} }
/**
* 撤回招标代理:撤回已发送的招标代理
* @param applyId 采购申请ID
*/
export function revokeAgent(applyId: number | string) {
return request({
url: '/purchase/purchasingapply/revokeAgent',
method: 'post',
data: { id: Number(applyId) }
});
}
/**
* 保存实施采购方式(分步骤实施采购-第一步)
* @param id 采购申请ID
* @param implementType 实施采购方式1-自行组织采购2-委托代理采购
*/
export function saveImplementType(id: number | string, implementType: string) {
return request({
url: '/purchase/purchasingapply/save-implement-type',
method: 'post',
data: { id: Number(id), implementType }
});
}
/** /**
* 修改采购申请 * 修改采购申请
* @param obj 对象数据 * @param obj 对象数据

View File

@@ -1,16 +1,44 @@
<template> <template>
<div class="implement-page"> <div class="implement-page">
<div class="implement-form"> <div class="implement-form">
<!-- 步骤一选择实施采购方式 -->
<div class="step-section">
<div class="step-header">
<span class="step-number" :class="{ completed: step1Completed && !isEditingStep1 }">1</span>
<span class="step-title">选择实施采购方式</span>
<el-tag v-if="step1Completed && !isEditingStep1" type="success" size="small">已完成</el-tag>
</div>
<div class="step-content">
<el-form-item label="实施采购方式" required> <el-form-item label="实施采购方式" required>
<el-radio-group v-model="implementType"> <el-radio-group v-model="implementType" :disabled="step1Completed && !isEditingStep1">
<el-radio :label="IMPLEMENT_TYPE.SELF_ORGANIZED">自行组织采购</el-radio> <el-radio :label="IMPLEMENT_TYPE.SELF_ORGANIZED">自行组织采购</el-radio>
<el-radio :label="IMPLEMENT_TYPE.ENTRUST_AGENT">委托代理采购</el-radio> <el-radio :label="IMPLEMENT_TYPE.ENTRUST_AGENT">委托代理采购</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<!-- 步骤一未完成显示保存按钮 -->
<el-form-item v-if="!step1Completed">
<el-button type="primary" :loading="saveTypeSubmitting" :disabled="!implementType" @click="handleSaveImplementType">保存</el-button>
</el-form-item>
<!-- 步骤一已完成但正在编辑显示修改确认按钮 -->
<el-form-item v-else-if="isEditingStep1">
<el-button type="primary" :loading="saveTypeSubmitting" :disabled="!implementType" @click="handleReSaveImplementType">确认修改</el-button>
<el-button @click="cancelEditStep1">取消</el-button>
</el-form-item>
<!-- 步骤一已完成且未在编辑显示修改按钮 -->
<el-form-item v-else>
<el-button type="default" @click="startEditStep1">修改</el-button>
</el-form-item>
</div>
</div>
<!-- 分配代理符合条件的申请单显示 --> <!-- 步骤二分配代理委托代理采购且步骤一完成时显示 -->
<template v-if="canAssignAgent"> <div v-if="showStep2" class="step-section">
<el-divider content-position="left">分配代理</el-divider> <div class="step-header">
<span class="step-number" :class="{ completed: step2Completed }">2</span>
<span class="step-title">分配代理机构</span>
<el-tag v-if="step2Completed" type="success" size="small">已完成</el-tag>
</div>
<div class="step-content">
<el-form-item label="分配方式"> <el-form-item label="分配方式">
<el-radio-group v-model="agentMode"> <el-radio-group v-model="agentMode">
<el-radio label="designated">指定代理</el-radio> <el-radio label="designated">指定代理</el-radio>
@@ -31,22 +59,34 @@
</el-form-item> </el-form-item>
<el-form-item v-if="applyRow?.agentName" label="当前代理"> <el-form-item v-if="applyRow?.agentName" label="当前代理">
<el-tag>{{ applyRow.agentName }}</el-tag> <el-tag>{{ applyRow.agentName }}</el-tag>
<!-- 发送状态 -->
<el-tag v-if="applyRow?.agentSent === YES_NO.YES" type="success" style="margin-left: 8px;">已发送</el-tag>
<el-tag v-else type="info" style="margin-left: 8px;">未发送</el-tag>
</el-form-item> </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 v-if="agentMode === 'designated' && canReassignAgent">
<el-button type="primary" :loading="assignAgentSubmitting" :disabled="!selectedAgentId" @click="handleAssignAgentDesignated">{{ step2Completed ? '重新指定' : '指定代理' }}</el-button>
</el-form-item> </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 v-if="agentMode === 'random' && canReassignAgent">
<el-button type="primary" :loading="assignAgentSubmitting" @click="handleAssignAgentRandom">随机分配</el-button>
</el-form-item> </el-form-item>
<!-- 发送招标代理按钮 --> <!-- 发送招标代理按钮 -->
<el-form-item v-if="canSendToAgent"> <el-form-item v-if="canSendToAgent">
<el-button type="success" :loading="sendToAgentSubmitting" @click="handleSendToAgent">发送招标代理</el-button> <el-button type="success" :loading="sendToAgentSubmitting" @click="handleSendToAgent">发送招标代理</el-button>
</el-form-item> </el-form-item>
</template> <!-- 撤回招标代理按钮 -->
<el-form-item v-if="canRevokeAgent">
<el-button type="warning" :loading="revokeAgentSubmitting" @click="handleRevokeAgent">撤回</el-button>
</el-form-item>
</div> </div>
<div class="implement-footer"> </div>
</div>
<!-- 底部按钮编辑步骤一时隐藏 -->
<div v-if="!isEditingStep1" class="implement-footer">
<el-button @click="handleClose">取消</el-button> <el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">确定</el-button> <!-- 只有步骤一完成后才显示确定按钮 -->
<el-button v-if="step1Completed" type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">确定</el-button>
</div> </div>
</div> </div>
</template> </template>
@@ -54,7 +94,7 @@
<script setup lang="ts" name="PurchasingImplement"> <script setup lang="ts" name="PurchasingImplement">
import { ref, computed, onMounted, watch, onUnmounted } from 'vue' import { ref, computed, onMounted, watch, onUnmounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { getDeptMembers, getObj, assignAgent, sendToAgent, implementApply } from '/@/api/finance/purchasingrequisition' import { getDeptMembers, getObj, assignAgent, sendToAgent, revokeAgent, saveImplementType } from '/@/api/finance/purchasingrequisition'
import { getPage as getAgentPage } from '/@/api/finance/purchaseagent' import { getPage as getAgentPage } from '/@/api/finance/purchaseagent'
import { useMessage } from '/@/hooks/message' import { useMessage } from '/@/hooks/message'
import { Session } from '/@/utils/storage' import { Session } from '/@/utils/storage'
@@ -131,6 +171,12 @@ const applyRow = ref<any>(null)
const implementType = ref<string>(IMPLEMENT_TYPE.SELF_ORGANIZED) const implementType = ref<string>(IMPLEMENT_TYPE.SELF_ORGANIZED)
const implementSubmitting = ref(false) const implementSubmitting = ref(false)
/** 步骤控制 */
const step1Completed = ref(false)
const saveTypeSubmitting = ref(false)
const isEditingStep1 = ref(false)
const originalImplementType = ref<string>('')
const representorMode = ref<'single' | 'multi'>('single') const representorMode = ref<'single' | 'multi'>('single')
const representorTeacherNo = ref<string>('') const representorTeacherNo = ref<string>('')
const representorsMulti = ref<string[]>([]) const representorsMulti = ref<string[]>([])
@@ -145,12 +191,15 @@ const assignAgentSubmitting = ref(false)
const rollingAgentName = ref<string>('') const rollingAgentName = ref<string>('')
const assignedAgentName = ref<string>('') const assignedAgentName = ref<string>('')
const sendToAgentSubmitting = ref(false) const sendToAgentSubmitting = ref(false)
const revokeAgentSubmitting = ref(false)
let rollInterval: ReturnType<typeof setInterval> | null = null let rollInterval: ReturnType<typeof setInterval> | null = null
/** 是否可以分配代理:委托代理采购 且 (学校统一采购 或 部门自行采购且委托采购中心采购) */ /** 是否可以分配代理:委托代理采购 且 (学校统一采购 或 部门自行采购且委托采购中心采购) 且 步骤一已完成 */
const canAssignAgent = computed(() => { const canAssignAgent = computed(() => {
// 自行组织采购不需要分配代理 // 自行组织采购不需要分配代理
if (implementType.value !== IMPLEMENT_TYPE.ENTRUST_AGENT) return false if (implementType.value !== IMPLEMENT_TYPE.ENTRUST_AGENT) return false
// 步骤一必须完成
if (!step1Completed.value) return false
const row = applyRow.value const row = applyRow.value
if (!row) return false if (!row) return false
// 学校统一采购 或 部门自行采购且委托采购中心采购 // 学校统一采购 或 部门自行采购且委托采购中心采购
@@ -158,6 +207,16 @@ const canAssignAgent = computed(() => {
|| (row.purchaseMode === PURCHASE_MODE.DEPT_SELF && row.purchaseChannel === PURCHASE_CHANNEL.ENTRUST_CENTER) || (row.purchaseMode === PURCHASE_MODE.DEPT_SELF && row.purchaseChannel === PURCHASE_CHANNEL.ENTRUST_CENTER)
}) })
/** 是否显示步骤二:委托代理采购 且 步骤一已完成 且 不在编辑步骤一 */
const showStep2 = computed(() => {
return step1Completed.value && !isEditingStep1.value && implementType.value === IMPLEMENT_TYPE.ENTRUST_AGENT
})
/** 步骤二是否完成:已分配代理 */
const step2Completed = computed(() => {
return !!applyRow.value?.agentId
})
/** 是否可以发送招标代理:委托代理采购 且 已分配代理 且 未发送 */ /** 是否可以发送招标代理:委托代理采购 且 已分配代理 且 未发送 */
const canSendToAgent = computed(() => { const canSendToAgent = computed(() => {
// 自行组织采购不需要发送 // 自行组织采购不需要发送
@@ -168,8 +227,23 @@ const canSendToAgent = computed(() => {
return !!row.agentId && row.agentSent !== YES_NO.YES return !!row.agentId && row.agentSent !== YES_NO.YES
}) })
/** 是否可以重新分配代理:未发送招标代理时可操作 */
const canReassignAgent = computed(() => {
const row = applyRow.value
if (!row) return false
// 未发送招标代理时可重新分配
return row.agentSent !== YES_NO.YES
})
/** 是否可以撤回招标代理:已发送招标代理时可撤回 */
const canRevokeAgent = computed(() => {
const row = applyRow.value
if (!row) return false
// 已分配代理 且 已发送招标代理时可撤回
return !!row.agentId && row.agentSent === YES_NO.YES
})
const loadAgentList = async () => { const loadAgentList = async () => {
if (!canAssignAgent.value) return
agentListLoading.value = true agentListLoading.value = true
try { try {
const res = await getAgentPage({ size: 500, current: 1 }) const res = await getAgentPage({ size: 500, current: 1 })
@@ -280,6 +354,106 @@ const handleSendToAgent = async () => {
} }
} }
/** 撤回招标代理 */
const handleRevokeAgent = async () => {
const id = applyRow.value?.id ?? applyId.value
if (!id) {
useMessage().warning('无法获取申请单ID')
return
}
revokeAgentSubmitting.value = true
try {
await revokeAgent(Number(id))
useMessage().success('已撤回招标代理')
await loadData()
} catch (e: any) {
useMessage().error(e?.msg || '撤回招标代理失败')
} finally {
revokeAgentSubmitting.value = false
}
}
/** 步骤一:保存实施采购方式 */
const handleSaveImplementType = async () => {
const id = applyRow.value?.id ?? applyId.value
if (!id) {
useMessage().warning('无法获取申请单ID')
return
}
if (!implementType.value) {
useMessage().warning('请选择实施采购方式')
return
}
saveTypeSubmitting.value = true
try {
await saveImplementType(Number(id), implementType.value)
useMessage().success('保存成功')
step1Completed.value = true
originalImplementType.value = implementType.value
// 流程嵌入场景:通知流程 Tab 已保存
if (isFlowEmbed.value && props.currJob && props.currElTab?.id) {
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emit)
}
// 如果是委托代理采购,加载代理列表
if (implementType.value === IMPLEMENT_TYPE.ENTRUST_AGENT) {
await loadAgentList()
}
} catch (e: any) {
useMessage().error(e?.msg || '保存失败')
} finally {
saveTypeSubmitting.value = false
}
}
/** 开始编辑步骤一 */
const startEditStep1 = () => {
originalImplementType.value = implementType.value
isEditingStep1.value = true
}
/** 取消编辑步骤一 */
const cancelEditStep1 = () => {
implementType.value = originalImplementType.value
isEditingStep1.value = false
}
/** 重新保存实施采购方式(修改后确认) */
const handleReSaveImplementType = async () => {
const id = applyRow.value?.id ?? applyId.value
if (!id) {
useMessage().warning('无法获取申请单ID')
return
}
if (!implementType.value) {
useMessage().warning('请选择实施采购方式')
return
}
saveTypeSubmitting.value = true
try {
await saveImplementType(Number(id), implementType.value)
useMessage().success('修改成功')
isEditingStep1.value = false
originalImplementType.value = implementType.value
// 如果从委托代理采购改为自行组织采购,清空代理相关状态
if (implementType.value === IMPLEMENT_TYPE.SELF_ORGANIZED) {
assignedAgentName.value = ''
selectedAgentId.value = ''
}
// 如果改为委托代理采购,加载代理列表
if (implementType.value === IMPLEMENT_TYPE.ENTRUST_AGENT) {
await loadAgentList()
}
// 流程嵌入场景:通知流程 Tab 已保存
if (isFlowEmbed.value && props.currJob && props.currElTab?.id) {
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emit)
}
} catch (e: any) {
useMessage().error(e?.msg || '修改失败')
} finally {
saveTypeSubmitting.value = false
}
}
const isInIframe = () => typeof window !== 'undefined' && window.self !== window.top const isInIframe = () => typeof window !== 'undefined' && window.self !== window.top
const postMessage = (type: string, payload?: any) => { const postMessage = (type: string, payload?: any) => {
@@ -304,7 +478,12 @@ const loadData = async () => {
applyRow.value = detailRes?.data ? { ...detailRes.data, id: detailRes.data.id ?? id } : { id } applyRow.value = detailRes?.data ? { ...detailRes.data, id: detailRes.data.id ?? id } : { id }
const row = applyRow.value const row = applyRow.value
if (row?.implementType) implementType.value = row.implementType if (row?.implementType) {
implementType.value = row.implementType
originalImplementType.value = row.implementType
// 已有 implementType 说明步骤一已完成
step1Completed.value = true
}
// 回显需求部门初审-采购代表人方式与人员 // 回显需求部门初审-采购代表人方式与人员
if (row?.representorTeacherNo) { if (row?.representorTeacherNo) {
representorMode.value = 'single' representorMode.value = 'single'
@@ -321,11 +500,18 @@ const loadData = async () => {
} }
deptMembers.value = needDeptMembers && membersRes?.data ? membersRes.data : [] deptMembers.value = needDeptMembers && membersRes?.data ? membersRes.data : []
// 加载代理列表并回显已分配代理 // 加载代理列表并回显已分配代理(委托代理采购时)
if (canAssignAgent.value) { if (step1Completed.value && implementType.value === IMPLEMENT_TYPE.ENTRUST_AGENT) {
// 先判断是否可以分配代理(学校统一采购 或 部门自行采购且委托采购中心采购)
const canLoadAgent = row.purchaseMode === PURCHASE_MODE.SCHOOL_UNIFIED
|| (row.purchaseMode === PURCHASE_MODE.DEPT_SELF && row.purchaseChannel === PURCHASE_CHANNEL.ENTRUST_CENTER)
if (canLoadAgent) {
await loadAgentList() await loadAgentList()
if (row?.agentName) { // 回显已分配代理
assignedAgentName.value = row.agentName if (row?.agentId) {
selectedAgentId.value = row.agentId
assignedAgentName.value = row.agentName || ''
}
} }
} }
} catch (_) { } catch (_) {
@@ -346,14 +532,28 @@ const handleImplementSubmit = async () => {
if (!row?.id && !applyId.value) return if (!row?.id && !applyId.value) return
const id = row?.id ?? applyId.value const id = row?.id ?? applyId.value
if (!id) return if (!id) return
// 步骤一未完成时,先保存步骤一
if (!step1Completed.value) {
if (!implementType.value) { if (!implementType.value) {
useMessage().warning('请选择实施采购方式') useMessage().warning('请选择实施采购方式')
return return
} }
// 自动保存步骤一
saveTypeSubmitting.value = true
try {
await saveImplementType(Number(id), implementType.value)
step1Completed.value = true
} catch (e: any) {
useMessage().error(e?.msg || '保存实施采购方式失败')
return
} finally {
saveTypeSubmitting.value = false
}
}
implementSubmitting.value = true implementSubmitting.value = true
try { try {
// TODO: 调用保存接口
useMessage().success('实施采购已保存') useMessage().success('实施采购已保存')
postMessage('purchasingimplement:saved') postMessage('purchasingimplement:saved')
// 流程嵌入场景:通知流程当前 Tab 已保存 // 流程嵌入场景:通知流程当前 Tab 已保存
@@ -367,7 +567,7 @@ const handleImplementSubmit = async () => {
} }
} }
/** 流程嵌入时供 handle.vue 调用的保存回调:与页面按钮保存逻辑保持一致 */ /** 流程嵌入时供 handle.vue 调用的"保存"回调:与页面按钮保存逻辑保持一致 */
async function flowSubmitForm() { async function flowSubmitForm() {
await handleImplementSubmit() await handleImplementSubmit()
} }
@@ -430,6 +630,47 @@ onUnmounted(() => {
flex-wrap: wrap; flex-wrap: wrap;
} }
.step-section {
margin-bottom: 24px;
padding: 16px;
background: var(--el-fill-color-lighter);
border-radius: 8px;
}
.step-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.step-number {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--el-color-primary);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
&.completed {
background: var(--el-color-success);
}
}
.step-title {
font-weight: 500;
font-size: 16px;
color: var(--el-text-color-primary);
}
.step-content {
padding-left: 40px;
}
.agent-roller { .agent-roller {
padding: 12px 16px; padding: 12px 16px;
background: var(--el-fill-color-light); background: var(--el-fill-color-light);