From 3b430f42c1412422aa32412463533385bf4dd44e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 01 七月 2026 09:32:08 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加采购申请页面
---
src/views/erp/purchase/in/data.ts | 6
src/api/mdm/unit/index.ts | 49 +
src/views/basicData/mdm/unit/modules/form.vue | 83 +
src/views/basicData/mdm/unit/data.ts | 198 ++++
src/api/erp/purchase/in/index.ts | 13
src/views/erp/purchase/order/data.ts | 24
src/api/erp/purchase/supplier/index.ts | 9
src/views/erp/purchase/in/modules/inspection-form.vue | 98 ++
src/views/erp/purchase/request/index.vue | 256 +++++
src/views/erp/purchase/order/modules/process-select-modal.vue | 53 +
src/views/erp/purchase/order/modules/item-form.vue | 15
src/views/erp/purchase/request/data.ts | 293 ++++++
src/views/erp/purchase/request/modules/form.vue | 135 +++
src/views/basicData/mdm/index.vue | 14
src/views/erp/purchase/statistics/price.vue | 133 ++
src/views/erp/purchase/supplier/data.ts | 127 +-
src/api/erp/purchase/request/index.ts | 84 +
src/views/basicData/mdm/unit/index.vue | 134 ++
src/api/erp/purchase/order/index.ts | 13
src/api/erp/purchase/statistics/index.ts | 84 +
src/views/erp/purchase/statistics/supplier.vue | 122 ++
src/views/erp/purchase/request/modules/item-form.vue | 267 +++++
src/views/erp/purchase/statistics/uncompleted.vue | 145 +++
src/views/erp/purchase/statistics/index.vue | 85 +
src/views/basicData/mdm/data.ts | 120 +-
src/views/erp/purchase/order/modules/form.vue | 90 +
src/views/erp/purchase/in/index.vue | 30
27 files changed, 2,484 insertions(+), 196 deletions(-)
diff --git a/src/api/erp/purchase/in/index.ts b/src/api/erp/purchase/in/index.ts
index 933e6ac..504c383 100644
--- a/src/api/erp/purchase/in/index.ts
+++ b/src/api/erp/purchase/in/index.ts
@@ -21,6 +21,10 @@
otherPrice?: number; // 鍏朵粬璐圭敤
totalProductPrice?: number; // 鍚堣鍟嗗搧閲戦
taxPrice?: number; // 鍚堣绋庨
+ inspectionStatus?: number; // 楠屾敹鐘舵�侊紙0:寰呴獙鏀� 1:宸查獙鏀� 2:楠屾敹涓嶅悎鏍硷級
+ inspectionTime?: string; // 楠屾敹鏃堕棿
+ inspectorId?: number; // 楠屾敹浜哄憳缂栧彿
+ inspectionResult?: string; // 楠屾敹缁撴灉
items?: PurchaseInItem[]; // 閲囪喘鍏ュ簱鏄庣粏
}
@@ -99,3 +103,12 @@
params,
});
}
+
+/** 楠屾敹閲囪喘鍏ュ簱 */
+export function inspectPurchaseIn(data: {
+ id: number;
+ inspectionStatus: number;
+ inspectionResult?: string;
+}) {
+ return requestClient.put('/erp/purchase-in/inspect', data);
+}
diff --git a/src/api/erp/purchase/order/index.ts b/src/api/erp/purchase/order/index.ts
index c85c7bd..6f34484 100644
--- a/src/api/erp/purchase/order/index.ts
+++ b/src/api/erp/purchase/order/index.ts
@@ -96,3 +96,16 @@
export function exportPurchaseOrder(params: any) {
return requestClient.download('/erp/purchase-order/export-excel', { params });
}
+
+/** 鑾峰彇閲囪喘瀹℃牳娴佺▼鍒楄〃 */
+export function getPurchaseApproveProcessList() {
+ return requestClient.get<any[]>('/erp/purchase-order/approve-process-list');
+}
+
+/** 鑾峰彇閲囪喘瀹℃牳娴佺▼鍒楄〃鍝嶅簲 */
+export interface ProcessDefinition {
+ id: string; // 娴佺▼瀹氫箟ID
+ key: string; // 娴佺▼Key
+ name: string; // 娴佺▼鍚嶇О
+ version: number; // 鐗堟湰鍙�
+}
diff --git a/src/api/erp/purchase/request/index.ts b/src/api/erp/purchase/request/index.ts
new file mode 100644
index 0000000..c35f89c
--- /dev/null
+++ b/src/api/erp/purchase/request/index.ts
@@ -0,0 +1,84 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace ErpPurchaseRequestApi {
+ /** 閲囪喘鐢宠淇℃伅 */
+ export interface PurchaseRequest {
+ id?: number; // 缂栧彿
+ no?: string; // 鐢宠缂栧彿
+ supplierId?: number; // 渚涘簲鍟嗙紪鍙�
+ supplierName?: string; // 渚涘簲鍟嗗悕绉�
+ requestTime: string; // 鐢宠鏃堕棿
+ requesterId?: number; // 鐢宠浜虹紪鍙�
+ requesterName?: string; // 鐢宠浜哄悕绉�
+ reason?: string; // 鐢宠鐞嗙敱
+ estimatedAmount?: number; // 棰勪及閲戦
+ status?: number; // 鐘舵��
+ remark?: string; // 澶囨敞
+ items?: PurchaseRequestItem[]; // 鐢宠鏄庣粏
+ createTime?: string; // 鍒涘缓鏃堕棿
+ }
+
+ /** 閲囪喘鐢宠鏄庣粏 */
+ export interface PurchaseRequestItem {
+ id?: number; // 缂栧彿
+ productId: number; // 浜у搧缂栧彿
+ productName?: string; // 浜у搧鍚嶇О
+ productUnitName?: string; // 浜у搧鍗曚綅
+ productBarCode?: string; // 浜у搧鏉$爜
+ requestCount: number; // 鐢宠鏁伴噺
+ estimatedPrice?: number; // 棰勪及鍗曚环
+ requiredDate?: string; // 闇�姹傛棩鏈�
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鍒涘缓閲囪喘鐢宠 */
+export function createPurchaseRequest(data: ErpPurchaseRequestApi.PurchaseRequest) {
+ return requestClient.post('/erp/purchase-request/create', data);
+}
+
+/** 鏇存柊閲囪喘鐢宠 */
+export function updatePurchaseRequest(data: ErpPurchaseRequestApi.PurchaseRequest) {
+ return requestClient.put('/erp/purchase-request/update', data);
+}
+
+/** 瀹℃壒閲囪喘鐢宠 */
+export function approvePurchaseRequest(params: {
+ id: number;
+ status: number;
+ remark?: string;
+}) {
+ return requestClient.put('/erp/purchase-request/approve', null, { params });
+}
+
+/** 杞负閲囪喘璁㈠崟 */
+export function convertToPurchaseOrder(id: number) {
+ return requestClient.put('/erp/purchase-request/convert', null, { params: { id } });
+}
+
+/** 鍒犻櫎閲囪喘鐢宠 */
+export function deletePurchaseRequest(ids: number[]) {
+ return requestClient.delete('/erp/purchase-request/delete', { params: { ids: ids.join(',') } });
+}
+
+/** 鑾峰彇閲囪喘鐢宠璇︽儏 */
+export function getPurchaseRequest(id: number) {
+ return requestClient.get<ErpPurchaseRequestApi.PurchaseRequest>(
+ `/erp/purchase-request/get?id=${id}`,
+ );
+}
+
+/** 鑾峰彇閲囪喘鐢宠鍒嗛〉 */
+export function getPurchaseRequestPage(params: PageParam) {
+ return requestClient.get<PageResult<ErpPurchaseRequestApi.PurchaseRequest>>(
+ '/erp/purchase-request/page',
+ { params },
+ );
+}
+
+/** 瀵煎嚭閲囪喘鐢宠 Excel */
+export function exportPurchaseRequest(params: any) {
+ return requestClient.download('/erp/purchase-request/export-excel', { params });
+}
diff --git a/src/api/erp/purchase/statistics/index.ts b/src/api/erp/purchase/statistics/index.ts
new file mode 100644
index 0000000..38ce979
--- /dev/null
+++ b/src/api/erp/purchase/statistics/index.ts
@@ -0,0 +1,84 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace ErpPurchaseStatisticsApi {
+ /** 閲囪喘姹囨�荤粺璁� */
+ export interface Summary {
+ totalOrderCount?: number; // 鎬昏鍗曟暟
+ totalOrderAmount?: number; // 鎬昏鍗曢噾棰�
+ totalInCount?: number; // 鎬诲叆搴撴暟
+ totalInAmount?: number; // 鎬诲叆搴撻噾棰�
+ totalReturnCount?: number; // 鎬婚��璐ф暟
+ totalReturnAmount?: number; // 鎬婚��璐ч噾棰�
+ }
+
+ /** 渚涘簲鍟嗕緵璐х粺璁� */
+ export interface SupplierStatistics {
+ supplierId?: number; // 渚涘簲鍟嗙紪鍙�
+ supplierName?: string; // 渚涘簲鍟嗗悕绉�
+ orderCount?: number; // 璁㈠崟鏁�
+ orderAmount?: number; // 璁㈠崟閲戦
+ inCount?: number; // 鍏ュ簱鏁�
+ inAmount?: number; // 鍏ュ簱閲戦
+ returnCount?: number; // 閫�璐ф暟
+ returnAmount?: number; // 閫�璐ч噾棰�
+ }
+
+ /** 閲囪喘浠锋牸鍒嗘瀽 */
+ export interface PriceAnalysis {
+ productId?: number; // 浜у搧缂栧彿
+ productName?: string; // 浜у搧鍚嶇О
+ productBarCode?: string; // 浜у搧鏉$爜
+ avgPrice?: number; // 骞冲潎浠锋牸
+ minPrice?: number; // 鏈�浣庝环鏍�
+ maxPrice?: number; // 鏈�楂樹环鏍�
+ latestPrice?: number; // 鏈�杩戜环鏍�
+ priceChangeRate?: number; // 浠锋牸鍙樺寲鐜�
+ }
+
+ /** 鏈畬鎴愯鍗曡窡韪� */
+ export interface UncompletedOrder {
+ orderId?: number; // 璁㈠崟缂栧彿
+ orderNo?: string; // 璁㈠崟鍙�
+ supplierName?: string; // 渚涘簲鍟嗗悕绉�
+ orderTime?: string; // 璁㈠崟鏃堕棿
+ productNames?: string; // 浜у搧鍚嶇О
+ orderAmount?: number; // 璁㈠崟閲戦
+ inAmount?: number; // 鍏ュ簱閲戦
+ uncompletedAmount?: number; // 鏈畬鎴愰噾棰�
+ daysOverdue?: number; // 瓒呮湡澶╂暟
+ }
+}
+
+/** 閲囪喘姹囨�荤粺璁� */
+export function getPurchaseSummary(params?: any) {
+ return requestClient.get<ErpPurchaseStatisticsApi.Summary>(
+ '/erp/purchase-statistics/summary',
+ { params },
+ );
+}
+
+/** 渚涘簲鍟嗕緵璐х粺璁� */
+export function getSupplierStatistics(params: PageParam) {
+ return requestClient.get<PageResult<ErpPurchaseStatisticsApi.SupplierStatistics>>(
+ '/erp/purchase-statistics/supplier',
+ { params },
+ );
+}
+
+/** 閲囪喘浠锋牸鍒嗘瀽 */
+export function getPriceAnalysis(params: PageParam) {
+ return requestClient.get<PageResult<ErpPurchaseStatisticsApi.PriceAnalysis>>(
+ '/erp/purchase-statistics/price',
+ { params },
+ );
+}
+
+/** 鏈畬鎴愯鍗曡窡韪� */
+export function getUncompletedOrders(params: PageParam) {
+ return requestClient.get<PageResult<ErpPurchaseStatisticsApi.UncompletedOrder>>(
+ '/erp/purchase-statistics/uncompleted',
+ { params },
+ );
+}
\ No newline at end of file
diff --git a/src/api/erp/purchase/supplier/index.ts b/src/api/erp/purchase/supplier/index.ts
index 6adfde7..8827bbb 100644
--- a/src/api/erp/purchase/supplier/index.ts
+++ b/src/api/erp/purchase/supplier/index.ts
@@ -7,19 +7,24 @@
export interface Supplier {
id?: number; // 渚涘簲鍟嗙紪鍙�
name: string; // 渚涘簲鍟嗗悕绉�
+ creditCode?: string; // 缁熶竴绀句細淇$敤浠g爜
contact: string; // 鑱旂郴浜�
mobile: string; // 鎵嬫満鍙风爜
telephone: string; // 鑱旂郴鐢佃瘽
+ phone?: string; // 鑱旂郴鐢佃瘽锛堝鐢級
email: string; // 鐢靛瓙閭
fax: string; // 浼犵湡
+ address?: string; // 鍦板潃
remark: string; // 澶囨敞
status: number; // 寮�鍚姸鎬�
sort: number; // 鎺掑簭
taxNo: string; // 绾崇◣浜鸿瘑鍒彿
taxPercent: number; // 绋庣巼
- bankName: string; // 寮�鎴疯
- bankAccount: string; // 寮�鎴疯处鍙�
+ bankName: string; // 寮�鎴烽摱琛�
+ bankAccount: string; // 閾惰璐﹀彿
bankAddress: string; // 寮�鎴峰湴鍧�
+ qualification?: string; // 璧勮川淇℃伅
+ cooperationTime?: string; // 鍚堜綔寮�濮嬫椂闂�
}
}
diff --git a/src/api/mdm/unit/index.ts b/src/api/mdm/unit/index.ts
new file mode 100644
index 0000000..d05a8f2
--- /dev/null
+++ b/src/api/mdm/unit/index.ts
@@ -0,0 +1,49 @@
+import type { PageParam, PageResult } from '#/packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace MdmUnitApi {
+ /** 璁¢噺鍗曚綅淇℃伅 */
+ export interface Unit {
+ id?: number; // 鍗曚綅缂栧彿
+ code: string; // 鍗曚綅缂栫爜
+ name: string; // 鍗曚綅鍚嶇О
+ primaryFlag?: boolean; // 鏄惁涓诲崟浣�
+ primaryId?: number; // 涓诲崟浣嶇紪鍙�
+ changeRate?: number; // 涓庝富鍗曚綅鎹㈢畻姣斾緥
+ status: number; // 鐘舵�侊紙0:鍚敤, 1:绂佺敤锛�
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ璁¢噺鍗曚綅鍒嗛〉 */
+export function getUnitPage(params: PageParam) {
+ return requestClient.get<PageResult<MdmUnitApi.Unit>>('/mdm/unit/page', {
+ params,
+ });
+}
+
+/** 鏌ヨ璁¢噺鍗曚綅鍒楄〃锛堟牴鎹甀D锛� */
+export function getUnitListByIds(ids: string) {
+ return requestClient.get<MdmUnitApi.Unit[]>(`/mdm/unit/list-by-ids?ids=${ids}`);
+}
+
+/** 鏌ヨ璁¢噺鍗曚綅璇︽儏 */
+export function getUnit(id: number) {
+ return requestClient.get<MdmUnitApi.Unit>(`/mdm/unit/get?id=${id}`);
+}
+
+/** 鏂板璁¢噺鍗曚綅 */
+export function createUnit(data: MdmUnitApi.Unit) {
+ return requestClient.post('/mdm/unit/create', data);
+}
+
+/** 淇敼璁¢噺鍗曚綅 */
+export function updateUnit(data: MdmUnitApi.Unit) {
+ return requestClient.put('/mdm/unit/update', data);
+}
+
+/** 鍒犻櫎璁¢噺鍗曚綅 */
+export function deleteUnit(id: number) {
+ return requestClient.delete(`/mdm/unit/delete?id=${id}`);
+}
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
index a809585..1808f14 100644
--- a/src/views/basicData/mdm/data.ts
+++ b/src/views/basicData/mdm/data.ts
@@ -5,6 +5,7 @@
import { getDictOptions } from '#/packages/effects/hooks/src';
import { z } from '#/adapter/form';
+import { getUnitPage } from '#/api/mdm/unit';
/** 鐗╂枡绫诲瀷閫夐」 */
export const ITEM_TYPE_OPTIONS = [
@@ -13,6 +14,22 @@
{ label: '鎴愬搧', value: 3 },
{ label: '杈呮枡', value: 4 },
];
+
+/** 鐗╂枡绫诲瀷鏄犲皠 */
+export const ITEM_TYPE_MAP: Record<number, string> = {
+ 1: '鍘熸枡',
+ 2: '鍗婃垚鍝�',
+ 3: '鎴愬搧',
+ 4: '杈呮枡',
+};
+
+/** 鐗╂枡绫诲瀷棰滆壊鏄犲皠 */
+export const ITEM_TYPE_COLOR_MAP: Record<number, string> = {
+ 1: 'green',
+ 2: 'orange',
+ 3: 'blue',
+ 4: 'purple',
+};
/** 鏂板/淇敼鐨勮〃鍗� */
export function useFormSchema(): VbenFormSchema[] {
@@ -44,63 +61,39 @@
rules: 'required',
},
{
+ component: 'RadioGroup',
+ fieldName: 'itemType',
+ label: '鐗╂枡绫诲瀷',
+ componentProps: {
+ options: ITEM_TYPE_OPTIONS,
+ buttonStyle: 'solid',
+ optionType: 'button',
+ },
+ rules: 'required',
+ },
+ {
+ component: 'ApiSelect',
+ fieldName: 'unitMeasureId',
+ label: '璁¢噺鍗曚綅',
+ rules: 'required',
+ componentProps: {
+ placeholder: '璇烽�夋嫨璁¢噺鍗曚綅',
+ allowClear: true,
+ api: async () => {
+ const res = await getUnitPage({ pageNo: 1, pageSize: 100, status: 0 });
+ return res.list || [];
+ },
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
component: 'Input',
fieldName: 'barCode',
label: '鏉$爜',
componentProps: {
placeholder: '璇疯緭鍏ユ潯鐮�',
},
- },
- {
- component: 'Input',
- fieldName: 'specification',
- label: '瑙勬牸鍨嬪彿',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鏍煎瀷鍙�',
- },
- },
- {
- component: 'TreeSelect',
- fieldName: 'categoryId',
- label: '鐗╂枡鍒嗙被',
- componentProps: {
- placeholder: '璇烽�夋嫨鐗╂枡鍒嗙被',
- allowClear: true,
- treeData: [],
- fieldNames: { label: 'name', value: 'id', children: 'children' },
- },
- },
- {
- component: 'Select',
- fieldName: 'unitMeasureId',
- label: '璁¢噺鍗曚綅',
- componentProps: {
- placeholder: '璇烽�夋嫨璁¢噺鍗曚綅',
- allowClear: true,
- options: [],
- fieldNames: { label: 'name', value: 'id' },
- },
- },
- {
- component: 'Select',
- fieldName: 'brandId',
- label: '鍝佺墝',
- componentProps: {
- placeholder: '璇烽�夋嫨鍝佺墝',
- allowClear: true,
- options: [],
- fieldNames: { label: 'name', value: 'id' },
- },
- },
- {
- component: 'Select',
- fieldName: 'itemType',
- label: '鐗╂枡绫诲瀷',
- componentProps: {
- placeholder: '璇烽�夋嫨鐗╂枡绫诲瀷',
- options: ITEM_TYPE_OPTIONS,
- },
- rules: 'required',
},
{
component: 'InputNumber',
@@ -177,7 +170,7 @@
checkedChildren: '鏄�',
unCheckedChildren: '鍚�',
},
- rules: z.boolean().default(false),
+ rules: z.boolean().default(true),
},
{
component: 'InputNumber',
@@ -235,17 +228,6 @@
},
},
{
- fieldName: 'categoryId',
- label: '鐗╂枡鍒嗙被',
- component: 'TreeSelect',
- componentProps: {
- placeholder: '璇烽�夋嫨鐗╂枡鍒嗙被',
- allowClear: true,
- treeData: [],
- fieldNames: { label: 'name', value: 'id', children: 'children' },
- },
- },
- {
fieldName: 'itemType',
label: '鐗╂枡绫诲瀷',
component: 'Select',
@@ -288,11 +270,6 @@
minWidth: 150,
},
{
- field: 'categoryName',
- title: '鍒嗙被',
- minWidth: 100,
- },
- {
field: 'unitMeasureName',
title: '鍗曚綅',
minWidth: 80,
@@ -301,10 +278,7 @@
field: 'itemType',
title: '鐗╂枡绫诲瀷',
minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.MDM_ITEM_TYPE },
- },
+ slots: { default: 'itemType' },
},
{
field: 'purchasePrice',
@@ -350,4 +324,4 @@
slots: { default: 'actions' },
},
];
-}
+}
\ No newline at end of file
diff --git a/src/views/basicData/mdm/index.vue b/src/views/basicData/mdm/index.vue
index 2331b95..c10a75f 100644
--- a/src/views/basicData/mdm/index.vue
+++ b/src/views/basicData/mdm/index.vue
@@ -19,7 +19,7 @@
} from '#/api/mdm/item';
import { $t } from '#/locales';
-import { useGridColumns, useGridFormSchema } from './data';
+import { useGridColumns, useGridFormSchema, ITEM_TYPE_MAP, ITEM_TYPE_COLOR_MAP } from './data';
import Form from './modules/form.vue';
defineOptions({ name: 'MdmItem' });
@@ -175,6 +175,12 @@
]"
/>
</template>
+ <template #itemType="{ row }">
+ <Tag v-if="row.itemType === 1" color="green">鍘熸枡</Tag>
+ <Tag v-else-if="row.itemType === 2" color="orange">鍗婃垚鍝�</Tag>
+ <Tag v-else-if="row.itemType === 3" color="blue">鎴愬搧</Tag>
+ <Tag v-else-if="row.itemType === 4" color="purple">杈呮枡</Tag>
+ </template>
<template #isBatchManaged="{ row }">
<Tag v-if="row.isBatchManaged" color="success">鏄�</Tag>
<Tag v-else>鍚�</Tag>
@@ -188,12 +194,6 @@
icon: ACTION_ICON.EDIT,
auth: ['mdm:item:update'],
onClick: handleEdit.bind(null, row),
- },
- {
- label: row.status === 0 ? '鍋滅敤' : '鍚敤',
- type: 'link',
- auth: ['mdm:item:update'],
- onClick: handleStatusChange.bind(null, row, row.status === 0),
},
{
label: $t('common.delete'),
diff --git a/src/views/basicData/mdm/unit/data.ts b/src/views/basicData/mdm/unit/data.ts
new file mode 100644
index 0000000..e6081e4
--- /dev/null
+++ b/src/views/basicData/mdm/unit/data.ts
@@ -0,0 +1,198 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import { CommonStatusEnum, DICT_TYPE } from '#/packages/constants/src';
+import { getDictOptions } from '#/packages/effects/hooks/src';
+
+import { z } from '#/adapter/form';
+import { getUnitPage } from '#/api/mdm/unit';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ component: 'Input',
+ fieldName: 'id',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ component: 'Input',
+ fieldName: 'code',
+ label: '鍗曚綅缂栫爜',
+ rules: 'required',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ崟浣嶇紪鐮�',
+ },
+ },
+ {
+ component: 'Input',
+ fieldName: 'name',
+ label: '鍗曚綅鍚嶇О',
+ rules: 'required',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ崟浣嶅悕绉�',
+ },
+ },
+ {
+ component: 'Switch',
+ fieldName: 'primaryFlag',
+ label: '鏄惁涓诲崟浣�',
+ componentProps: {
+ class: 'w-auto',
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ component: 'ApiSelect',
+ fieldName: 'primaryId',
+ label: '涓诲崟浣�',
+ dependencies: {
+ triggerFields: ['primaryFlag'],
+ show: (values) => {
+ return values.primaryFlag === false;
+ },
+ required: (values) => {
+ return values.primaryFlag === false;
+ },
+ },
+ componentProps: {
+ placeholder: '璇烽�夋嫨涓诲崟浣�',
+ allowClear: true,
+ api: async () => {
+ const res = await getUnitPage({ pageNo: 1, pageSize: 100, primaryFlag: true, status: 0 });
+ return res.list || [];
+ },
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ component: 'InputNumber',
+ fieldName: 'changeRate',
+ label: '鎹㈢畻姣斾緥',
+ dependencies: {
+ triggerFields: ['primaryFlag'],
+ show: (values) => {
+ return values.primaryFlag === false;
+ },
+ required: (values) => {
+ return values.primaryFlag === false;
+ },
+ },
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 4,
+ placeholder: '璇疯緭鍏ユ崲绠楁瘮渚�',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'RadioGroup',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+ buttonStyle: 'solid',
+ optionType: 'button',
+ },
+ rules: z.number().default(CommonStatusEnum.ENABLE),
+ },
+ {
+ component: 'Textarea',
+ fieldName: 'remark',
+ label: '澶囨敞',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍗曚綅缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ崟浣嶇紪鐮�',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍗曚綅鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ崟浣嶅悕绉�',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐘舵��',
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ { type: 'checkbox', width: 40 },
+ {
+ field: 'code',
+ title: '鍗曚綅缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'name',
+ title: '鍗曚綅鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'primaryFlag',
+ title: '涓诲崟浣�',
+ minWidth: 80,
+ slots: { default: 'primaryFlag' },
+ },
+ {
+ field: 'changeRate',
+ title: '鎹㈢畻姣斾緥',
+ minWidth: 100,
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ minWidth: 80,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.COMMON_STATUS },
+ },
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ minWidth: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 130,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
\ No newline at end of file
diff --git a/src/views/basicData/mdm/unit/index.vue b/src/views/basicData/mdm/unit/index.vue
new file mode 100644
index 0000000..e2d3a44
--- /dev/null
+++ b/src/views/basicData/mdm/unit/index.vue
@@ -0,0 +1,134 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MdmUnitApi } from '#/api/mdm/unit';
+
+import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteUnit,
+ getUnitPage,
+} from '#/api/mdm/unit';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+defineOptions({ name: 'MdmUnit' });
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓璁¢噺鍗曚綅 */
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+/** 缂栬緫璁¢噺鍗曚綅 */
+function handleEdit(row: MdmUnitApi.Unit) {
+ formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎璁¢噺鍗曚綅 */
+async function handleDelete(row: MdmUnitApi.Unit) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteUnit(row.id as number);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getUnitPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MdmUnitApi.Unit>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid table-title="璁¢噺鍗曚綅鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['璁¢噺鍗曚綅']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mdm:unit:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #primaryFlag="{ row }">
+ <Tag v-if="row.primaryFlag" color="success">鏄�</Tag>
+ <Tag v-else>鍚�</Tag>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mdm:unit:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mdm:unit:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/basicData/mdm/unit/modules/form.vue b/src/views/basicData/mdm/unit/modules/form.vue
new file mode 100644
index 0000000..f01ea05
--- /dev/null
+++ b/src/views/basicData/mdm/unit/modules/form.vue
@@ -0,0 +1,83 @@
+<script lang="ts" setup>
+import type { MdmUnitApi } from '#/api/mdm/unit';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createUnit,
+ getUnit,
+ updateUnit,
+} from '#/api/mdm/unit';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MdmUnitApi.Unit>();
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['璁¢噺鍗曚綅'])
+ : $t('ui.actionTitle.create', ['璁¢噺鍗曚綅']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ labelWidth: 100,
+ },
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ const data = (await formApi.getValues()) as MdmUnitApi.Unit;
+ try {
+ await (formData.value?.id
+ ? updateUnit(data)
+ : createUnit(data));
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<MdmUnitApi.Unit>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getUnit(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal class="w-[500px]" :title="getTitle">
+ <Form class="mx-4" />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/in/data.ts b/src/views/erp/purchase/in/data.ts
index 0b957db..613bcaf 100644
--- a/src/views/erp/purchase/in/data.ts
+++ b/src/views/erp/purchase/in/data.ts
@@ -503,6 +503,12 @@
minWidth: 120,
},
{
+ field: 'inspectionStatus',
+ title: '楠屾敹鐘舵��',
+ minWidth: 120,
+ slots: { default: 'inspectionStatus' },
+ },
+ {
field: 'status',
title: '瀹℃壒鐘舵��',
minWidth: 120,
diff --git a/src/views/erp/purchase/in/index.vue b/src/views/erp/purchase/in/index.vue
index a9a2e4b..9c82b12 100644
--- a/src/views/erp/purchase/in/index.vue
+++ b/src/views/erp/purchase/in/index.vue
@@ -7,25 +7,32 @@
import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
-import { message } from 'ant-design-vue';
+import { message, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deletePurchaseIn,
exportPurchaseIn,
getPurchaseInPage,
+ inspectPurchaseIn,
updatePurchaseInStatus,
} from '#/api/erp/purchase/in';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
+import InspectionForm from './modules/inspection-form.vue';
/** ERP 閲囪喘鍏ュ簱鍒楄〃 */
defineOptions({ name: 'ErpPurchaseIn' });
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const [InspectionModal, inspectionModalApi] = useVbenModal({
+ connectedComponent: InspectionForm,
destroyOnClose: true,
});
@@ -83,6 +90,11 @@
}
}
+/** 楠屾敹鎿嶄綔 */
+function handleInspection(row: ErpPurchaseInApi.PurchaseIn) {
+ inspectionModalApi.setData({ id: row.id }).open();
+}
+
const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
records,
@@ -133,7 +145,9 @@
</script>
<template>
- <Page auto-content-height><FormModal @success="handleRefresh" />
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <InspectionModal @success="handleRefresh" />
<Grid table-title="閲囪喘鍏ュ簱鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -167,6 +181,11 @@
]"
/>
</template>
+ <template #inspectionStatus="{ row }">
+ <Tag v-if="row.inspectionStatus === 0" color="warning">寰呴獙鏀�</Tag>
+ <Tag v-else-if="row.inspectionStatus === 1" color="success">宸查獙鏀�</Tag>
+ <Tag v-else-if="row.inspectionStatus === 2" color="error">楠屾敹涓嶅悎鏍�</Tag>
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
@@ -186,6 +205,13 @@
onClick: handleEdit.bind(null, row),
},
{
+ label: '楠屾敹',
+ type: 'link',
+ auth: ['erp:purchase-in:inspect'],
+ ifShow: () => row.inspectionStatus === 0 && row.status === 20,
+ onClick: handleInspection.bind(null, row),
+ },
+ {
label: row.status === 10 ? '瀹℃壒' : '鍙嶅鎵�',
type: 'link',
icon: ACTION_ICON.AUDIT,
diff --git a/src/views/erp/purchase/in/modules/inspection-form.vue b/src/views/erp/purchase/in/modules/inspection-form.vue
new file mode 100644
index 0000000..38293a3
--- /dev/null
+++ b/src/views/erp/purchase/in/modules/inspection-form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { inspectPurchaseIn } from '#/api/erp/purchase/in';
+import { $t } from '#/locales';
+
+const emit = defineEmits(['success']);
+const purchaseInId = ref<number>();
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ labelWidth: 100,
+ },
+ wrapperClass: 'grid-cols-1',
+ layout: 'horizontal',
+ schema: [
+ {
+ fieldName: 'inspectionStatus',
+ label: '楠屾敹鐘舵��',
+ component: 'RadioGroup',
+ componentProps: {
+ options: [
+ { label: '楠屾敹鍚堟牸', value: 1 },
+ { label: '楠屾敹涓嶅悎鏍�', value: 2 },
+ ],
+ buttonStyle: 'solid',
+ optionType: 'button',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'inspectionResult',
+ label: '楠屾敹缁撴灉',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ獙鏀剁粨鏋�',
+ rows: 3,
+ },
+ },
+ ],
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ if (!purchaseInId.value) {
+ message.error('鍏ュ簱鍗旾D涓嶅瓨鍦�');
+ return;
+ }
+ modalApi.lock();
+ const data = await formApi.getValues();
+ try {
+ await inspectPurchaseIn({
+ id: purchaseInId.value,
+ inspectionStatus: data.inspectionStatus,
+ inspectionResult: data.inspectionResult,
+ });
+ await modalApi.close();
+ emit('success');
+ message.success('楠屾敹鎴愬姛');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ purchaseInId.value = undefined;
+ await formApi.resetForm();
+ return;
+ }
+ const data = modalApi.getData<{ id: number }>();
+ if (!data || !data.id) {
+ return;
+ }
+ purchaseInId.value = data.id;
+ // 榛樿閫夋嫨楠屾敹鍚堟牸
+ await formApi.setValues({ inspectionStatus: 1 });
+ },
+});
+</script>
+
+<template>
+ <Modal title="閲囪喘鍏ュ簱楠屾敹" class="w-1/3">
+ <Form class="mx-4" />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/order/data.ts b/src/views/erp/purchase/order/data.ts
index 0c2a84d..9a2991d 100644
--- a/src/views/erp/purchase/order/data.ts
+++ b/src/views/erp/purchase/order/data.ts
@@ -62,6 +62,18 @@
rules: 'required',
},
{
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ placeholder: '璇疯緭鍏ユ敮浠樿閲�',
+ precision: 2,
+ min: 0,
+ },
+ fieldName: 'depositPrice',
+ label: '鏀粯璁㈤噾',
+ rules: 'required',
+ },
+ {
fieldName: 'remark',
label: '澶囨敞',
component: 'Textarea',
@@ -150,18 +162,6 @@
labelField: 'name',
valueField: 'id',
},
- },
- {
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ユ敮浠樿閲�',
- precision: 2,
- min: 0,
- },
- fieldName: 'depositPrice',
- label: '鏀粯璁㈤噾',
- rules: z.number().min(0).optional(),
},
];
}
diff --git a/src/views/erp/purchase/order/modules/form.vue b/src/views/erp/purchase/order/modules/form.vue
index 58a42fe..37981be 100644
--- a/src/views/erp/purchase/order/modules/form.vue
+++ b/src/views/erp/purchase/order/modules/form.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { FormType } from '../data';
-import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
+import type { ErpPurchaseOrderApi, ProcessDefinition } from '#/api/erp/purchase/order';
import { computed, ref } from 'vue';
@@ -13,6 +13,7 @@
import { getAccountSimpleList } from '#/api/erp/finance/account';
import {
createPurchaseOrder,
+ getPurchaseApproveProcessList,
getPurchaseOrder,
updatePurchaseOrder,
} from '#/api/erp/purchase/order';
@@ -20,11 +21,13 @@
import { useFormSchema } from '../data';
import PurchaseOrderItemForm from './item-form.vue';
+import ProcessSelectModal from './process-select-modal.vue';
const emit = defineEmits(['success']);
const formData = ref<ErpPurchaseOrderApi.PurchaseOrder>();
-const formType = ref<FormType>('create'); // 琛ㄥ崟绫诲瀷锛�'create' | 'edit' | 'detail'
+const formType = ref<FormType>('create');
const itemFormRef = ref<InstanceType<typeof PurchaseOrderItemForm>>();
+const selectedProcessId = ref<string>('');
const getTitle = computed(() => {
if (formType.value === 'create') {
@@ -48,11 +51,15 @@
schema: useFormSchema(formType.value),
showDefaultActions: false,
handleValuesChange: (values, changedFields) => {
- // 鐩殑锛氬悓姝ュ埌 item-form 缁勪欢锛岃Е鍙戞暣浣撶殑浠锋牸璁$畻
if (formData.value && changedFields.includes('discountPercent')) {
formData.value.discountPercent = values.discountPercent;
}
},
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+ connectedComponent: ProcessSelectModal,
+ destroyOnClose: true,
});
/** 鏇存柊閲囪喘璁㈠崟椤� */
@@ -78,6 +85,33 @@
});
}
+/** 娴佺▼閫夋嫨鎴愬姛 */
+function handleProcessSelectSuccess(processId: string) {
+ selectedProcessId.value = processId;
+ submitOrder();
+}
+
+/** 鎻愪氦璁㈠崟 */
+async function submitOrder() {
+ const data = (await formApi.getValues()) as ErpPurchaseOrderApi.PurchaseOrder;
+ data.items = formData.value?.items?.map((item) => ({
+ ...item,
+ id: undefined,
+ }));
+ if (data.fileUrl && Array.isArray(data.fileUrl)) {
+ data.fileUrl = data.fileUrl.length > 0 ? data.fileUrl[0] : '';
+ }
+ if (selectedProcessId.value) {
+ data.processDefinitionId = selectedProcessId.value;
+ }
+ await (formType.value === 'create'
+ ? createPurchaseOrder(data)
+ : updatePurchaseOrder(data));
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+}
+
/** 鍒涘缓鎴栨洿鏂伴噰璐鍗� */
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
@@ -96,42 +130,42 @@
}
modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as ErpPurchaseOrderApi.PurchaseOrder;
- data.items = formData.value?.items?.map((item) => ({
- ...item,
- // 瑙e喅鏂板閲囪喘璁㈠崟鎶ラ敊
- id: undefined,
- }));
- // 灏嗘枃浠舵暟缁勮浆鎹负瀛楃涓�
- if (data.fileUrl && Array.isArray(data.fileUrl)) {
- data.fileUrl = data.fileUrl.length > 0 ? data.fileUrl[0] : '';
- }
- try {
- await (formType.value === 'create'
- ? createPurchaseOrder(data)
- : updatePurchaseOrder(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
+
+ if (formType.value === 'create') {
+ try {
+ const processes = await getPurchaseApproveProcessList();
+ if (processes.length === 1) {
+ selectedProcessId.value = processes[0].id;
+ await submitOrder();
+ } else if (processes.length > 1) {
+ processModalApi.setData(processes).open();
+ } else {
+ message.error('閲囪喘瀹℃牳鍒嗙被涓嬫病鏈夊彲鐢ㄧ殑娴佺▼妯″瀷锛岃鍏堝垱寤哄苟鍙戝竷娴佺▼');
+ }
+ } catch (error: any) {
+ message.error(error.message || '鑾峰彇娴佺▼鍒楄〃澶辫触');
+ } finally {
+ modalApi.unlock();
+ }
+ } else {
+ try {
+ await submitOrder();
+ } finally {
+ modalApi.unlock();
+ }
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
+ selectedProcessId.value = '';
return;
}
- // 鍔犺浇鏁版嵁
const data = modalApi.getData<{ formType: FormType; id?: number }>();
formType.value = data.formType;
formApi.setDisabled(formType.value === 'detail');
formApi.updateSchema(useFormSchema(formType.value));
if (!data || !data.id) {
- // 鏂板鏃讹紝榛樿閫変腑璐︽埛
const accountList = await getAccountSimpleList();
const defaultAccount = accountList.find((item) => item.defaultStatus);
if (defaultAccount) {
@@ -142,7 +176,6 @@
modalApi.lock();
try {
formData.value = await getPurchaseOrder(data.id);
- // 璁剧疆鍒� values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
@@ -157,6 +190,7 @@
class="w-3/4"
:show-confirm-button="formType !== 'detail'"
>
+ <ProcessModal @success="handleProcessSelectSuccess" />
<Form class="mx-3">
<template #items>
<PurchaseOrderItemForm
diff --git a/src/views/erp/purchase/order/modules/item-form.vue b/src/views/erp/purchase/order/modules/item-form.vue
index fefe146..777ca3f 100644
--- a/src/views/erp/purchase/order/modules/item-form.vue
+++ b/src/views/erp/purchase/order/modules/item-form.vue
@@ -1,5 +1,5 @@
<script lang="ts" setup>
-import type { ErpProductApi } from '#/api/erp/product/product';
+import type { MdmItemApi } from '#/api/mdm/item';
import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
@@ -13,7 +13,7 @@
import { Input, InputNumber, Select } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getProductSimpleList } from '#/api/erp/product/product';
+import { getItemPage } from '#/api/mdm/item';
import { getStockCount } from '#/api/erp/stock/stock';
import { useFormItemColumns } from '../data';
@@ -37,7 +37,7 @@
]);
const tableData = ref<ErpPurchaseOrderApi.PurchaseOrderItem[]>([]); // 琛ㄦ牸鏁版嵁
-const productOptions = ref<ErpProductApi.Product[]>([]); // 浜у搧涓嬫媺閫夐」
+const productOptions = ref<MdmItemApi.Item[]>([]); // 浜у搧涓嬫媺閫夐」
/** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
const summaries = computed(() => {
@@ -158,9 +158,9 @@
return;
}
row.productId = productId;
- row.productUnitId = product.unitId;
+ row.productUnitId = product.unitMeasureId;
row.productBarCode = product.barCode;
- row.productUnitName = product.unitName;
+ row.productUnitName = product.unitMeasureName;
row.productName = product.name;
row.stockCount = (await getStockCount(productId)) || 0;
row.productPrice = product.purchasePrice || 0;
@@ -213,7 +213,8 @@
/** 鍒濆鍖� */
onMounted(async () => {
- productOptions.value = await getProductSimpleList();
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ productOptions.value = res.list || [];
// 鐩殑锛氭柊澧炴椂锛岄粯璁ゆ坊鍔犱竴琛�
if (tableData.value.length === 0) {
handleAdd();
@@ -225,6 +226,7 @@
<Grid class="w-full">
<template #productId="{ row }">
<Select
+ v-if="!disabled"
v-model:value="row.productId"
:options="productOptions"
:field-names="{ label: 'name', value: 'id' }"
@@ -233,6 +235,7 @@
show-search
@change="handleProductChange($event, row)"
/>
+ <span v-else>{{ row.productName || '-' }}</span>
</template>
<template #count="{ row }">
<InputNumber
diff --git a/src/views/erp/purchase/order/modules/process-select-modal.vue b/src/views/erp/purchase/order/modules/process-select-modal.vue
new file mode 100644
index 0000000..05cb637
--- /dev/null
+++ b/src/views/erp/purchase/order/modules/process-select-modal.vue
@@ -0,0 +1,53 @@
+<script lang="ts" setup>
+import type { ProcessDefinition } from '#/api/erp/purchase/order';
+
+import { ref, computed } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { Select } from 'ant-design-vue';
+
+const emit = defineEmits(['success']);
+
+const processList = ref<ProcessDefinition[]>([]);
+const selectedProcessId = ref<string>('');
+
+const processOptions = computed(() => {
+ return processList.value.map((p) => ({
+ label: `${p.name} (V${p.version})`,
+ value: p.id,
+ }));
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!selectedProcessId.value) {
+ return;
+ }
+ await modalApi.close();
+ emit('success', selectedProcessId.value);
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ selectedProcessId.value = '';
+ processList.value = [];
+ return;
+ }
+ const data = modalApi.getData<ProcessDefinition[]>();
+ if (data && data.length > 0) {
+ processList.value = data;
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal title="閫夋嫨瀹℃壒娴佺▼" class="w-1/3">
+ <Select
+ v-model:value="selectedProcessId"
+ :options="processOptions"
+ style="width: 100%"
+ placeholder="璇烽�夋嫨瀹℃壒娴佺▼"
+ />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/request/data.ts b/src/views/erp/purchase/request/data.ts
new file mode 100644
index 0000000..34682d6
--- /dev/null
+++ b/src/views/erp/purchase/request/data.ts
@@ -0,0 +1,293 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+import { erpPriceInputFormatter } from '../../../../packages/utils/src';
+
+import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSimpleUserList } from '#/api/system/user';
+import { getRangePickerDefaultProps } from '#/utils';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'edit';
+
+/** 鐘舵�佸父閲� */
+export const PURCHASE_REQUEST_STATUS = {
+ PENDING: 0, // 寰呭鎵�
+ APPROVED: 1, // 宸插鎵�
+ REJECTED: 2, // 宸叉嫆缁�
+ CONVERTED: 3, // 宸茶浆璁㈠崟
+};
+
+/** 琛ㄥ崟鐨勯厤缃」 */
+export function useFormSchema(formType: FormType): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'no',
+ label: '鐢宠缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '绯荤粺鑷姩鐢熸垚',
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'requestTime',
+ label: '鐢宠鏃堕棿',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '閫夋嫨鐢宠鏃堕棿',
+ showTime: true,
+ format: 'YYYY-MM-DD HH:mm:ss',
+ valueFormat: 'x',
+ disabled: formType === 'detail',
+ },
+ rules: 'required',
+ },
+ {
+ label: '渚涘簲鍟�',
+ fieldName: 'supplierId',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ allowClear: true,
+ showSearch: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ disabled: formType === 'detail',
+ },
+ },
+ {
+ fieldName: 'reason',
+ label: '鐢宠鐞嗙敱',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ョ敵璇风悊鐢�',
+ autoSize: { minRows: 1, maxRows: 1 },
+ disabled: formType === 'detail',
+ },
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ autoSize: { minRows: 1, maxRows: 1 },
+ disabled: formType === 'detail',
+ },
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'items',
+ label: '鐢宠鏄庣粏',
+ component: 'Input',
+ formItemClass: 'col-span-3',
+ },
+ ];
+}
+
+/** 琛ㄥ崟鐨勬槑缁嗚〃鏍煎垪 */
+export function useFormItemColumns(
+ disabled: boolean,
+): VxeTableGridOptions['columns'] {
+ return [
+ { type: 'seq', title: '搴忓彿', minWidth: 50, fixed: 'left' },
+ {
+ field: 'productId',
+ title: '浜у搧鍚嶇О',
+ minWidth: 200,
+ slots: { default: 'productId' },
+ },
+ {
+ field: 'productBarCode',
+ title: '鏉$爜',
+ minWidth: 120,
+ },
+ {
+ field: 'productUnitName',
+ title: '鍗曚綅',
+ minWidth: 80,
+ },
+ {
+ field: 'requestCount',
+ title: '鐢宠鏁伴噺',
+ minWidth: 120,
+ fixed: 'right',
+ slots: { default: 'requestCount' },
+ },
+ {
+ field: 'estimatedPrice',
+ title: '棰勪及鍗曚环',
+ minWidth: 120,
+ fixed: 'right',
+ slots: { default: 'estimatedPrice' },
+ },
+ {
+ field: 'totalPrice',
+ title: '棰勪及閲戦',
+ minWidth: 120,
+ fixed: 'right',
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'requiredDate',
+ title: '闇�姹傛棩鏈�',
+ minWidth: 140,
+ fixed: 'right',
+ slots: { default: 'requiredDate' },
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 150,
+ slots: { default: 'remark' },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 50,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ visible: !disabled,
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'no',
+ label: '鐢宠缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ョ敵璇风紪鍙�',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'supplierId',
+ label: '渚涘簲鍟�',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ allowClear: true,
+ showSearch: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'requestTime',
+ label: '鐢宠鏃堕棿',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'requesterId',
+ label: '鐢宠浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐢宠浜�',
+ allowClear: true,
+ showSearch: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ options: [
+ { label: '寰呭鎵�', value: 0 },
+ { label: '宸插鎵�', value: 1 },
+ { label: '宸叉嫆缁�', value: 2 },
+ { label: '宸茶浆璁㈠崟', value: 3 },
+ ],
+ placeholder: '璇烽�夋嫨鐘舵��',
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ {
+ type: 'checkbox',
+ width: 50,
+ fixed: 'left',
+ },
+ {
+ field: 'no',
+ title: '鐢宠缂栧彿',
+ width: 200,
+ fixed: 'left',
+ },
+ {
+ field: 'supplierName',
+ title: '渚涘簲鍟�',
+ minWidth: 120,
+ },
+ {
+ field: 'requestTime',
+ title: '鐢宠鏃堕棿',
+ width: 160,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'requesterName',
+ title: '鐢宠浜�',
+ minWidth: 120,
+ },
+ {
+ field: 'estimatedAmount',
+ title: '棰勪及閲戦',
+ formatter: 'formatAmount2',
+ minWidth: 120,
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ minWidth: 120,
+ slots: { default: 'status' },
+ },
+ {
+ field: 'reason',
+ title: '鐢宠鐞嗙敱',
+ minWidth: 150,
+ showOverflow: 'tooltip',
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 160,
+ formatter: 'formatDate',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/erp/purchase/request/index.vue b/src/views/erp/purchase/request/index.vue
new file mode 100644
index 0000000..a3cd39c
--- /dev/null
+++ b/src/views/erp/purchase/request/index.vue
@@ -0,0 +1,256 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
+
+import { message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ approvePurchaseRequest,
+ convertToPurchaseOrder,
+ deletePurchaseRequest,
+ exportPurchaseRequest,
+ getPurchaseRequestPage,
+} from '#/api/erp/purchase/request';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema, PURCHASE_REQUEST_STATUS } from './data';
+import Form from './modules/form.vue';
+
+/** ERP 閲囪喘鐢宠鍒楄〃 */
+defineOptions({ name: 'ErpPurchaseRequest' });
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportPurchaseRequest(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '閲囪喘鐢宠.xls', source: data });
+}
+
+/** 鏂板閲囪喘鐢宠 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 缂栬緫閲囪喘鐢宠 */
+function handleEdit(row: ErpPurchaseRequestApi.PurchaseRequest) {
+ formModalApi.setData({ formType: 'edit', id: row.id }).open();
+}
+
+/** 鍒犻櫎閲囪喘鐢宠 */
+async function handleDelete(ids: number[]) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting'),
+ duration: 0,
+ });
+ try {
+ await deletePurchaseRequest(ids);
+ message.success($t('ui.actionMessage.deleteSuccess'));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 瀹℃壒閲囪喘鐢宠 */
+async function handleApprove(row: ErpPurchaseRequestApi.PurchaseRequest, status: number) {
+ const hideLoading = message.loading({
+ content: `纭畾${status === 1 ? '瀹℃壒閫氳繃' : '瀹℃壒鎷掔粷'}璇ョ敵璇峰悧锛焋,
+ duration: 0,
+ });
+ try {
+ await approvePurchaseRequest({ id: row.id!, status, remark: '' });
+ message.success(`${status === 1 ? '瀹℃壒閫氳繃' : '瀹℃壒鎷掔粷'}鎴愬姛`);
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 杞负閲囪喘璁㈠崟 */
+async function handleConvert(row: ErpPurchaseRequestApi.PurchaseRequest) {
+ const hideLoading = message.loading({
+ content: '姝e湪杞负閲囪喘璁㈠崟...',
+ duration: 0,
+ });
+ try {
+ await convertToPurchaseOrder(row.id!);
+ message.success('宸叉垚鍔熻浆涓洪噰璐鍗�');
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const checkedIds = ref<number[]>([]);
+function handleRowCheckboxChange({
+ records,
+}: {
+ records: ErpPurchaseRequestApi.PurchaseRequest[];
+}) {
+ checkedIds.value = records.map((item) => item.id!);
+}
+
+/** 鏌ョ湅璇︽儏 */
+function handleDetail(row: ErpPurchaseRequestApi.PurchaseRequest) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getPurchaseRequestPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<ErpPurchaseRequestApi.PurchaseRequest>,
+ gridEvents: {
+ checkboxAll: handleRowCheckboxChange,
+ checkboxChange: handleRowCheckboxChange,
+ },
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid table-title="閲囪喘鐢宠鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['閲囪喘鐢宠']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['erp:purchase-request:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['erp:purchase-request:export'],
+ onClick: handleExport,
+ },
+ {
+ label: '鎵归噺鍒犻櫎',
+ type: 'primary',
+ danger: true,
+ disabled: isEmpty(checkedIds),
+ icon: ACTION_ICON.DELETE,
+ auth: ['erp:purchase-request:delete'],
+ popConfirm: {
+ title: `鏄惁鍒犻櫎鎵�閫変腑鏁版嵁锛焋,
+ confirm: handleDelete.bind(null, checkedIds),
+ },
+ },
+ ]"
+ />
+ </template>
+ <template #status="{ row }">
+ <Tag v-if="row.status === 0" color="warning">寰呭鎵�</Tag>
+ <Tag v-else-if="row.status === 1" color="success">宸插鎵�</Tag>
+ <Tag v-else-if="row.status === 2" color="error">宸叉嫆缁�</Tag>
+ <Tag v-else-if="row.status === 3" color="default">宸茶浆璁㈠崟</Tag>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.detail'),
+ type: 'link',
+ icon: ACTION_ICON.VIEW,
+ auth: ['erp:purchase-request:query'],
+ onClick: handleDetail.bind(null, row),
+ },
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['erp:purchase-request:update'],
+ ifShow: () => row.status === 0,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: '瀹℃壒閫氳繃',
+ type: 'link',
+ auth: ['erp:purchase-request:approve'],
+ ifShow: () => row.status === 0,
+ popConfirm: {
+ title: '纭瀹℃壒閫氳繃璇ョ敵璇峰悧锛�',
+ confirm: handleApprove.bind(null, row, 1),
+ },
+ },
+ {
+ label: '瀹℃壒鎷掔粷',
+ type: 'link',
+ danger: true,
+ auth: ['erp:purchase-request:approve'],
+ ifShow: () => row.status === 0,
+ popConfirm: {
+ title: '纭瀹℃壒鎷掔粷璇ョ敵璇峰悧锛�',
+ confirm: handleApprove.bind(null, row, 2),
+ },
+ },
+ {
+ label: '杞鍗�',
+ type: 'link',
+ auth: ['erp:purchase-request:convert'],
+ ifShow: () => row.status === 1,
+ popConfirm: {
+ title: '纭杞负閲囪喘璁㈠崟鍚楋紵',
+ confirm: handleConvert.bind(null, row),
+ },
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['erp:purchase-request:delete'],
+ ifShow: () => row.status === 0,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.no]),
+ confirm: handleDelete.bind(null, [row.id!]),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/request/modules/form.vue b/src/views/erp/purchase/request/modules/form.vue
new file mode 100644
index 0000000..4bf6954
--- /dev/null
+++ b/src/views/erp/purchase/request/modules/form.vue
@@ -0,0 +1,135 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message, Tag } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createPurchaseRequest,
+ getPurchaseRequest,
+ updatePurchaseRequest,
+} from '#/api/erp/purchase/request';
+import { $t } from '#/locales';
+
+import { useFormSchema, PURCHASE_REQUEST_STATUS } from '../data';
+import PurchaseRequestItemForm from './item-form.vue';
+
+const emit = defineEmits(['success']);
+const formData = ref<ErpPurchaseRequestApi.PurchaseRequest>();
+const formType = ref<FormType>('create');
+const itemFormRef = ref<InstanceType<typeof PurchaseRequestItemForm>>();
+
+const getTitle = computed(() => {
+ if (formType.value === 'create') {
+ return $t('ui.actionTitle.create', ['閲囪喘鐢宠']);
+ } else if (formType.value === 'edit') {
+ return $t('ui.actionTitle.edit', ['閲囪喘鐢宠']);
+ } else {
+ return '閲囪喘鐢宠璇︽儏';
+ }
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-3',
+ layout: 'vertical',
+ schema: useFormSchema(formType.value),
+ showDefaultActions: false,
+});
+
+/** 鏇存柊閲囪喘鐢宠椤� */
+function handleUpdateItems(items: ErpPurchaseRequestApi.PurchaseRequestItem[]) {
+ formData.value = modalApi.getData<ErpPurchaseRequestApi.PurchaseRequest>();
+ formData.value.items = items;
+ formApi.setValues({
+ items,
+ });
+}
+
+/** 鍒涘缓鎴栨洿鏂伴噰璐敵璇� */
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ const itemFormInstance = Array.isArray(itemFormRef.value)
+ ? itemFormRef.value[0]
+ : itemFormRef.value;
+ try {
+ itemFormInstance.validate();
+ } catch (error: any) {
+ message.error(error.message || '瀛愯〃鍗曢獙璇佸け璐�');
+ return;
+ }
+
+ modalApi.lock();
+ const data =
+ (await formApi.getValues()) as ErpPurchaseRequestApi.PurchaseRequest;
+ data.items = formData.value?.items?.map((item) => ({
+ ...item,
+ id: undefined,
+ }));
+ try {
+ await (formType.value === 'create'
+ ? createPurchaseRequest(data)
+ : updatePurchaseRequest(data));
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setDisabled(formType.value === 'detail');
+ formApi.updateSchema(useFormSchema(formType.value));
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getPurchaseRequest(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal
+ :title="getTitle"
+ class="w-3/4"
+ :show-confirm-button="formType !== 'detail'"
+ >
+ <Form class="mx-3">
+ <template #items>
+ <PurchaseRequestItemForm
+ ref="itemFormRef"
+ :items="formData?.items ?? []"
+ :disabled="formType === 'detail'"
+ @update:items="handleUpdateItems"
+ />
+ </template>
+ </Form>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/request/modules/item-form.vue b/src/views/erp/purchase/request/modules/item-form.vue
new file mode 100644
index 0000000..14b2713
--- /dev/null
+++ b/src/views/erp/purchase/request/modules/item-form.vue
@@ -0,0 +1,267 @@
+<script lang="ts" setup>
+import type { MdmItemApi } from '#/api/mdm/item';
+import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
+
+import { computed, nextTick, onMounted, ref, watch } from 'vue';
+
+import {
+ erpCountInputFormatter,
+ erpPriceInputFormatter,
+ erpPriceMultiply,
+} from '../../../../../packages/utils/src';
+
+import { Input, InputNumber, Select, DatePicker } from 'ant-design-vue';
+
+import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getItemPage } from '#/api/mdm/item';
+
+import { useFormItemColumns } from '../data';
+
+interface Props {
+ items?: ErpPurchaseRequestApi.PurchaseRequestItem[];
+ disabled?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ items: () => [],
+ disabled: false,
+});
+
+const emit = defineEmits(['update:items']);
+
+const tableData = ref<ErpPurchaseRequestApi.PurchaseRequestItem[]>([]);
+const productOptions = ref<MdmItemApi.Item[]>([]);
+
+/** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
+const summaries = computed(() => {
+ return {
+ count: tableData.value.reduce((sum, item) => sum + (item.requestCount || 0), 0),
+ totalPrice: tableData.value.reduce(
+ (sum, item) => {
+ const price = erpPriceMultiply(item.estimatedPrice || 0, item.requestCount || 0) ?? 0;
+ return sum + price;
+ },
+ 0,
+ ),
+ };
+});
+
+/** 琛ㄦ牸閰嶇疆 */
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useFormItemColumns(props.disabled),
+ data: tableData.value,
+ minHeight: 250,
+ autoResize: true,
+ border: true,
+ rowConfig: {
+ keyField: 'seq',
+ isHover: true,
+ },
+ pagerConfig: {
+ enabled: false,
+ },
+ toolbarConfig: {
+ enabled: false,
+ },
+ },
+});
+
+/** 鐩戝惉澶栭儴浼犲叆鐨勫垪鏁版嵁 */
+watch(
+ () => props.items,
+ async (items) => {
+ if (!items) {
+ return;
+ }
+ items.forEach((item) => initRow(item));
+ tableData.value = [...items];
+ await nextTick();
+ await gridApi.grid.reloadData(tableData.value);
+ },
+ {
+ immediate: true,
+ },
+);
+
+/** 澶勭悊鏂板 */
+function handleAdd() {
+ const newRow = {
+ id: undefined,
+ productId: undefined,
+ productUnitName: undefined,
+ productBarCode: undefined,
+ requestCount: 1,
+ estimatedPrice: undefined,
+ requiredDate: undefined,
+ remark: undefined,
+ totalPrice: 0,
+ };
+ tableData.value.push(newRow);
+ emit('update:items', [...tableData.value]);
+}
+
+/** 澶勭悊鍒犻櫎 */
+function handleDelete(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
+ const index = tableData.value.findIndex((item) => item.seq === row.seq);
+ if (index !== -1) {
+ tableData.value.splice(index, 1);
+ }
+ emit('update:items', [...tableData.value]);
+}
+
+/** 澶勭悊浜у搧鍙樻洿 */
+async function handleProductChange(productId: any, row: any) {
+ const product = productOptions.value.find((p) => p.id === productId);
+ if (!product) {
+ return;
+ }
+ row.productId = productId;
+ row.productBarCode = product.barCode;
+ row.productUnitName = product.unitMeasureName;
+ row.productName = product.name;
+ row.estimatedPrice = product.purchasePrice || 0;
+ row.requestCount = row.requestCount || 1;
+ handleRowChange(row);
+}
+
+/** 澶勭悊琛屾暟鎹彉鏇� */
+function handleRowChange(row: any) {
+ const index = tableData.value.findIndex((item) => item.seq === row.seq);
+ if (index === -1) {
+ tableData.value.push(row);
+ } else {
+ tableData.value[index] = row;
+ }
+ // 璁$畻棰勪及閲戦
+ if (row.estimatedPrice && row.requestCount) {
+ row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0;
+ }
+ emit('update:items', [...tableData.value]);
+}
+
+/** 鍒濆鍖栬鏁版嵁 */
+function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
+ if (row.estimatedPrice && row.requestCount) {
+ row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0;
+ }
+}
+
+/** 琛ㄥ崟鏍¢獙 */
+function validate() {
+ for (let i = 0; i < tableData.value.length; i++) {
+ const item = tableData.value[i];
+ if (item) {
+ if (!item.productId) {
+ throw new Error(`绗� ${i + 1} 琛岋細浜у搧涓嶈兘涓虹┖`);
+ }
+ if (!item.requestCount || item.requestCount <= 0) {
+ throw new Error(`绗� ${i + 1} 琛岋細鐢宠鏁伴噺涓嶈兘涓虹┖`);
+ }
+ }
+ }
+}
+
+defineExpose({
+ validate,
+});
+
+/** 鍒濆鍖� */
+onMounted(async () => {
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ productOptions.value = res.list || [];
+ if (tableData.value.length === 0) {
+ handleAdd();
+ }
+});
+</script>
+
+<template>
+ <Grid class="w-full">
+ <template #productId="{ row }">
+ <Select
+ v-if="!disabled"
+ v-model:value="row.productId"
+ :options="productOptions"
+ :field-names="{ label: 'name', value: 'id' }"
+ class="w-full"
+ placeholder="璇烽�夋嫨浜у搧"
+ show-search
+ @change="handleProductChange($event, row)"
+ />
+ <span v-else>{{ row.productName || '-' }}</span>
+ </template>
+ <template #requestCount="{ row }">
+ <InputNumber
+ v-if="!disabled"
+ v-model:value="row.requestCount"
+ :min="0"
+ :precision="3"
+ @change="handleRowChange(row)"
+ />
+ <span v-else>{{ erpCountInputFormatter(row.requestCount) || '-' }}</span>
+ </template>
+ <template #estimatedPrice="{ row }">
+ <InputNumber
+ v-if="!disabled"
+ v-model:value="row.estimatedPrice"
+ :min="0"
+ :precision="2"
+ @change="handleRowChange(row)"
+ />
+ <span v-else>{{ erpPriceInputFormatter(row.estimatedPrice) || '-' }}</span>
+ </template>
+ <template #requiredDate="{ row }">
+ <DatePicker
+ v-if="!disabled"
+ v-model:value="row.requiredDate"
+ valueFormat="YYYY-MM-DD"
+ class="w-full"
+ @change="handleRowChange(row)"
+ />
+ <span v-else>{{ row.requiredDate || '-' }}</span>
+ </template>
+ <template #remark="{ row }">
+ <Input v-if="!disabled" v-model:value="row.remark" class="w-full" @change="handleRowChange(row)" />
+ <span v-else>{{ row.remark || '-' }}</span>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '鍒犻櫎',
+ type: 'link',
+ danger: true,
+ popConfirm: {
+ title: '纭鍒犻櫎璇ヤ骇鍝佸悧锛�',
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+
+ <template #bottom>
+ <div class="mt-2 rounded border border-border bg-muted p-2">
+ <div class="flex justify-between text-sm text-muted-foreground">
+ <span class="font-medium text-foreground">鍚堣锛�</span>
+ <div class="flex space-x-4">
+ <span>鐢宠鏁伴噺锛歿{ erpCountInputFormatter(summaries.count) }}</span>
+ <span>棰勪及閲戦锛歿{ erpPriceInputFormatter(summaries.totalPrice) }}</span>
+ </div>
+ </div>
+ </div>
+ <TableAction
+ v-if="!disabled"
+ class="mt-2 flex justify-center"
+ :actions="[
+ {
+ label: '娣诲姞鐢宠浜у搧',
+ type: 'default',
+ onClick: handleAdd,
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/statistics/index.vue b/src/views/erp/purchase/statistics/index.vue
new file mode 100644
index 0000000..1fc05c1
--- /dev/null
+++ b/src/views/erp/purchase/statistics/index.vue
@@ -0,0 +1,85 @@
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+import { Page } from '../../../../packages/effects/common-ui/src';
+import { erpPriceInputFormatter } from '../../../../packages/utils/src';
+
+import { Card, Col, Row, Statistic } from 'ant-design-vue';
+
+import { getPurchaseSummary } from '#/api/erp/purchase/statistics';
+import type { ErpPurchaseStatisticsApi } from '#/api/erp/purchase/statistics';
+
+defineOptions({ name: 'ErpPurchaseStatistics' });
+
+const loading = ref(false);
+const summary = ref<ErpPurchaseStatisticsApi.Summary>({});
+
+/** 鑾峰彇缁熻鏁版嵁 */
+async function fetchSummary() {
+ loading.value = true;
+ try {
+ summary.value = await getPurchaseSummary();
+ } finally {
+ loading.value = false;
+ }
+}
+
+onMounted(() => {
+ fetchSummary();
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <div class="p-4">
+ <Card title="閲囪喘姹囨�荤粺璁�" :loading="loading">
+ <Row :gutter="16">
+ <Col :span="6">
+ <Statistic
+ title="鎬昏鍗曟暟"
+ :value="summary.totalOrderCount || 0"
+ suffix="鍗�"
+ />
+ </Col>
+ <Col :span="6">
+ <Statistic
+ title="鎬昏鍗曢噾棰�"
+ :value="erpPriceInputFormatter(summary.totalOrderAmount) || '0.00'"
+ suffix="鍏�"
+ />
+ </Col>
+ <Col :span="6">
+ <Statistic
+ title="鎬诲叆搴撴暟"
+ :value="summary.totalInCount || 0"
+ suffix="鍗�"
+ />
+ </Col>
+ <Col :span="6">
+ <Statistic
+ title="鎬诲叆搴撻噾棰�"
+ :value="erpPriceInputFormatter(summary.totalInAmount) || '0.00'"
+ suffix="鍏�"
+ />
+ </Col>
+ </Row>
+ <Row :gutter="16" class="mt-4">
+ <Col :span="6">
+ <Statistic
+ title="鎬婚��璐ф暟"
+ :value="summary.totalReturnCount || 0"
+ suffix="鍗�"
+ />
+ </Col>
+ <Col :span="6">
+ <Statistic
+ title="鎬婚��璐ч噾棰�"
+ :value="erpPriceInputFormatter(summary.totalReturnAmount) || '0.00'"
+ suffix="鍏�"
+ />
+ </Col>
+ </Row>
+ </Card>
+ </div>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/statistics/price.vue b/src/views/erp/purchase/statistics/price.vue
new file mode 100644
index 0000000..86e6f1f
--- /dev/null
+++ b/src/views/erp/purchase/statistics/price.vue
@@ -0,0 +1,133 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpPurchaseStatisticsApi } from '#/api/erp/purchase/statistics';
+
+import { Page } from '../../../../packages/effects/common-ui/src';
+
+import { Tag } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getPriceAnalysis } from '#/api/erp/purchase/statistics';
+import { getProductSimpleList } from '#/api/erp/product/product';
+import { getRangePickerDefaultProps } from '#/utils';
+
+import type { VbenFormSchema } from '#/adapter/form';
+
+defineOptions({ name: 'ErpPurchaseStatisticsPrice' });
+
+/** 鎼滅储琛ㄥ崟 */
+const formSchema: VbenFormSchema[] = [
+ {
+ fieldName: 'productId',
+ label: '浜у搧',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨浜у搧',
+ allowClear: true,
+ showSearch: true,
+ api: getProductSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'orderTime',
+ label: '閲囪喘鏃堕棿',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+];
+
+/** 鍒楄〃瀛楁 */
+const columns: VxeTableGridOptions['columns'] = [
+ {
+ field: 'productName',
+ title: '浜у搧鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'productBarCode',
+ title: '浜у搧鏉$爜',
+ minWidth: 120,
+ },
+ {
+ field: 'avgPrice',
+ title: '骞冲潎浠锋牸',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'minPrice',
+ title: '鏈�浣庝环鏍�',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'maxPrice',
+ title: '鏈�楂樹环鏍�',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'latestPrice',
+ title: '鏈�杩戜环鏍�',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'priceChangeRate',
+ title: '浠锋牸鍙樺寲鐜�',
+ minWidth: 120,
+ slots: { default: 'priceChangeRate' },
+ },
+];
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: formSchema,
+ },
+ gridOptions: {
+ columns,
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getPriceAnalysis({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'productId',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<ErpPurchaseStatisticsApi.PriceAnalysis>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <Grid table-title="閲囪喘浠锋牸鍒嗘瀽">
+ <template #priceChangeRate="{ row }">
+ <Tag v-if="row.priceChangeRate > 0" color="red">
+ +{{ row.priceChangeRate }}%
+ </Tag>
+ <Tag v-else-if="row.priceChangeRate < 0" color="green">
+ {{ row.priceChangeRate }}%
+ </Tag>
+ <Tag v-else color="default">{{ row.priceChangeRate }}%</Tag>
+ </template>
+ </Grid>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/statistics/supplier.vue b/src/views/erp/purchase/statistics/supplier.vue
new file mode 100644
index 0000000..a2a73b4
--- /dev/null
+++ b/src/views/erp/purchase/statistics/supplier.vue
@@ -0,0 +1,122 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpPurchaseStatisticsApi } from '#/api/erp/purchase/statistics';
+
+import { Page } from '../../../../packages/effects/common-ui/src';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSupplierStatistics } from '#/api/erp/purchase/statistics';
+import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getRangePickerDefaultProps } from '#/utils';
+
+import type { VbenFormSchema } from '#/adapter/form';
+
+defineOptions({ name: 'ErpPurchaseStatisticsSupplier' });
+
+/** 鎼滅储琛ㄥ崟 */
+const formSchema: VbenFormSchema[] = [
+ {
+ fieldName: 'supplierId',
+ label: '渚涘簲鍟�',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ allowClear: true,
+ showSearch: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'orderTime',
+ label: '璁㈠崟鏃堕棿',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+];
+
+/** 鍒楄〃瀛楁 */
+const columns: VxeTableGridOptions['columns'] = [
+ {
+ field: 'supplierName',
+ title: '渚涘簲鍟�',
+ minWidth: 150,
+ },
+ {
+ field: 'orderCount',
+ title: '璁㈠崟鏁�',
+ minWidth: 100,
+ formatter: 'formatAmount3',
+ },
+ {
+ field: 'orderAmount',
+ title: '璁㈠崟閲戦',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'inCount',
+ title: '鍏ュ簱鏁�',
+ minWidth: 100,
+ formatter: 'formatAmount3',
+ },
+ {
+ field: 'inAmount',
+ title: '鍏ュ簱閲戦',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'returnCount',
+ title: '閫�璐ф暟',
+ minWidth: 100,
+ formatter: 'formatAmount3',
+ },
+ {
+ field: 'returnAmount',
+ title: '閫�璐ч噾棰�',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+];
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: formSchema,
+ },
+ gridOptions: {
+ columns,
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getSupplierStatistics({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'supplierId',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<ErpPurchaseStatisticsApi.SupplierStatistics>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <Grid table-title="渚涘簲鍟嗕緵璐х粺璁�" />
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/statistics/uncompleted.vue b/src/views/erp/purchase/statistics/uncompleted.vue
new file mode 100644
index 0000000..8aebf45
--- /dev/null
+++ b/src/views/erp/purchase/statistics/uncompleted.vue
@@ -0,0 +1,145 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpPurchaseStatisticsApi } from '#/api/erp/purchase/statistics';
+
+import { Page } from '../../../../packages/effects/common-ui/src';
+
+import { Tag } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getUncompletedOrders } from '#/api/erp/purchase/statistics';
+import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getRangePickerDefaultProps } from '#/utils';
+
+import type { VbenFormSchema } from '#/adapter/form';
+
+defineOptions({ name: 'ErpPurchaseStatisticsUncompleted' });
+
+/** 鎼滅储琛ㄥ崟 */
+const formSchema: VbenFormSchema[] = [
+ {
+ fieldName: 'orderNo',
+ label: '璁㈠崟鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ鍗曞彿',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'supplierId',
+ label: '渚涘簲鍟�',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ allowClear: true,
+ showSearch: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'orderTime',
+ label: '璁㈠崟鏃堕棿',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+];
+
+/** 鍒楄〃瀛楁 */
+const columns: VxeTableGridOptions['columns'] = [
+ {
+ field: 'orderNo',
+ title: '璁㈠崟鍙�',
+ minWidth: 150,
+ },
+ {
+ field: 'supplierName',
+ title: '渚涘簲鍟�',
+ minWidth: 150,
+ },
+ {
+ field: 'orderTime',
+ title: '璁㈠崟鏃堕棿',
+ minWidth: 160,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'productNames',
+ title: '浜у搧淇℃伅',
+ minWidth: 150,
+ showOverflow: 'tooltip',
+ },
+ {
+ field: 'orderAmount',
+ title: '璁㈠崟閲戦',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'inAmount',
+ title: '鍏ュ簱閲戦',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'uncompletedAmount',
+ title: '鏈畬鎴愰噾棰�',
+ minWidth: 120,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'daysOverdue',
+ title: '瓒呮湡澶╂暟',
+ minWidth: 100,
+ slots: { default: 'daysOverdue' },
+ },
+];
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: formSchema,
+ },
+ gridOptions: {
+ columns,
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getUncompletedOrders({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'orderId',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<ErpPurchaseStatisticsApi.UncompletedOrder>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <Grid table-title="鏈畬鎴愯鍗曡窡韪�">
+ <template #daysOverdue="{ row }">
+ <Tag v-if="row.daysOverdue > 0" color="error">
+ 宸茶秴鏈� {{ row.daysOverdue }} 澶�
+ </Tag>
+ <Tag v-else color="success">鏈秴鏈�</Tag>
+ </template>
+ </Grid>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/supplier/data.ts b/src/views/erp/purchase/supplier/data.ts
index fdebf70..2dc2f3f 100644
--- a/src/views/erp/purchase/supplier/data.ts
+++ b/src/views/erp/purchase/supplier/data.ts
@@ -27,6 +27,22 @@
},
},
{
+ fieldName: 'creditCode',
+ label: '缁熶竴绀句細淇$敤浠g爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ョ粺涓�绀句細淇$敤浠g爜',
+ },
+ },
+ {
+ fieldName: 'taxNo',
+ label: '绋庡彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ョ◣鍙�',
+ },
+ },
+ {
fieldName: 'contact',
label: '鑱旂郴浜�',
component: 'Input',
@@ -35,15 +51,7 @@
},
},
{
- fieldName: 'mobile',
- label: '鎵嬫満鍙风爜',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ墜鏈哄彿鐮�',
- },
- },
- {
- fieldName: 'telephone',
+ fieldName: 'phone',
label: '鑱旂郴鐢佃瘽',
component: 'Input',
componentProps: {
@@ -52,83 +60,70 @@
},
{
fieldName: 'email',
- label: '鐢靛瓙閭',
+ label: '閭',
component: 'Input',
componentProps: {
- placeholder: '璇疯緭鍏ョ數瀛愰偖绠�',
- },
- },
- {
- fieldName: 'fax',
- label: '浼犵湡',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ紶鐪�',
- },
- },
- {
- fieldName: 'status',
- label: '寮�鍚姸鎬�',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- {
- fieldName: 'sort',
- label: '鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ユ帓搴�',
- },
- rules: 'required',
- },
- {
- fieldName: 'taxNo',
- label: '绾崇◣浜鸿瘑鍒彿',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ撼绋庝汉璇嗗埆鍙�',
- },
- },
- {
- fieldName: 'taxPercent',
- label: '绋庣巼(%)',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ョ◣鐜�',
- min: 0,
- precision: 2,
+ placeholder: '璇疯緭鍏ラ偖绠�',
},
},
{
fieldName: 'bankName',
- label: '寮�鎴疯',
+ label: '寮�鎴烽摱琛�',
component: 'Input',
componentProps: {
- placeholder: '璇疯緭鍏ュ紑鎴疯',
+ placeholder: '璇疯緭鍏ュ紑鎴烽摱琛�',
},
},
{
fieldName: 'bankAccount',
- label: '寮�鎴疯处鍙�',
+ label: '閾惰璐﹀彿',
component: 'Input',
componentProps: {
- placeholder: '璇疯緭鍏ュ紑鎴疯处鍙�',
+ placeholder: '璇疯緭鍏ラ摱琛岃处鍙�',
},
},
{
- fieldName: 'bankAddress',
- label: '寮�鎴峰湴鍧�',
+ fieldName: 'cooperationTime',
+ label: '鍚堜綔寮�濮嬫椂闂�',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鍚堜綔寮�濮嬫椂闂�',
+ class: '!w-full',
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'address',
+ label: '鍦板潃',
component: 'Input',
componentProps: {
- placeholder: '璇疯緭鍏ュ紑鎴峰湴鍧�',
+ placeholder: '璇疯緭鍏ュ湴鍧�',
},
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'RadioGroup',
+ componentProps: {
+ options: [
+ { label: '鍚敤', value: 0 },
+ { label: '绂佺敤', value: 1 },
+ ],
+ buttonStyle: 'solid',
+ optionType: 'button',
+ },
+ rules: z.number().default(0),
+ },
+ {
+ fieldName: 'qualification',
+ label: '璧勮川淇℃伅',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ祫璐ㄤ俊鎭�',
+ rows: 3,
+ },
+ formItemClass: 'col-span-2',
},
{
fieldName: 'remark',
--
Gitblit v1.9.3