From 2169fec9c12b7a90982a213078761340dd75746c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A6=82=E5=88=9D?= <3236758982@qq.com> Date: Sat, 23 May 2026 16:42:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9D=8E=E7=BA=A2=E6=94=80=EF=BC=9AV2.0?= =?UTF-8?q?.008=E9=87=87=E8=B4=AD=E5=85=A5=E5=BA=93=E5=BA=93=E4=BD=8D?= =?UTF-8?q?=E6=8E=A8=E8=8D=90=E7=9A=84=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- env/.env | 6 +- manifest.config.ts | 44 +-- package.json | 4 - pnpm-lock.yaml | 9 +- src/api/erp/approval/index.ts | 82 +++++ src/api/erp/purchase-in/index.ts | 2 + src/pages-erp/purchase-in/detail/index.vue | 50 ++- src/pages-erp/purchase-in/form/index.vue | 246 +++++++++++-- src/pages-erp/purchase-in/index.vue | 400 +++++++++++++++++++-- src/pages/index/index.ts | 124 +++---- 10 files changed, 802 insertions(+), 165 deletions(-) create mode 100644 src/api/erp/approval/index.ts diff --git a/env/.env b/env/.env index dec65b6..8fdc597 100644 --- a/env/.env +++ b/env/.env @@ -1,7 +1,7 @@ VITE_APP_TITLE = '亚为mom小程序' VITE_APP_PORT = 9000 -VITE_UNI_APPID = '__UNI__7CADA36' +VITE_UNI_APPID = '__UNI__2EB12DE' VITE_WX_APPID = 'wx1a832d51073d3a35' # h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base @@ -10,8 +10,8 @@ VITE_WX_APPID = 'wx1a832d51073d3a35' VITE_APP_PUBLIC_BASE=/ # 后台请求地址 -VITE_SERVER_BASEURL = 'http://192.168.1.20:48080/admin-api' -VITE_UPLOAD_BASEURL = 'http://192.168.1.20:48080/upload' +VITE_SERVER_BASEURL = 'http://127.0.0.1:48080/admin-api' +VITE_UPLOAD_BASEURL = 'http://127.0.0.1:48080/upload' # 备注:如果后台带统一前缀,则也要加到后面,eg: https://ukw0y1.laf.run/api # 注意,如果是微信小程序,还有一套请求地址的配置,根据 develop、trial、release 分别设置上传地址,见 `src/utils/index.ts`。 diff --git a/manifest.config.ts b/manifest.config.ts index b420eb2..10e4baa 100644 --- a/manifest.config.ts +++ b/manifest.config.ts @@ -82,33 +82,33 @@ export default defineManifestConfig({ /* 图标配置 */ icons: { android: { - hdpi: 'static/app/icons/72x72.png', - xhdpi: 'static/app/icons/96x96.png', - xxhdpi: 'static/app/icons/144x144.png', - xxxhdpi: 'static/app/icons/192x192.png', + hdpi: 'src/static/app/icons/72x72.png', + xhdpi: 'src/static/app/icons/96x96.png', + xxhdpi: 'src/static/app/icons/144x144.png', + xxxhdpi: 'src/static/app/icons/192x192.png', }, ios: { - appstore: 'static/app/icons/1024x1024.png', + appstore: 'src/static/app/icons/1024x1024.png', ipad: { - 'app': 'static/app/icons/76x76.png', - 'app@2x': 'static/app/icons/152x152.png', - 'notification': 'static/app/icons/20x20.png', - 'notification@2x': 'static/app/icons/40x40.png', - 'proapp@2x': 'static/app/icons/167x167.png', - 'settings': 'static/app/icons/29x29.png', - 'settings@2x': 'static/app/icons/58x58.png', - 'spotlight': 'static/app/icons/40x40.png', - 'spotlight@2x': 'static/app/icons/80x80.png', + 'app': 'src/static/app/icons/76x76.png', + 'app@2x': 'src/static/app/icons/152x152.png', + 'notification': 'src/static/app/icons/20x20.png', + 'notification@2x': 'src/static/app/icons/40x40.png', + 'proapp@2x': 'src/static/app/icons/167x167.png', + 'settings': 'src/static/app/icons/29x29.png', + 'settings@2x': 'src/static/app/icons/58x58.png', + 'spotlight': 'src/static/app/icons/40x40.png', + 'spotlight@2x': 'src/static/app/icons/80x80.png', }, iphone: { - 'app@2x': 'static/app/icons/120x120.png', - 'app@3x': 'static/app/icons/180x180.png', - 'notification@2x': 'static/app/icons/40x40.png', - 'notification@3x': 'static/app/icons/60x60.png', - 'settings@2x': 'static/app/icons/58x58.png', - 'settings@3x': 'static/app/icons/87x87.png', - 'spotlight@2x': 'static/app/icons/80x80.png', - 'spotlight@3x': 'static/app/icons/120x120.png', + 'app@2x': 'src/static/app/icons/120x120.png', + 'app@3x': 'src/static/app/icons/180x180.png', + 'notification@2x': 'src/static/app/icons/40x40.png', + 'notification@3x': 'src/static/app/icons/60x60.png', + 'settings@2x': 'src/static/app/icons/58x58.png', + 'settings@3x': 'src/static/app/icons/87x87.png', + 'spotlight@2x': 'src/static/app/icons/80x80.png', + 'spotlight@3x': 'src/static/app/icons/120x120.png', }, }, }, diff --git a/package.json b/package.json index a378270..f1ee107 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,6 @@ "init-husky": "git init && husky", "init-baseFiles": "node ./scripts/create-base-files.js", "init-json": "pnpm init-baseFiles", - "prepare": "pnpm init-husky & pnpm init-baseFiles", "lint": "eslint", "lint:fix": "eslint --fix" }, @@ -180,9 +179,6 @@ "pnpm": { "overrides": { "unconfig": "7.3.2" - }, - "patchedDependencies": { - "wot-design-uni@1.13.0": "patches/wot-design-uni@1.13.0.patch" } }, "overrides": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed78561..565b131 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,11 +8,6 @@ overrides: bin-wrapper: npm:bin-wrapper-china unconfig: 7.3.2 -patchedDependencies: - wot-design-uni@1.13.0: - hash: 29ab7840a74d54969d4cbe5b840208d3d4911a82be189ac8581d462190fb5210 - path: patches/wot-design-uni@1.13.0.patch - importers: .: @@ -94,7 +89,7 @@ importers: version: 4.5.1(vue@3.4.21(typescript@5.8.3)) wot-design-uni: specifier: ^1.13.0 - version: 1.13.0(patch_hash=29ab7840a74d54969d4cbe5b840208d3d4911a82be189ac8581d462190fb5210)(vue@3.4.21(typescript@5.8.3)) + version: 1.13.0(vue@3.4.21(typescript@5.8.3)) z-paging: specifier: 2.8.7 version: 2.8.7 @@ -13321,7 +13316,7 @@ snapshots: word-wrap@1.2.5: {} - wot-design-uni@1.13.0(patch_hash=29ab7840a74d54969d4cbe5b840208d3d4911a82be189ac8581d462190fb5210)(vue@3.4.21(typescript@5.8.3)): + wot-design-uni@1.13.0(vue@3.4.21(typescript@5.8.3)): dependencies: vue: 3.4.21(typescript@5.8.3) diff --git a/src/api/erp/approval/index.ts b/src/api/erp/approval/index.ts new file mode 100644 index 0000000..8cf1922 --- /dev/null +++ b/src/api/erp/approval/index.ts @@ -0,0 +1,82 @@ +import { http } from '@/http/http' + +/** 审批记录 */ +export interface ApprovalRecord { + id?: number + processInstanceId: string + bizId: string + bizTableName: string + applicant: string + applicantName?: string + approver: string + approverName?: string + assigner?: string + assignerName?: string + assignReason?: string + approvalTime?: string + comment?: string + approvalResult: number + flowType: number + parentApprovalId?: number + approvalLevel: number + processStatus: number + sort?: number + createTime?: string + updateTime?: string +} + +/** 提交审批参数 */ +export interface ApprovalSubmitVO { + bizId: string + bizTableName: string + nextApprover: string + remark?: string +} + +/** 处理审批参数 */ +export interface ApprovalProcessVO { + id: number + approvalResult: number + comment?: string + nextApprover?: string + assignReason?: string +} + +/** 审批结果枚举 */ +export const APPROVAL_RESULT = { + PENDING: 0, // 待审批 + APPROVED: 1, // 通过 + REJECTED: 2, // 拒绝 + TRANSFERRED: 3, // 转办 +} + +/** 审批结果选项 */ +export const APPROVAL_RESULT_OPTIONS = [ + { label: '通过', value: APPROVAL_RESULT.APPROVED }, + { label: '拒绝', value: APPROVAL_RESULT.REJECTED }, +] + +/** 提交审批 */ +export function submitApproval(data: ApprovalSubmitVO) { + return http.post('/erp/approval-record/submit', data) +} + +/** 处理审批 */ +export function processApproval(data: ApprovalProcessVO) { + return http.post('/erp/approval-record/process', data) +} + +/** 获取业务单据的审批记录列表 */ +export function getApprovalRecordListByBiz(bizId: string, bizTableName: string) { + return http.get('/erp/approval-record/list-by-biz', { bizId, bizTableName }) +} + +/** 获取用户的待办审批列表 */ +export function getPendingApprovalList(approver: string) { + return http.get('/erp/approval-record/pending-list', { approver }) +} + +/** 终止审批流程 */ +export function terminateApproval(bizId: string, bizTableName: string) { + return http.post('/erp/approval-record/terminate', { bizId, bizTableName }) +} diff --git a/src/api/erp/purchase-in/index.ts b/src/api/erp/purchase-in/index.ts index 418029e..20f12c3 100644 --- a/src/api/erp/purchase-in/index.ts +++ b/src/api/erp/purchase-in/index.ts @@ -22,6 +22,7 @@ export interface PurchaseInItem { productCategoryName?: string stockCount?: number totalCount?: number + inCount?: number totalPrice?: number totalProductPrice?: number orderItemId?: number @@ -57,6 +58,7 @@ export interface PurchaseIn { isQualified?: boolean returnType?: string | null returnRemark?: string + hasApprovalRecords?: boolean } /** 审核表单数据 */ diff --git a/src/pages-erp/purchase-in/detail/index.vue b/src/pages-erp/purchase-in/detail/index.vue index 9ed6126..bd7c38c 100644 --- a/src/pages-erp/purchase-in/detail/index.vue +++ b/src/pages-erp/purchase-in/detail/index.vue @@ -74,9 +74,17 @@ {{ item.productName || '-' }} {{ item.productBarCode }} - {{ item.warehouseName }} + + {{ item.warehouseName }} + [{{ item.locationCode }}] + + + + 批次号: + {{ item.batchNo }} + 规格: @@ -170,15 +178,19 @@ - + 采购入库审核 - 是否合格 + + 是否合格 + - 返回方式 + + 返回方式 + - 返回备注 + + 返回备注 + - + 取消 @@ -246,23 +260,30 @@ const auditForm = reactive({ /** 获取状态文本 */ function getStatusText(status?: number, isQualified?: boolean) { - if (status === 10) return '未审核' - if (status === 20) return isQualified ? '已审核' : '不合格' - if (status === 30) return '不合格' + if (status === 10) + return '未审核' + if (status === 20) + return isQualified ? '已审核' : '不合格' + if (status === 30) + return '不合格' return '未知' } /** 获取状态样式 */ function getStatusClass(status?: number, isQualified?: boolean) { - if (status === 10) return 'bg-[#fff7e6] text-[#fa8c16]' - if (status === 20) return isQualified ? 'bg-[#f6ffed] text-[#52c41a]' : 'bg-[#fff1f0] text-[#f5222d]' - if (status === 30) return 'bg-[#fff1f0] text-[#f5222d]' + if (status === 10) + return 'bg-[#fff7e6] text-[#fa8c16]' + if (status === 20) + return isQualified ? 'bg-[#f6ffed] text-[#52c41a]' : 'bg-[#fff1f0] text-[#f5222d]' + if (status === 30) + return 'bg-[#fff1f0] text-[#f5222d]' return 'bg-[#f5f5f5] text-[#999]' } /** 获取返回方式标签 */ function getReturnTypeLabel(value?: string | null) { - if (!value) return '-' + if (!value) + return '-' const option = RETURN_TYPE_OPTIONS.find(opt => opt.value === value) return option ? option.label : value } @@ -310,7 +331,8 @@ function handleQualifiedChange(val: boolean) { /** 提交审核 */ async function submitAudit() { - if (!props.id) return + if (!props.id) + return if (!auditForm.isQualified && !auditForm.returnType) { toast.warning('请选择返回方式') return diff --git a/src/pages-erp/purchase-in/form/index.vue b/src/pages-erp/purchase-in/form/index.vue index 5d98139..e3af801 100644 --- a/src/pages-erp/purchase-in/form/index.vue +++ b/src/pages-erp/purchase-in/form/index.vue @@ -26,7 +26,7 @@ placeholder="请选择入库时间" /> - + {{ formData.orderNo }} 点击选择采购订单 @@ -97,12 +97,25 @@ > 产品 #{{ index + 1 }} {{ item.productName || '' }} - - 删除 - + + + 推荐货位 + + + 拆分 + + + 删除 + + @@ -115,6 +128,24 @@ @confirm="(e: any) => onWarehouseConfirm(e, index)" /> + + + 库位编码 + + + + + 批次号 + + @@ -232,7 +263,9 @@ > - {{ item.no }} + + {{ item.no }} + @@ -241,20 +274,32 @@ 产品 - {{ item.productNames || '-' }} + {{ item.productNames || '-' }} - + - {{ item.totalCount || 0 }} - 总数量 + + {{ item.totalCount || 0 }} + + + 总数量 + - {{ item.inCount || 0 }} - 已入库 + + {{ item.inCount || 0 }} + + + 已入库 + - ¥{{ item.totalPrice || 0 }} - 含税金额 + + ¥{{ item.totalPrice || 0 }} + + + 含税金额 + @@ -301,6 +346,9 @@ interface WarehouseSimple { id: number name: string defaultStatus?: boolean + enableLocation?: boolean + roomNoMax?: number + locationCapacity?: number } /** 供应商简单信息 */ @@ -370,9 +418,19 @@ const warehouseColumns = computed(() => [ warehouseList.value.map(w => ({ value: w.id, label: w.name })), ]) +/** 判断仓库是否启用库位管理 */ +function isLocationEnabled(warehouseId?: number): boolean { + if (!warehouseId) + return false + const warehouse = warehouseList.value.find(w => w.id === warehouseId) + console.log('[库位推荐] warehouseId:', warehouseId, 'warehouse:', warehouse, 'enableLocation:', warehouse?.enableLocation) + return warehouse?.enableLocation === true +} + /** 获取供应商名称 */ function getSupplierName() { - if (!formData.value.supplierId) return '' + if (!formData.value.supplierId) + return '' const supplier = supplierList.value.find(s => s.id === formData.value.supplierId) return supplier?.name || formData.value.supplierName || '' } @@ -383,12 +441,21 @@ function onAccountConfirm({ value }: any) { } /** 仓库选择回调 */ -function onWarehouseConfirm({ value }: any, index: number) { +function onWarehouseConfirm(e: any, index: number) { + console.log('[仓库选择] 完整事件:', e, 'index:', index) if (formData.value.items && formData.value.items[index]) { - formData.value.items[index].warehouseId = value?.[0] - const warehouse = warehouseList.value.find(w => w.id === value?.[0]) + // wd-picker 的 confirm 事件返回 { value, selectedItems } + const warehouseId = e?.value?.[0] ?? e?.selectedItems?.[0]?.value ?? formData.value.items[index].warehouseId + console.log('[仓库选择] 解析后 warehouseId:', warehouseId) + formData.value.items[index].warehouseId = warehouseId + const warehouse = warehouseList.value.find(w => w.id === warehouseId) + console.log('[仓库选择] 找到仓库:', warehouse) if (warehouse) { formData.value.items[index].warehouseName = warehouse.name + // 如果仓库未启用库位管理,清空库位编码 + if (!warehouse.enableLocation) { + formData.value.items[index].locationCode = undefined + } } } } @@ -407,7 +474,8 @@ function calcTotalPrice(item: PurchaseInItem) { watch( () => formData.value, (val) => { - if (!val || !val.items) return + if (!val || !val.items) + return // 计算每个明细的金额 val.items.forEach((item) => { if (item.productPrice && item.count) { @@ -437,6 +505,7 @@ async function loadDropdownData() { supplierList.value = suppliers || [] accountList.value = accounts || [] warehouseList.value = warehouses || [] + console.log('[仓库数据] warehouseList:', warehouseList.value) // 设置默认账户 const defaultAccount = accountList.value.find(a => a.defaultStatus) if (defaultAccount && !formData.value.accountId) { @@ -467,6 +536,135 @@ function handleRemoveItem(index: number) { }) } +/** 拆分明细行:复制当前产品行为一条新行,用于“同一产品分多个库位”录入 */ +function handleSplit(index: number) { + const sourceItem = formData.value.items?.[index] + if (!sourceItem) + return + + const newItem: PurchaseInItem = { + ...sourceItem, + id: undefined, + locationCode: sourceItem.locationCode, + batchNo: sourceItem.batchNo, + count: sourceItem.count, + totalProductPrice: undefined, + taxPrice: undefined, + totalPrice: undefined, + remark: undefined, + } + formData.value.items?.splice(index + 1, 0, newItem) + toast.success('已拆分,请调整数量和库位') +} + +/** 推荐货位 */ +async function handleRecommendLocation(item: PurchaseInItem, index: number) { + console.log('[推荐货位] 开始, item:', item, 'index:', index) + if (!item.warehouseId) { + toast.warning('请先选择仓库') + return + } + if (!item.productId) { + toast.warning('请先选择产品') + return + } + if (!item.count || Number(item.count) <= 0) { + toast.warning('请先填写有效数量') + return + } + + const warehouse = warehouseList.value.find(w => w.id === item.warehouseId) + console.log('[推荐货位] 仓库:', warehouse, 'locationCapacity:', warehouse?.locationCapacity) + const locationCapacity = warehouse?.locationCapacity || 0 + if (locationCapacity <= 0) { + toast.warning('当前仓库未配置单货位容量,无法推荐货位') + return + } + + try { + toast.loading('正在推荐货位...') + const { http } = await import('@/http/http') + const totalCount = Number(item.count) + const requiredLocationCount = Math.ceil(totalCount / locationCapacity) + + const recommendList = await http.get('/erp/location/recommend-inbound', { + productId: item.productId, + warehouseId: item.warehouseId, + count: totalCount, + topN: requiredLocationCount, + }) + + if (!recommendList || recommendList.length === 0) { + toast.warning('当前没有可推荐的空货位,请手工选择') + return + } + + // 单个货位直接回填 + if (recommendList.length === 1) { + item.locationCode = recommendList[0] + toast.success(`已推荐货位: ${recommendList[0]}`) + return + } + + // 多个货位,提示用户选择或自动拆分 + uni.showModal({ + title: '推荐货位', + content: `需要 ${requiredLocationCount} 个货位,推荐: ${recommendList.join(', ')}\n是否自动拆分并分配?`, + confirmText: '自动拆分', + cancelText: '手动填写', + success: (res) => { + if (res.confirm) { + // 自动拆分 + applyAutoSplit(index, recommendList, locationCapacity) + } else { + // 只填写第一个货位 + item.locationCode = recommendList[0] + toast.info(`已填写第一个货位: ${recommendList[0]},请手动拆分其他`) + } + }, + }) + } catch (error) { + console.error('推荐货位失败', error) + } finally { + toast.close() + } +} + +/** 自动拆分并分配货位 */ +function applyAutoSplit(index: number, locationCodes: string[], locationCapacity: number) { + const sourceItem = formData.value.items?.[index] + if (!sourceItem) + return + + const totalCount = Number(sourceItem.count) + let remaining = totalCount + + // 更新第一行 + const firstCount = Math.min(remaining, locationCapacity) + sourceItem.locationCode = locationCodes[0] + sourceItem.count = Number(firstCount.toFixed(4)) + remaining = Number((remaining - firstCount).toFixed(4)) + + // 插入后续行 + for (let i = 1; i < locationCodes.length && remaining > 0; i++) { + const currentCount = Math.min(remaining, locationCapacity) + const newItem: PurchaseInItem = { + ...sourceItem, + id: undefined, + locationCode: locationCodes[i], + count: Number(currentCount.toFixed(4)), + totalProductPrice: undefined, + taxPrice: undefined, + totalPrice: undefined, + remark: undefined, + } + formData.value.items?.splice(index + i, 0, newItem) + remaining = Number((remaining - currentCount).toFixed(4)) + } + + toast.success(`已自动拆分为 ${locationCodes.length} 个货位`) +} + /** 打开订单选择弹窗 */ function openOrderSelect() { orderSelectVisible.value = true @@ -495,7 +693,8 @@ async function getOrderList() { /** 加载更多订单 */ function loadMoreOrders() { - if (orderLoadMoreState.value === 'finished') return + if (orderLoadMoreState.value === 'finished') + return orderQueryParams.value.pageNo++ getOrderList() } @@ -507,7 +706,8 @@ function handleSelectOrder(item: PurchaseOrder) { /** 确认选择订单 */ async function confirmSelectOrder() { - if (!selectedOrderId.value) return + if (!selectedOrderId.value) + return try { toast.loading('加载订单详情...') const orderDetail = await getPurchaseOrder(selectedOrderId.value) diff --git a/src/pages-erp/purchase-in/index.vue b/src/pages-erp/purchase-in/index.vue index 810b459..391b003 100644 --- a/src/pages-erp/purchase-in/index.vue +++ b/src/pages-erp/purchase-in/index.vue @@ -11,18 +11,18 @@ - + {{ tab.label }} @@ -56,7 +56,7 @@ 产品 - {{ item.productNames || '-' }} + {{ item.productNames || '-' }} @@ -64,23 +64,40 @@ {{ formatDate(item.inTime) }} - + 创建人 {{ item.creatorName || '-' }} + + + 批次号 + {{ formatItemBatchNos(item) || '-' }} + - + - {{ item.totalCount || 0 }} - 总数量 + + {{ item.totalCount || 0 }} + + + 总数量 + - ¥{{ item.totalPrice || 0 }} - 应付 + + ¥{{ item.totalPrice || 0 }} + + + 应付 + - ¥{{ item.paymentPrice || 0 }} - 已付 + + ¥{{ item.paymentPrice || 0 }} + + + 已付 + ¥{{ (item.totalPrice || 0) - (item.paymentPrice || 0) }} - 未付 + + 未付 + @@ -107,6 +126,24 @@ > 修改 + + 提交审批 + + + 审批记录 + + + 处理审批 + 删除 @@ -151,15 +188,19 @@ - + 采购入库审核 - 是否合格 + + 是否合格 + - 返回方式 + + 返回方式 + - 返回备注 + + 返回备注 + - + 取消 @@ -188,6 +231,124 @@ + + + + + + 提交审批 + + + + 审批人 + + + + + + 备注 + + + + + + 取消 + + + 提交 + + + + + + + + + + 处理审批 + + + + 审批结果 + + + + {{ opt.label }} + + + + + + 审批意见 + + + + + + 取消 + + + 确定 + + + + + + + + + + 审批记录 + + + 暂无审批记录 + + + + + {{ record.approverName || record.approver }} + + {{ getApprovalResultText(record.approvalResult) }} + + + + 意见:{{ record.comment }} + + + {{ record.approvalTime ? new Date(record.approvalTime).toLocaleString('zh-CN') : '-' }} + + + + + + 关闭 + + + + @@ -197,6 +358,7 @@ import type { LoadMoreState } from '@/http/types' import { onReachBottom } from '@dcloudio/uni-app' import { onMounted, reactive, ref } from 'vue' import { useToast } from 'wot-design-uni' +import { APPROVAL_RESULT, APPROVAL_RESULT_OPTIONS, getApprovalRecordListByBiz, processApproval, submitApproval } from '@/api/erp/approval' import { deletePurchaseIn, getPurchaseInPage, RETURN_TYPE_OPTIONS, updatePurchaseInStatus } from '@/api/erp/purchase-in' import { useAccess } from '@/hooks/useAccess' import { navigateBackPlus } from '@/utils' @@ -237,25 +399,96 @@ const auditForm = reactive({ returnRemark: undefined as string | undefined, }) +// 提交审批相关 +const submitApprovalVisible = ref(false) +const submitApprovalLoading = ref(false) +const submitApprovalForm = reactive({ + bizId: '' as string, + nextApprover: '' as string, + remark: '' as string, +}) + +// 处理审批相关 +const processApprovalVisible = ref(false) +const processApprovalLoading = ref(false) +const processApprovalForm = reactive({ + id: undefined as number | undefined, + approvalResult: APPROVAL_RESULT.APPROVED, + comment: '' as string, +}) + +// 审批记录相关 +const approvalRecordsVisible = ref(false) +const approvalRecords = ref([]) + /** 格式化日期 */ function formatDate(date?: string) { - if (!date) return '-' + if (!date) + return '-' return new Date(date).toLocaleDateString('zh-CN') } +/** 格式化批次号列表 */ +function formatItemBatchNos(row: PurchaseIn): string { + const items = row?.items || [] + const batchCountMap = new Map() + items.forEach((it: any) => { + const batchNo = (it?.batchNo || '').trim() + if (!batchNo) + return + const count = Number(it?.count || 0) + batchCountMap.set(batchNo, (batchCountMap.get(batchNo) || 0) + count) + }) + if (!batchCountMap.size) + return '' + return Array.from(batchCountMap.entries()) + .map(([batchNo, count]) => `${batchNo}[${count}]`) + .join(',') +} + /** 获取状态文本 */ function getStatusText(status?: number, isQualified?: boolean) { - if (status === 10) return '未审核' - if (status === 20) return isQualified ? '已审核' : '不合格' - if (status === 30) return '不合格' + if (status === 10) + return '未审核' + if (status === 20) + return isQualified ? '已审核' : '不合格' + if (status === 30) + return '不合格' return '未知' } /** 获取状态样式 */ function getStatusClass(status?: number, isQualified?: boolean) { - if (status === 10) return 'bg-[#fff7e6] text-[#fa8c16]' - if (status === 20) return isQualified ? 'bg-[#f6ffed] text-[#52c41a]' : 'bg-[#fff1f0] text-[#f5222d]' - if (status === 30) return 'bg-[#fff1f0] text-[#f5222d]' + if (status === 10) + return 'bg-[#fff7e6] text-[#fa8c16]' + if (status === 20) + return isQualified ? 'bg-[#f6ffed] text-[#52c41a]' : 'bg-[#fff1f0] text-[#f5222d]' + if (status === 30) + return 'bg-[#fff1f0] text-[#f5222d]' + return 'bg-[#f5f5f5] text-[#999]' +} + +/** 获取审批结果文本 */ +function getApprovalResultText(result?: number) { + if (result === APPROVAL_RESULT.PENDING) + return '待审批' + if (result === APPROVAL_RESULT.APPROVED) + return '已通过' + if (result === APPROVAL_RESULT.REJECTED) + return '已拒绝' + if (result === APPROVAL_RESULT.TRANSFERRED) + return '已转办' + return '未知' +} + +/** 获取审批结果样式 */ +function getApprovalResultClass(result?: number) { + if (result === APPROVAL_RESULT.PENDING) + return 'bg-[#fff7e6] text-[#fa8c16]' + if (result === APPROVAL_RESULT.APPROVED) + return 'bg-[#f6ffed] text-[#52c41a]' + if (result === APPROVAL_RESULT.REJECTED) + return 'bg-[#fff1f0] text-[#f5222d]' return 'bg-[#f5f5f5] text-[#999]' } @@ -270,7 +503,26 @@ async function getList() { try { const params = { ...queryParams.value } const data = await getPurchaseInPage(params) - list.value = [...list.value, ...data.list] + + // 为每条记录检查是否有审批记录 + const listWithApprovalStatus = await Promise.all( + data.list.map(async (item) => { + try { + const records = await getApprovalRecordListByBiz(String(item.id), 'erp_purchase_in') + return { + ...item, + hasApprovalRecords: records && records.length > 0, + } + } catch { + return { + ...item, + hasApprovalRecords: false, + } + } + }), + ) + + list.value = [...list.value, ...listWithApprovalStatus] total.value = data.total loadMoreState.value = list.value.length >= total.value ? 'finished' : 'loading' } catch { @@ -364,7 +616,8 @@ function handleQualifiedChange(val: boolean) { /** 提交审核 */ async function submitAudit() { - if (!auditForm.id) return + if (!auditForm.id) + return if (!auditForm.isQualified && !auditForm.returnType) { toast.warning('请选择返回方式') return @@ -394,7 +647,8 @@ function handleReverseAudit(id: number) { title: '提示', content: '确定反审核该入库单吗?', success: async (res) => { - if (!res.confirm) return + if (!res.confirm) + return try { await updatePurchaseInStatus({ id, @@ -418,7 +672,8 @@ function handleDelete(id: number) { title: '提示', content: '确定要删除该采购入库单吗?', success: async (res) => { - if (!res.confirm) return + if (!res.confirm) + return try { await deletePurchaseIn([id]) toast.success('删除成功') @@ -432,6 +687,91 @@ function handleDelete(id: number) { }) } +/** 提交审批 */ +function handleSubmitApproval(item: PurchaseIn) { + submitApprovalForm.bizId = String(item.id) + submitApprovalForm.nextApprover = '' + submitApprovalForm.remark = '' + submitApprovalVisible.value = true +} + +/** 确认提交审批 */ +async function confirmSubmitApproval() { + if (!submitApprovalForm.nextApprover) { + toast.warning('请输入审批人') + return + } + submitApprovalLoading.value = true + try { + await submitApproval({ + bizId: submitApprovalForm.bizId, + bizTableName: 'erp_purchase_in', + nextApprover: submitApprovalForm.nextApprover, + remark: submitApprovalForm.remark, + }) + toast.success('提交审批成功') + submitApprovalVisible.value = false + list.value = [] + queryParams.value.pageNo = 1 + getList() + } finally { + submitApprovalLoading.value = false + } +} + +/** 查看审批记录 */ +async function handleViewApproval(item: PurchaseIn) { + try { + toast.loading('加载中...') + const records = await getApprovalRecordListByBiz(String(item.id), 'erp_purchase_in') + approvalRecords.value = records || [] + approvalRecordsVisible.value = true + } finally { + toast.close() + } +} + +/** 处理审批 */ +async function handleProcessApproval(item: PurchaseIn) { + try { + toast.loading('加载中...') + const records = await getApprovalRecordListByBiz(String(item.id), 'erp_purchase_in') + // 找到待处理的审批记录 + const pendingRecord = records?.find((r: any) => r.approvalResult === APPROVAL_RESULT.PENDING) + if (!pendingRecord) { + toast.warning('没有待处理的审批记录') + return + } + processApprovalForm.id = pendingRecord.id + processApprovalForm.approvalResult = APPROVAL_RESULT.APPROVED + processApprovalForm.comment = '' + processApprovalVisible.value = true + } finally { + toast.close() + } +} + +/** 确认处理审批 */ +async function confirmProcessApproval() { + if (!processApprovalForm.id) + return + processApprovalLoading.value = true + try { + await processApproval({ + id: processApprovalForm.id, + approvalResult: processApprovalForm.approvalResult, + comment: processApprovalForm.comment, + }) + toast.success('审批处理成功') + processApprovalVisible.value = false + list.value = [] + queryParams.value.pageNo = 1 + getList() + } finally { + processApprovalLoading.value = false + } +} + /** 触底加载更多 */ onReachBottom(() => { loadMore() diff --git a/src/pages/index/index.ts b/src/pages/index/index.ts index 6de5750..6c53822 100644 --- a/src/pages/index/index.ts +++ b/src/pages/index/index.ts @@ -26,68 +26,68 @@ export interface MenuGroup { /** 菜单分组原始数据 */ const menuGroupsData: MenuGroup[] = [ - // { - // key: 'purchase', - // name: '采购管理', - // menus: [ - // { - // key: 'purchaseRequisition', - // name: '采购申请', - // icon: 'edit', - // url: '/pages-erp/purchase-requisition/index', - // iconColor: '#2f54eb', - // permission: 'erp:purchase-requisition:query', - // }, - // { - // key: 'purchaseOrder', - // name: '采购订单', - // icon: 'order', - // url: '/pages-erp/purchase-order/index', - // iconColor: '#1890ff', - // permission: 'erp:purchase-order:query', - // }, - // { - // key: 'purchaseIn', - // name: '采购入库', - // icon: 'goods', - // url: '/pages-erp/purchase-in/index', - // iconColor: '#52c41a', - // permission: 'erp:purchase-in:query', - // }, - // { - // key: 'purchaseReturn', - // name: '采购退货', - // icon: 'refund', - // url: '/pages-erp/purchase-return/index', - // iconColor: '#fa8c16', - // permission: 'erp:purchase-return:query', - // }, - // { - // key: 'supplier', - // name: '供应商', - // icon: 'shop', - // url: '/pages-erp/supplier/index', - // iconColor: '#722ed1', - // permission: 'erp:supplier:query', - // }, - // { - // key: 'farmer', - // name: '农户管理', - // icon: 'user', - // url: '/pages-erp/farmer/index', - // iconColor: '#13c2c2', - // permission: 'erp:farmer:query', - // }, - // { - // key: 'pickBroccoli', - // name: '采摘管理', - // icon: 'flower', - // url: '/pages-erp/pick-broccoli/index', - // iconColor: '#eb2f96', - // permission: 'erp:pick-broccoli:query', - // }, - // ], - // }, + { + key: 'purchase', + name: '采购管理', + menus: [ + { + key: 'purchaseRequisition', + name: '采购申请', + icon: 'edit', + url: '/pages-erp/purchase-requisition/index', + iconColor: '#2f54eb', + permission: 'erp:purchase-requisition:query', + }, + { + key: 'purchaseOrder', + name: '采购订单', + icon: 'order', + url: '/pages-erp/purchase-order/index', + iconColor: '#1890ff', + permission: 'erp:purchase-order:query', + }, + { + key: 'purchaseIn', + name: '采购入库', + icon: 'goods', + url: '/pages-erp/purchase-in/index', + iconColor: '#52c41a', + permission: 'erp:purchase-in:query', + }, + { + key: 'purchaseReturn', + name: '采购退货', + icon: 'refund', + url: '/pages-erp/purchase-return/index', + iconColor: '#fa8c16', + permission: 'erp:purchase-return:query', + }, + { + key: 'supplier', + name: '供应商', + icon: 'shop', + url: '/pages-erp/supplier/index', + iconColor: '#722ed1', + permission: 'erp:supplier:query', + }, + { + key: 'farmer', + name: '农户管理', + icon: 'user', + url: '/pages-erp/farmer/index', + iconColor: '#13c2c2', + permission: 'erp:farmer:query', + }, + { + key: 'pickBroccoli', + name: '采摘管理', + icon: 'flower', + url: '/pages-erp/pick-broccoli/index', + iconColor: '#eb2f96', + permission: 'erp:pick-broccoli:query', + }, + ], + }, // { // key: 'agri', // name: '农业溯源',