From 4167a2a94d07ccf9614c0de9beeed51171be95fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A2=E5=85=B5?= <374362909@qq.com> Date: Mon, 2 Mar 2026 10:06:48 +0800 Subject: [PATCH] rebuild all --- src/api/purchase/bidOpeningNotice.ts | 64 +++ src/api/purchase/docProcess.ts | 35 ++ src/api/purchase/purchasingrequisition.ts | 4 +- src/router/index.ts | 2 + src/router/route.ts | 8 + src/views/admin/system/menu/form.vue | 2 +- .../accept/AcceptCommonForm.vue | 12 + .../accept/PurchasingAcceptModal.vue | 3 + .../purchase/purchasingrequisition/add.vue | 2 +- .../docProcess/DocProcessDialog.vue | 339 ++++++++++++++++ .../docProcess/ReviewerSetting.vue | 367 ++++++++++++++++++ .../purchasingrequisition/implement.vue | 2 + 12 files changed, 836 insertions(+), 4 deletions(-) create mode 100644 src/api/purchase/bidOpeningNotice.ts create mode 100644 src/views/purchase/purchasingrequisition/docProcess/ReviewerSetting.vue diff --git a/src/api/purchase/bidOpeningNotice.ts b/src/api/purchase/bidOpeningNotice.ts new file mode 100644 index 0000000..694ed24 --- /dev/null +++ b/src/api/purchase/bidOpeningNotice.ts @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2025, cyweb All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + */ + +import request from '/@/utils/request'; + +/** + * 根据采购申请ID获取开标通知 + * @param applyId 采购申请ID + */ +export function getNoticeByApplyId(applyId: string | number) { + return request({ + url: '/purchase/bidopeningnotice/' + applyId, + method: 'get' + }); +} + +/** + * 保存开标通知(草稿) + * @param data 开标通知数据 + */ +export function saveNotice(data: any) { + return request({ + url: '/purchase/bidopeningnotice/save', + method: 'post', + data + }); +} + +/** + * 发布开标通知 + * @param data 开标通知数据 + */ +export function publishNotice(data: any) { + return request({ + url: '/purchase/bidopeningnotice/publish', + method: 'post', + data + }); +} + +/** + * 判断是否可以发送开标通知 + * @param applyId 采购申请ID + */ +export function canSendNotice(applyId: string | number) { + return request({ + url: '/purchase/bidopeningnotice/can-send/' + applyId, + method: 'get' + }); +} \ No newline at end of file diff --git a/src/api/purchase/docProcess.ts b/src/api/purchase/docProcess.ts index 49936f7..c5ecca2 100644 --- a/src/api/purchase/docProcess.ts +++ b/src/api/purchase/docProcess.ts @@ -271,4 +271,39 @@ export function finalizeDoc(data: any) { method: 'post', data }); +} + +/** + * 获取采购代表设置信息 + * @param applyId 采购申请ID + */ +export function getReviewerSetting(applyId: string | number) { + return request({ + url: `/purchase/purchasingdoc/reviewer/${applyId}`, + method: 'get' + }); +} + +/** + * 设置采购代表 + * @param data 设置信息 + */ +export function setReviewerSetting(data: any) { + return request({ + url: '/purchase/purchasingdoc/reviewer/set', + method: 'post', + data + }); +} + +/** + * 随机抽取采购代表 + * @param data 候选人列表 + */ +export function randomSelectReviewer(data: any) { + return request({ + url: '/purchase/purchasingdoc/reviewer/random', + method: 'post', + data + }); } \ No newline at end of file diff --git a/src/api/purchase/purchasingrequisition.ts b/src/api/purchase/purchasingrequisition.ts index 5e05784..5484b8c 100644 --- a/src/api/purchase/purchasingrequisition.ts +++ b/src/api/purchase/purchasingrequisition.ts @@ -33,7 +33,7 @@ export function getPage(params?: any) { * 通过id查询 * @param id ID */ -export function getObj(id: number) { +export function getObj(id: string | number) { return request({ url: '/purchase/purchasingapply/' + id, method: 'get' @@ -123,7 +123,7 @@ export function saveImplementType(id: number | string, implementType: string) { return request({ url: '/purchase/purchasingapply/save-implement-type', method: 'post', - data: { id: Number(id), implementType } + data: { id: id, implementType } }); } diff --git a/src/router/index.ts b/src/router/index.ts index 9f64543..d63d94f 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -11,6 +11,7 @@ import { staticRoutes, notFoundAndNoPower } from '/@/router/route'; import { initBackEndControlRoutes } from '/@/router/backEnd'; import { flowConfig } from "/@/flow/designer/config/flow-config"; import { replaceRouterRoute } from "/@/flow/support/extend"; +import { NextLoading } from '/@/utils/loading'; /** * 1、前端控制路由时:isRequestRoutes 为 false,需要写 roles,需要走 setFilterRoute 方法。 @@ -148,6 +149,7 @@ router.beforeEach(async (to, from, next) => { // 路由加载后 router.afterEach(() => { NProgress.done(); + NextLoading.done(); }); // 导出路由 diff --git a/src/router/route.ts b/src/router/route.ts index 82f9a8a..59983f9 100644 --- a/src/router/route.ts +++ b/src/router/route.ts @@ -99,6 +99,14 @@ export const staticRoutes: Array = [ isAuth: true, }, }, + { + path: '/purchase/purchasingrequisition/add', + name: 'purchase.purchasingrequisition.add', + component: () => import('/@/views/purchase/purchasingrequisition/add.vue'), + meta: { + isAuth: true, + }, + }, ...staticRoutesFlow ]; diff --git a/src/views/admin/system/menu/form.vue b/src/views/admin/system/menu/form.vue index d5a5f42..5d3b9fa 100644 --- a/src/views/admin/system/menu/form.vue +++ b/src/views/admin/system/menu/form.vue @@ -40,7 +40,7 @@ {{ t('sysmenu.permission') }} - + diff --git a/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue b/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue index d7aaabf..020c586 100644 --- a/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue +++ b/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue @@ -72,6 +72,17 @@ + + + + + @@ -113,6 +124,7 @@ const form = reactive({ purchaserName: '', assetAdminId: '', assetAdminName: '', + transactionAmount: null, ...props.modelValue, }) diff --git a/src/views/purchase/purchasingrequisition/accept/PurchasingAcceptModal.vue b/src/views/purchase/purchasingrequisition/accept/PurchasingAcceptModal.vue index f5baa16..f36d557 100644 --- a/src/views/purchase/purchasingrequisition/accept/PurchasingAcceptModal.vue +++ b/src/views/purchase/purchasingrequisition/accept/PurchasingAcceptModal.vue @@ -186,6 +186,7 @@ const loadData = async () => { totalPhases: config.common.totalPhases || 1, supplierName: config.common.supplierName || '', supplierContact: config.common.supplierContact || '', + transactionAmount: config.common.transactionAmount || null, }) } } @@ -275,6 +276,7 @@ const saveCommonConfig = async () => { purchaserName: String(form.purchaserName ?? ''), assetAdminId: String(form.assetAdminId ?? ''), assetAdminName: String(form.assetAdminName ?? ''), + transactionAmount: form.transactionAmount ?? null, }) useMessage().success('保存成功') await loadData() @@ -353,6 +355,7 @@ const DEFAULT_COMMON_FORM = { purchaserName: '', assetAdminId: '', assetAdminName: '', + transactionAmount: null, } /** 将弹窗内所有内容恢复为初始空值(替换整个对象以确保引用变化) */ diff --git a/src/views/purchase/purchasingrequisition/add.vue b/src/views/purchase/purchasingrequisition/add.vue index 07a424b..d348954 100644 --- a/src/views/purchase/purchasingrequisition/add.vue +++ b/src/views/purchase/purchasingrequisition/add.vue @@ -1204,7 +1204,7 @@ const handleCancel = () => { async function loadDetail(applyId: string | number) { if (!applyId) return; try { - const res = await getObj(Number(applyId)); + const res = await getObj(String(applyId)); const detail = res?.data; if (detail && typeof detail === 'object') { Object.assign(dataForm, { diff --git a/src/views/purchase/purchasingrequisition/docProcess/DocProcessDialog.vue b/src/views/purchase/purchasingrequisition/docProcess/DocProcessDialog.vue index 09e5b37..a6c9305 100644 --- a/src/views/purchase/purchasingrequisition/docProcess/DocProcessDialog.vue +++ b/src/views/purchase/purchasingrequisition/docProcess/DocProcessDialog.vue @@ -127,6 +127,119 @@ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 上传文件 + +
+ + 已上传 + 查看 +
+
+
+ + + + 上传文件 + +
+ + 已上传 + 查看 +
+
+
+
+ + + + + + + + + + 保存草稿 + 发布通知 + + +
+ + + + +
+
@@ -244,9 +357,17 @@ import { submitToAsset as submitToAssetApi, finalizeDoc as finalizeDocApi } from '/@/api/purchase/docProcess' +import { + getNoticeByApplyId, + saveNotice, + publishNotice, + canSendNotice +} from '/@/api/purchase/bidOpeningNotice' import type { UploadInstance, UploadProps, UploadUserFile } from 'element-plus' +import { Document } from '@element-plus/icons-vue' const AuditRecordList = defineAsyncComponent(() => import('./AuditRecordList.vue')) +const ReviewerSetting = defineAsyncComponent(() => import('./ReviewerSetting.vue')) const props = defineProps<{ mode: 'agent' | 'audit' @@ -297,6 +418,33 @@ const supplyUploadForm = ref({ fileRemark: '' }) +// 开标通知相关 +const bidOpeningFormRef = ref() +const bidOpeningLoading = ref(false) +const bidOpeningSaving = ref(false) +const bidOpeningPublishing = ref(false) +const bidOpeningNoticeStatus = ref('') +const bidOpeningForm = ref({ + id: '', + applyId: '', + projectName: '', + openTime: '', + openLocation: '', + agentContactName: '', + agentContactPhone: '', + publicUrl: '', + bidConfirmationLetter: '', + authorizationLetter: '', + remarks: '', + publishTime: '' +}) +const bidOpeningRules = { + openTime: [{ required: true, message: '请选择开标时间', trigger: 'change' }], + openLocation: [{ required: true, message: '请输入开标地点', trigger: 'blur' }], + agentContactName: [{ required: true, message: '请输入代理联系人姓名', trigger: 'blur' }], + agentContactPhone: [{ required: true, message: '请输入代理联系人电话', trigger: 'blur' }] +} + // 弹窗标题 const dialogTitle = computed(() => { return props.mode === 'agent' ? `处理项目 - ${rowData.value.purchaseNo || ''}` : '招标文件审核' @@ -336,6 +484,12 @@ const isReviewing = computed(() => ['ASSET_REVIEWING', 'DEPT_REVIEWING', 'AUDIT_ const isConfirming = computed(() => statusField.value === 'ASSET_CONFIRMING') const isCompleted = computed(() => statusField.value === 'COMPLETED') +// 是否显示开标通知Tab(招标代理模式且状态为已完成) +const showBidOpeningNotice = computed(() => props.mode === 'agent' && isCompleted.value) + +// 是否显示采购代表设置Tab(需求部门审核中且有提交权限) +const canSetReviewer = computed(() => statusField.value === 'DEPT_REVIEWING' && availableActions.value.includes('submitToAsset')) + // 上传配置 const uploadAction = computed(() => { const baseUrl = import.meta.env.VITE_API_URL || '' @@ -365,6 +519,9 @@ const open = async (row: any) => { fileList.value = [] uploadedFileData.value = null + // 重置开标通知表单 + resetBidOpeningForm() + // 获取申请ID(兼容 id 和 applyId 两种字段名) applyId.value = row.applyId || row.id @@ -380,6 +537,10 @@ const open = async (row: any) => { loadRequirementFiles() // 加载招标文件 loadDocList() + // 加载开标通知(如果是招标代理模式且状态为已完成) + if (props.mode === 'agent') { + loadBidOpeningNotice() + } } const loadRequirementFiles = async () => { @@ -801,6 +962,154 @@ const submitFinalize = async () => { } } +// ==================== 开标通知相关方法 ==================== + +/** + * 重置开标通知表单 + */ +const resetBidOpeningForm = () => { + bidOpeningForm.value = { + id: '', + applyId: '', + projectName: '', + openTime: '', + openLocation: '', + agentContactName: '', + agentContactPhone: '', + publicUrl: '', + bidConfirmationLetter: '', + authorizationLetter: '', + remarks: '', + publishTime: '' + } + bidOpeningNoticeStatus.value = '' +} + +/** + * 加载开标通知 + */ +const loadBidOpeningNotice = async () => { + if (!applyId.value) return + bidOpeningLoading.value = true + try { + const res = await getNoticeByApplyId(applyId.value) + if (res?.code === 0 || res?.code === 200) { + const data = res.data + if (data) { + bidOpeningForm.value = { + id: data.id || '', + applyId: data.applyId || applyId.value, + projectName: data.projectName || rowData.value.projectName || '', + openTime: data.openTime || '', + openLocation: data.openLocation || '', + agentContactName: data.agentContactName || '', + agentContactPhone: data.agentContactPhone || '', + publicUrl: data.publicUrl || '', + bidConfirmationLetter: data.bidConfirmationLetter || '', + authorizationLetter: data.authorizationLetter || '', + remarks: data.remarks || '', + publishTime: data.publishTime || '' + } + bidOpeningNoticeStatus.value = data.status || '' + } else { + bidOpeningForm.value.applyId = applyId.value + bidOpeningForm.value.projectName = rowData.value.projectName || '' + } + } + } catch (e) { + // 忽略错误,可能是第一次创建 + } finally { + bidOpeningLoading.value = false + } +} + +/** + * 开标通知文件上传成功回调 + */ +const handleBidFileSuccess = (response: any, file: any, field: string) => { + if (response?.code === 0 || response?.code === 200) { + bidOpeningForm.value[field] = response.data.remark || response.data.filePath + useMessage().success('文件上传成功') + } else { + useMessage().error(response?.msg || '文件上传失败') + } +} + +/** + * 查看文件 + */ +const viewFile = (url: string) => { + if (url) { + window.open(url, '_blank') + } +} + +/** + * 保存开标通知草稿 + */ +const handleSaveBidOpeningDraft = async () => { + try { + await bidOpeningFormRef.value?.validate() + } catch { + useMessage().warning('请填写必填项') + return + } + + bidOpeningSaving.value = true + try { + const res = await saveNotice({ + ...bidOpeningForm.value, + applyId: applyId.value + }) + if (res?.code === 0 || res?.code === 200) { + useMessage().success('保存成功') + await loadBidOpeningNotice() + } else { + useMessage().error(res?.msg || '保存失败') + } + } catch (e: any) { + useMessage().error(e?.msg || '保存失败') + } finally { + bidOpeningSaving.value = false + } +} + +/** + * 发布开标通知 + */ +const handlePublishBidOpening = async () => { + try { + await bidOpeningFormRef.value?.validate() + } catch { + useMessage().warning('请填写必填项') + return + } + + try { + await useMessageBox().confirm('确定要发布开标通知吗?发布后将无法修改。') + } catch { + return + } + + bidOpeningPublishing.value = true + try { + const res = await publishNotice({ + ...bidOpeningForm.value, + applyId: applyId.value + }) + if (res?.code === 0 || res?.code === 200) { + useMessage().success('发布成功') + await loadBidOpeningNotice() + } else { + useMessage().error(res?.msg || '发布失败') + } + } catch (e: any) { + useMessage().error(e?.msg || '发布失败') + } finally { + bidOpeningPublishing.value = false + } +} + const handleClose = () => { visible.value = false } @@ -841,6 +1150,11 @@ const getFileTypeLabel = (type: string) => { return labelMap[type] || type } +// 采购代表设置保存成功回调 +const handleReviewerSaved = () => { + emit('refresh') +} + defineExpose({ open }) @@ -868,4 +1182,29 @@ defineExpose({ open }) .mt-4 { margin-top: 16px; } + +.bid-opening-form { + padding: 20px; + + .file-info { + display: flex; + align-items: center; + gap: 8px; + margin-top: 8px; + color: #606266; + + .el-icon { + font-size: 16px; + } + } + + .form-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #ebeef5; + } +} \ No newline at end of file diff --git a/src/views/purchase/purchasingrequisition/docProcess/ReviewerSetting.vue b/src/views/purchase/purchasingrequisition/docProcess/ReviewerSetting.vue new file mode 100644 index 0000000..763e894 --- /dev/null +++ b/src/views/purchase/purchasingrequisition/docProcess/ReviewerSetting.vue @@ -0,0 +1,367 @@ + + + + + \ No newline at end of file diff --git a/src/views/purchase/purchasingrequisition/implement.vue b/src/views/purchase/purchasingrequisition/implement.vue index a6081d9..b0b20dd 100644 --- a/src/views/purchase/purchasingrequisition/implement.vue +++ b/src/views/purchase/purchasingrequisition/implement.vue @@ -316,6 +316,7 @@ const handleRevokeAgent = async () => { const handleSaveImplementType = async () => { const id = applyRow.value?.id ?? applyId.value + if (!id) { useMessage().warning('无法获取申请单ID') return @@ -326,6 +327,7 @@ const handleSaveImplementType = async () => { } saveTypeSubmitting.value = true try { + await saveImplementType(id, implementType.value) useMessage().success('保存成功') step1Completed.value = true