Merge branch 'dev_pro2.0' of http://114.132.189.42:9002/r/mom-pro2-before into dev_pro2.0
| | |
| | | |
| | | src/packages/icons: |
| | | dependencies: |
| | | '@iconify/json': |
| | | specifier: ^2.2.483 |
| | | version: 2.2.489 |
| | | '@vben-core/icons': |
| | | specifier: workspace:* |
| | | version: link:../@core/base/icons |
| | |
| | | totalProductPrice: number; |
| | | discountPercent: number; |
| | | totalPrice: number; |
| | | depositPrice?: number; // å®ééé¢ï¼åä½ï¼å
|
| | | totalReceivablePrice: number; |
| | | signContactId: number; |
| | | signContactName?: string; |
| | |
| | | updateTime?: Date; |
| | | products?: ContractProduct[]; |
| | | contactName?: string; |
| | | orderId?: number; // å
³è ERP éå®è®¢åç¼å· |
| | | orderNo?: string; // ERP éå®è®¢ååå· |
| | | } |
| | | |
| | | /** åå产åä¿¡æ¯ */ |
| | | export interface ContractProduct { |
| | | id: number; |
| | | productId: number; |
| | | productName: string; |
| | | productNo: string; |
| | | productUnit: number; |
| | | productPrice: number; |
| | | itemId: number; |
| | | itemName: string; |
| | | itemCode: string; |
| | | itemBarCode: string; |
| | | itemSpecification?: string; |
| | | itemUnitName: string; |
| | | itemUnitName2?: string; |
| | | itemUnitName3?: string; |
| | | itemPrice: number; |
| | | contractPrice: number; |
| | | count: number; |
| | | totalPrice: number; |
| | | } |
| | | |
| | | /** å®¡æ¹æµç¨ */ |
| | | export interface ApproveProcess { |
| | | key: string; |
| | | name: string; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** æäº¤å®¡æ ¸ */ |
| | | export function submitContract(id: number) { |
| | | return requestClient.put(`/crm/contract/submit?id=${id}`); |
| | | export function submitContract(id: number, processDefinitionKey: string) { |
| | | return requestClient.put('/crm/contract/submit', null, { |
| | | params: { id, processDefinitionKey }, |
| | | }); |
| | | } |
| | | |
| | | /** è·åååå®¡æ¹æµç¨å表 */ |
| | | export function getContractApproveProcessList() { |
| | | return requestClient.get<CrmContractApi.ApproveProcess[]>( |
| | | '/crm/contract/approve-process-list', |
| | | ); |
| | | } |
| | | |
| | | /** åå转移 */ |
| | |
| | | export function getRemindContractCount() { |
| | | return requestClient.get<number>('/crm/contract/remind-count'); |
| | | } |
| | | |
| | | /** æ ¹æ®ååçæéå®è®¢å */ |
| | | export function generateSaleOrder(id: number) { |
| | | return requestClient.post<number>('/crm/contract/generate-sale-order', null, { params: { id } }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace CrmReportApi { |
| | | /** 客æ·åæç»è®¡ */ |
| | | export interface CustomerAnalyze { |
| | | totalCount: number; |
| | | todayCount: number; |
| | | dealCount: number; |
| | | dealRate: number; |
| | | lockCount: number; |
| | | poolCount: number; |
| | | } |
| | | |
| | | /** åæºåæç»è®¡ */ |
| | | export interface BusinessAnalyze { |
| | | totalCount: number; |
| | | todayCount: number; |
| | | totalPrice: number; |
| | | successCount: number; |
| | | successPrice: number; |
| | | successRate: number; |
| | | processingCount: number; |
| | | failCount: number; |
| | | } |
| | | |
| | | /** åååæç»è®¡ */ |
| | | export interface ContractAnalyze { |
| | | totalCount: number; |
| | | todayCount: number; |
| | | totalPrice: number; |
| | | approveCount: number; |
| | | approvePrice: number; |
| | | processCount: number; |
| | | receivablePrice: number; |
| | | receivableRate: number; |
| | | } |
| | | |
| | | /** é宿¼æé¶æ®µ */ |
| | | export interface SalesFunnelStage { |
| | | name: string; |
| | | count: number | null; |
| | | price: number; |
| | | conversionRate: number; |
| | | } |
| | | |
| | | /** é宿¼æç»è®¡ */ |
| | | export interface SalesFunnel { |
| | | stages: SalesFunnelStage[]; |
| | | } |
| | | |
| | | /** éå®ä¸ç»©é¡¹ */ |
| | | export interface SalesPerformanceItem { |
| | | userId: number; |
| | | userName: string; |
| | | deptName: string; |
| | | contractCount: number; |
| | | contractPrice: number; |
| | | receivablePrice: number; |
| | | businessCount: number; |
| | | businessPrice: number; |
| | | } |
| | | |
| | | /** éå®ä¸ç»©ç»è®¡ */ |
| | | export interface SalesPerformance { |
| | | items: SalesPerformanceItem[]; |
| | | } |
| | | } |
| | | |
| | | /** è·å客æ·åæç»è®¡ */ |
| | | export function getCustomerAnalyze(startTime?: string, endTime?: string) { |
| | | return requestClient.get<CrmReportApi.CustomerAnalyze>( |
| | | '/crm/report/get-customer-analyze', |
| | | { params: { startTime, endTime } }, |
| | | ); |
| | | } |
| | | |
| | | /** è·ååæºåæç»è®¡ */ |
| | | export function getBusinessAnalyze(startTime?: string, endTime?: string) { |
| | | return requestClient.get<CrmReportApi.BusinessAnalyze>( |
| | | '/crm/report/get-business-analyze', |
| | | { params: { startTime, endTime } }, |
| | | ); |
| | | } |
| | | |
| | | /** è·ååååæç»è®¡ */ |
| | | export function getContractAnalyze(startTime?: string, endTime?: string) { |
| | | return requestClient.get<CrmReportApi.ContractAnalyze>( |
| | | '/crm/report/get-contract-analyze', |
| | | { params: { startTime, endTime } }, |
| | | ); |
| | | } |
| | | |
| | | /** è·åé宿¼æç»è®¡ */ |
| | | export function getSalesFunnel() { |
| | | return requestClient.get<CrmReportApi.SalesFunnel>( |
| | | '/crm/report/get-sales-funnel', |
| | | ); |
| | | } |
| | | |
| | | /** è·åéå®ä¸ç»©ç»è®¡ */ |
| | | export function getSalesPerformance(startTime?: string, endTime?: string) { |
| | | return requestClient.get<CrmReportApi.SalesPerformance>( |
| | | '/crm/report/get-sales-performance', |
| | | { params: { startTime, endTime } }, |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '../../../packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace CrmSaleQuotationApi { |
| | | /** æ¥ä»·åä¿¡æ¯ */ |
| | | export interface SaleQuotation { |
| | | id: number; |
| | | name: string; |
| | | no: string; |
| | | customerId: number; |
| | | customerName?: string; |
| | | businessId?: number; |
| | | businessName?: string; |
| | | contactId?: number; |
| | | contactName?: string; |
| | | quotationTime: Date; |
| | | validUntil?: Date; |
| | | totalProductPrice: number; |
| | | discountPercent: number; |
| | | totalPrice: number; |
| | | taxRate?: number; |
| | | fileUrl?: string; |
| | | remark?: string; |
| | | status: number; |
| | | contractId?: number; |
| | | contractNo?: string; |
| | | ownerUserId: number; |
| | | ownerUserName?: string; |
| | | creator: string; |
| | | creatorName: string; |
| | | createTime?: Date; |
| | | updateTime?: Date; |
| | | items?: SaleQuotationItem[]; |
| | | } |
| | | |
| | | /** æ¥ä»·åç©ææç» */ |
| | | export interface SaleQuotationItem { |
| | | id: number; |
| | | itemId: number; |
| | | itemName: string; |
| | | itemCode: string; |
| | | itemBarCode?: string; |
| | | itemSpecification?: string; |
| | | itemUnitName: string; |
| | | itemUnitName2?: string; |
| | | itemUnitName3?: string; |
| | | itemPrice: number; |
| | | quotationPrice: number; |
| | | count: number; |
| | | totalPrice: number; |
| | | taxPercent?: number; |
| | | taxPrice?: number; |
| | | remark?: string; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢æ¥ä»·åå页å表 */ |
| | | export function getSaleQuotationPage(params: PageParam) { |
| | | return requestClient.get<PageResult<CrmSaleQuotationApi.SaleQuotation>>( |
| | | '/crm/sale-quotation/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢æ¥ä»·åå页å表ï¼åºäºå®¢æ·ï¼ */ |
| | | export function getSaleQuotationPageByCustomer(params: PageParam) { |
| | | return requestClient.get<PageResult<CrmSaleQuotationApi.SaleQuotation>>( |
| | | '/crm/sale-quotation/page-by-customer', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢æ¥ä»·åå页å表ï¼åºäºåæºï¼ */ |
| | | export function getSaleQuotationPageByBusiness(params: PageParam) { |
| | | return requestClient.get<PageResult<CrmSaleQuotationApi.SaleQuotation>>( |
| | | '/crm/sale-quotation/page-by-business', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢æ¥ä»·å详æ
*/ |
| | | export function getSaleQuotation(id: number) { |
| | | return requestClient.get<CrmSaleQuotationApi.SaleQuotation>( |
| | | `/crm/sale-quotation/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢æ¥ä»·å */ |
| | | export function createSaleQuotation(data: CrmSaleQuotationApi.SaleQuotation) { |
| | | return requestClient.post('/crm/sale-quotation/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹æ¥ä»·å */ |
| | | export function updateSaleQuotation(data: CrmSaleQuotationApi.SaleQuotation) { |
| | | return requestClient.put('/crm/sale-quotation/update', data); |
| | | } |
| | | |
| | | /** å 餿¥ä»·å */ |
| | | export function deleteSaleQuotation(id: number) { |
| | | return requestClient.delete(`/crm/sale-quotation/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 转为åå */ |
| | | export function convertSaleQuotationToContract(id: number) { |
| | | return requestClient.post<number>('/crm/sale-quotation/convert-contract', null, { |
| | | params: { id }, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace CrmSaleTraceApi { |
| | | /** éå®è¿½æº¯ä¿¡æ¯ */ |
| | | export interface SaleTrace { |
| | | businessId?: number; |
| | | businessName?: string; |
| | | businessPrice?: number; |
| | | businessCreateTime?: Date; |
| | | customerId?: number; |
| | | customerName?: string; |
| | | contractId?: number; |
| | | contractName?: string; |
| | | contractNo?: string; |
| | | contractPrice?: number; |
| | | contractCreateTime?: Date; |
| | | contractStatus?: number; |
| | | orderId?: number; |
| | | orderNo?: string; |
| | | orderPrice?: number; |
| | | orderCreateTime?: Date; |
| | | receivablePrice?: number; |
| | | receivableItems?: ReceivableItem[]; |
| | | } |
| | | |
| | | /** 忬¾æç» */ |
| | | export interface ReceivableItem { |
| | | receivableId: number; |
| | | receivableNo: string; |
| | | price: number; |
| | | createTime?: Date; |
| | | } |
| | | } |
| | | |
| | | /** æ ¹æ®åæºç¼å·è·åé宿µç¨è¿½æº¯ä¿¡æ¯ */ |
| | | export function getTraceByBusinessId(businessId: number) { |
| | | return requestClient.get<CrmSaleTraceApi.SaleTrace>( |
| | | '/crm/sale-trace/get-by-business-id', |
| | | { params: { businessId } }, |
| | | ); |
| | | } |
| | | |
| | | /** æ ¹æ®ååç¼å·è·åé宿µç¨è¿½æº¯ä¿¡æ¯ */ |
| | | export function getTraceByContractId(contractId: number) { |
| | | return requestClient.get<CrmSaleTraceApi.SaleTrace>( |
| | | '/crm/sale-trace/get-by-contract-id', |
| | | { params: { contractId } }, |
| | | ); |
| | | } |
| | | |
| | | /** æ ¹æ®å®¢æ·ç¼å·è·åé宿µç¨è¿½æº¯å表 */ |
| | | export function getTraceListByCustomerId(customerId: number) { |
| | | return requestClient.get<CrmSaleTraceApi.SaleTrace[]>( |
| | | '/crm/sale-trace/get-list-by-customer-id', |
| | | { params: { customerId } }, |
| | | ); |
| | | } |
| | |
| | | export interface PurchaseRequest { |
| | | id?: number; // ç¼å· |
| | | no?: string; // ç³è¯·ç¼å· |
| | | status?: number; // ç¶æï¼0æªæäº¤ 10审æ¹ä¸ 20å®¡æ ¸éè¿ 30å®¡æ ¸ä¸éè¿ 40已忶 |
| | | requestUserId?: number; // ç³è¯·äººç¼å· |
| | | requestUserName?: string; // ç³è¯·äººåç§° |
| | | requestDeptId?: number; // ç³è¯·é¨é¨ç¼å· |
| | | requestDeptName?: string; // ç³è¯·é¨é¨åç§° |
| | | requestTime: string; // ç³è¯·æ¶é´ |
| | | requestReason?: string; // ç³è¯·çç± |
| | | supplierId?: number; // ä¾åºåç¼å· |
| | | supplierName?: string; // ä¾åºååç§° |
| | | requestTime: string; // ç³è¯·æ¶é´ |
| | | requesterId?: number; // ç³è¯·äººç¼å· |
| | | requesterName?: string; // ç³è¯·äººåç§° |
| | | reason?: string; // ç³è¯·çç± |
| | | estimatedAmount?: number; // é¢ä¼°éé¢ |
| | | status?: number; // ç¶æ |
| | | discountPercent?: number; // 伿 ç |
| | | fileUrl?: string; // éä»¶å°å |
| | | remark?: string; // 夿³¨ |
| | | totalCount?: number; // æ»æ°é |
| | | totalPrice?: number; // æ»éé¢ |
| | | productNames?: string; // 产ååç§°å表 |
| | | orderId?: number; // çæçéè´è®¢åç¼å· |
| | | orderNo?: string; // çæçéè´è®¢åå· |
| | | items?: PurchaseRequestItem[]; // ç³è¯·æç» |
| | | createTime?: string; // å建æ¶é´ |
| | | } |
| | |
| | | id?: number; // ç¼å· |
| | | productId: number; // 产åç¼å· |
| | | productName?: string; // 产ååç§° |
| | | productUnitName?: string; // 产ååä½ |
| | | productUnitId?: number; // 产ååä½ç¼å· |
| | | productUnitName?: string; // 产ååä½åç§° |
| | | productBarCode?: string; // äº§åæ¡ç |
| | | requestCount: number; // ç³è¯·æ°é |
| | | estimatedPrice?: number; // é¢ä¼°åä»· |
| | | requiredDate?: string; // éæ±æ¥æ |
| | | productPrice?: number; // 产ååä»·ï¼åèä»·ï¼ |
| | | count: number; // æ°é |
| | | taxPercent?: number; // ç¨ç |
| | | demandTime?: string; // éæ±æ¥æ |
| | | remark?: string; // 夿³¨ |
| | | } |
| | | } |
| | |
| | | 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 submitPurchaseRequest(id: number, processDefinitionKey: string) { |
| | | return requestClient.put('/erp/purchase-request/submit', null, { params: { id, processDefinitionKey } }); |
| | | } |
| | | |
| | | /** 转为éè´è®¢å */ |
| | | export function convertToPurchaseOrder(id: number) { |
| | | return requestClient.put('/erp/purchase-request/convert', null, { params: { id } }); |
| | | /** çæéè´è®¢å */ |
| | | export function generatePurchaseOrder(id: number, supplierId: number) { |
| | | return requestClient.post<number>('/erp/purchase-request/generate-order', null, { params: { id, supplierId } }); |
| | | } |
| | | |
| | | /** å é¤éè´ç³è¯· */ |
| | |
| | | export function exportPurchaseRequest(params: any) { |
| | | return requestClient.download('/erp/purchase-request/export-excel', { params }); |
| | | } |
| | | |
| | | /** è·åå®¡æ¹æµç¨å表 */ |
| | | export function getApproveProcessList(id: number) { |
| | | return requestClient.get(`/erp/purchase-request/approve-process-list`, { params: { id } }); |
| | | } |
| | |
| | | id?: number; // 订åå·¥åç¼å· |
| | | no: string; // éå®è®¢åå· |
| | | customerId: number; // 客æ·ç¼å· |
| | | customerName?: string; // 客æ·åç§° |
| | | accountId?: number; // æ¶æ¬¾è´¦æ·ç¼å· |
| | | orderTime: Date; // è®¢åæ¶é´ |
| | | totalCount: number; // å计æ°é |
| | |
| | | discountPercent?: number; // 伿 çï¼ç¾åæ¯ |
| | | discountPrice?: number; // 伿 éé¢ï¼åä½ï¼å
|
| | | depositPrice?: number; // å®ééé¢ï¼åä½ï¼å
|
| | | contractId?: number; // å
³è CRM ååç¼å· |
| | | contractNo?: string; // CRM åååå· |
| | | items?: SaleOrderItem[]; // éå®è®¢å产åæç»å表 |
| | | } |
| | | |
| | |
| | | export function exportSaleOrder(params: any) { |
| | | return requestClient.download('/erp/sale-order/export-excel', { params }); |
| | | } |
| | | |
| | | /** ä½åºéå®è®¢å */ |
| | | export function cancelSaleOrder(id: number) { |
| | | return requestClient.put('/erp/sale-order/cancel', null, { |
| | | params: { id }, |
| | | }); |
| | | } |
| | | |
| | | /** å¯ç¨éå®è®¢åï¼æ¢å¤å·²ä½åºè®¢åï¼ */ |
| | | export function enableSaleOrder(id: number) { |
| | | return requestClient.put('/erp/sale-order/enable', null, { |
| | | params: { id }, |
| | | }); |
| | | } |
| | |
| | | categoryName?: string; |
| | | unitMeasureId?: number; |
| | | unitMeasureName?: string; |
| | | unitMeasureId2?: number; |
| | | unitMeasureName2?: string; |
| | | unitMeasureRate1?: number; |
| | | unitMeasureId3?: number; |
| | | unitMeasureName3?: string; |
| | | unitMeasureRate2?: number; |
| | | brandId?: number; |
| | | brandName?: string; |
| | | itemType: number; |
| | |
| | | maxStock?: number; |
| | | isBatchManaged: boolean; |
| | | expiryDay?: number; |
| | | warehouseId?: number; |
| | | warehouseCode?: string; |
| | | warehouseName?: string; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmCustomerApi } from '#/api/crm/customer'; |
| | | |
| | | import { computed, ref, watch } from 'vue'; |
| | | |
| | | import { Select } from 'ant-design-vue'; |
| | | |
| | | import { getCustomerSimpleList } from '#/api/crm/customer'; |
| | | |
| | | interface Props { |
| | | value?: number; |
| | | disabled?: boolean; |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | value: undefined, |
| | | disabled: false, |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:value', 'change']); |
| | | |
| | | const customerList = ref<CrmCustomerApi.Customer[]>([]); |
| | | const loading = ref(false); |
| | | |
| | | const options = computed(() => { |
| | | return customerList.value.map((item) => ({ |
| | | label: item.name, |
| | | value: item.id, |
| | | })); |
| | | }); |
| | | |
| | | const selectedValue = ref(props.value); |
| | | |
| | | watch( |
| | | () => props.value, |
| | | (val) => { |
| | | selectedValue.value = val; |
| | | }, |
| | | ); |
| | | |
| | | watch(selectedValue, (val) => { |
| | | emit('update:value', val); |
| | | const customer = customerList.value.find((item) => item.id === val); |
| | | emit('change', customer); |
| | | }); |
| | | |
| | | async function loadCustomers() { |
| | | if (customerList.value.length > 0) return; |
| | | loading.value = true; |
| | | try { |
| | | customerList.value = await getCustomerSimpleList(); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | function filterOption(input: string, option: any) { |
| | | return option.label.toLowerCase().includes(input.toLowerCase()); |
| | | } |
| | | |
| | | loadCustomers(); |
| | | </script> |
| | | |
| | | <template> |
| | | <Select |
| | | v-model:value="selectedValue" |
| | | :options="options" |
| | | :loading="loading" |
| | | :disabled="disabled" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | show-search |
| | | :filter-option="filterOption" |
| | | allow-clear |
| | | /> |
| | | </template> |
| | |
| | | attrs.onCancel = popConfirm.cancel; |
| | | } |
| | | |
| | | // ä¸è®¾ç½® getPopupContainerï¼è®©å®é»è®¤æè½½å° bodyï¼é¿å
è¢«é®æ¡ |
| | | |
| | | return attrs; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | .ant-popconfirm { |
| | | width: 210px; |
| | | |
| | | //.ant-popconfirm-message { |
| | | // white-space: nowrap; |
| | | //} |
| | | |
| | | .ant-popconfirm-buttons { |
| | | .ant-btn { |
| | | margin-inline-start: 4px !important; |
| | |
| | | cancel?: () => void; |
| | | icon?: string; |
| | | disabled?: boolean; |
| | | placement?: string; |
| | | getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement; |
| | | } |
| | | |
| | | export interface ActionItem extends ButtonProps { |
| | |
| | | } |
| | | }, |
| | | "dependencies": { |
| | | "@iconify/json": "^2.2.483", |
| | | "@vben-core/icons": "workspace:*" |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** |
| | | * ç¦»çº¿å¾æ é¢å è½½é
ç½® |
| | | * ææ¶ç¦ç¨ï¼ä½¿ç¨å¨çº¿å è½½æ¹å¼ |
| | | */ |
| | | |
| | | import { addCollection } from '@iconify/vue'; |
| | | |
| | | /** |
| | | * åå§åç¦»çº¿å¾æ |
| | | * ç®å使ç¨å¨çº¿å è½½ï¼æ¤å½æ°ä¸ºç©º |
| | | */ |
| | | export function initOfflineIcons() { |
| | | // ææ¶ä¸é¢å è½½ï¼ä½¿ç¨ Iconify å¨çº¿ API |
| | | // 妿éè¦ç¦»çº¿å è½½ï¼éè¦å° JSON æä»¶å¤å¶å°é¡¹ç®ä¸ |
| | | } |
| | |
| | | companyName: import.meta.env.VITE_APP_TITLE, |
| | | companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben', |
| | | }, |
| | | tabbar: { |
| | | /** å·æ°æµè§å¨ååªä¿çå½å页é¢ï¼ä¸æä¹
åæææ ç¾é¡µ */ |
| | | persist: false, |
| | | }, |
| | | }); |
| | | |
| | | export const preferencesExtension = |
| | |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getUnitPage } from '#/api/mdm/unit'; |
| | | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; |
| | | |
| | | /** ç©æç±»åé项 */ |
| | | export const ITEM_TYPE_OPTIONS = [ |
| | |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'specification', |
| | | label: 'è§æ ¼åå·', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è§æ ¼åå·', |
| | | }, |
| | | }, |
| | | { |
| | | component: 'ApiSelect', |
| | | fieldName: 'unitMeasureId', |
| | | label: '计éåä½', |
| | | rules: 'required', |
| | | formItemClass: 'col-span-1', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è®¡éåä½', |
| | | allowClear: true, |
| | |
| | | const res = await getUnitPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | return res.list || []; |
| | | }, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | component: 'ApiSelect', |
| | | fieldName: 'unitMeasureId2', |
| | | label: 'è¾
åä½1', |
| | | formItemClass: 'col-span-1', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è¾
åä½1', |
| | | allowClear: true, |
| | | api: async () => { |
| | | const res = await getUnitPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | return res.list || []; |
| | | }, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | component: 'InputNumber', |
| | | fieldName: 'unitMeasureRate1', |
| | | label: 'è¾
åä½1æ¢ç®æ¯ç', |
| | | formItemClass: 'col-span-1', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 6, |
| | | placeholder: '请è¾å
¥æ¢ç®æ¯ç', |
| | | }, |
| | | }, |
| | | { |
| | | component: 'ApiSelect', |
| | | fieldName: 'unitMeasureId3', |
| | | label: 'è¾
åä½2', |
| | | formItemClass: 'col-span-1', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è¾
åä½2', |
| | | allowClear: true, |
| | | api: async () => { |
| | | const res = await getUnitPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | return res.list || []; |
| | | }, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | component: 'InputNumber', |
| | | fieldName: 'unitMeasureRate2', |
| | | label: 'è¾
åä½2æ¢ç®æ¯ç', |
| | | formItemClass: 'col-span-1', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 6, |
| | | placeholder: '请è¾å
¥æ¢ç®æ¯ç', |
| | | }, |
| | | }, |
| | | { |
| | | component: 'ApiSelect', |
| | | fieldName: 'warehouseId', |
| | | label: 'é»è®¤ä»åº', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©é»è®¤ä»åº', |
| | | allowClear: true, |
| | | api: getWarehouseSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'unitMeasureName2', |
| | | title: 'è¾
åä½1', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'unitMeasureRate1', |
| | | title: 'è¾
åä½1æ¢ç®æ¯ç', |
| | | minWidth: 130, |
| | | }, |
| | | { |
| | | field: 'unitMeasureName3', |
| | | title: 'è¾
åä½2', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'unitMeasureRate2', |
| | | title: 'è¾
åä½2æ¢ç®æ¯ç', |
| | | minWidth: 130, |
| | | }, |
| | | { |
| | | field: 'warehouseName', |
| | | title: 'é»è®¤ä»åº', |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | field: 'itemType', |
| | | title: 'ç©æç±»å', |
| | | minWidth: 100, |
| | |
| | | import { |
| | | deleteItem, |
| | | deleteItemList, |
| | | exportItem, |
| | | // exportItem, |
| | | getItemPage, |
| | | updateItemStatus, |
| | | } from '#/api/mdm/item'; |
| | |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportItem(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'ç©æ.xls', source: data }); |
| | | } |
| | | // async function handleExport() { |
| | | // const data = await exportItem(await gridApi.formApi.getValues()); |
| | | // downloadFileFromBlobPart({ fileName: 'ç©æ.xls', source: data }); |
| | | // } |
| | | |
| | | /** åå»ºç©æ */ |
| | | function handleCreate() { |
| | |
| | | auth: ['mdm:item:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mdm:item:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | // { |
| | | // label: $t('ui.actionTitle.export'), |
| | | // type: 'primary', |
| | | // icon: ACTION_ICON.DOWNLOAD, |
| | | // auth: ['mdm:item:export'], |
| | | // onClick: handleExport, |
| | | // }, |
| | | { |
| | | label: $t('ui.actionTitle.deleteBatch'), |
| | | type: 'primary', |
| | |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | labelWidth: 100, |
| | | labelWidth: 110, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[800px]" :title="getTitle"> |
| | | <Modal class="w-[1000px]" :title="getTitle"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | import { getDictOptions } from '#/packages/effects/hooks/src'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getUnitPage } from '#/api/mdm/unit'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | |
| | | 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: '请è¾å
¥æ¢ç®æ¯ä¾', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | field: 'name', |
| | | title: 'åä½åç§°', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | field: 'primaryFlag', |
| | | title: '主åä½', |
| | | minWidth: 80, |
| | | slots: { default: 'primaryFlag' }, |
| | | }, |
| | | { |
| | | field: 'changeRate', |
| | | title: 'æ¢ç®æ¯ä¾', |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | field: 'status', |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { DICT_TYPE } from '../../../packages/constants/src'; |
| | | import { useUserStore } from '../../../packages/stores/src'; |
| | | import { erpPriceInputFormatter, erpPriceMultiply } from '../../../packages/utils/src'; |
| | | |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'orderDate', |
| | | label: 'ä¸åæ¥æ', |
| | | component: 'DatePicker', |
| | | rules: 'required', |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | placeholder: 'è¯·éæ©ä¸åæ¥æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'businessId', |
| | | label: 'åæºåç§°', |
| | | component: 'Select', |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'orderDate', |
| | | label: 'ä¸åæ¥æ', |
| | | component: 'DatePicker', |
| | | rules: 'required', |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | placeholder: 'è¯·éæ©ä¸åæ¥æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'startTime', |
| | | label: 'ååå¼å§æ¶é´', |
| | | component: 'DatePicker', |
| | |
| | | valueFormat: 'x', |
| | | placeholder: 'è¯·éæ©ååç»ææ¶é´', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'depositPrice', |
| | | label: 'å®ééé¢ï¼å
ï¼', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | placeholder: '请è¾å
¥å®ééé¢', |
| | | }, |
| | | rules: z.number().min(0).optional(), |
| | | }, |
| | | { |
| | | fieldName: 'signUserId', |
| | |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'auditStatus', |
| | | label: 'ååç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: [ |
| | | { label: 'æªæäº¤', value: 0 }, |
| | | { label: '审æ¹ä¸', value: 10 }, |
| | | { label: 'å®¡æ ¸éè¿', value: 20 }, |
| | | { label: 'å®¡æ ¸ä¸éè¿', value: 30 }, |
| | | { label: '已忶', value: 40 }, |
| | | { label: 'å·²ä½åº', value: 50 }, |
| | | ], |
| | | placeholder: 'è¯·éæ©ååç¶æ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'orderNo', |
| | | label: 'å
³è订å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è®¢åå·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | |
| | | field: 'no', |
| | | minWidth: 180, |
| | | fixed: 'left', |
| | | slots: { default: 'no' }, |
| | | }, |
| | | { |
| | | title: 'åååç§°', |
| | |
| | | { |
| | | title: 'ååéé¢ï¼å
ï¼', |
| | | field: 'totalPrice', |
| | | minWidth: 140, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'å®ééé¢ï¼å
ï¼', |
| | | field: 'depositPrice', |
| | | minWidth: 140, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | |
| | | field: 'auditStatus', |
| | | fixed: 'right', |
| | | minWidth: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, |
| | | }, |
| | | slots: { default: 'auditStatus' }, |
| | | }, |
| | | { |
| | | title: 'å
³è订å', |
| | | field: 'orderNo', |
| | | fixed: 'right', |
| | | minWidth: 150, |
| | | slots: { default: 'orderNo' }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | field: 'actions', |
| | | fixed: 'right', |
| | | minWidth: 130, |
| | | minWidth: 220, |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'depositPrice', |
| | | label: 'å®ééé¢ï¼å
ï¼', |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'orderDate', |
| | | label: 'ä¸åæ¶é´', |
| | | render: (val) => formatDateTime(val) as string, |
| | |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'depositPrice', |
| | | label: 'å®ééé¢ï¼å
ï¼', |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'orderDate', |
| | | label: 'ä¸åæ¶é´', |
| | | render: (val) => formatDateTime(val) as string, |
| | |
| | | label: 'å
¬å¸ç¾çº¦äºº', |
| | | }, |
| | | { |
| | | field: 'orderNo', |
| | | label: 'éå®è®¢å', |
| | | slot: 'orderNo', // ä½¿ç¨ slot èªå®ä¹æ¸²æ |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | label: '夿³¨', |
| | | }, |
| | |
| | | const contract = ref<CrmContractApi.Contract>({} as CrmContractApi.Contract); // åå详æ
|
| | | const logList = ref<SystemOperateLogApi.OperateLog[]>([]); // æä½æ¥å¿ |
| | | const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // å¢éæåå表 Ref |
| | | const activeTab = ref('1'); // å½åæ¿æ´»ç Tab |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: false, |
| | |
| | | <Descriptions :data="contract" /> |
| | | </Card> |
| | | <Card class="mt-4 min-h-[60%]"> |
| | | <Tabs> |
| | | <Tabs.TabPane tab="è·è¿è®°å½" key="1" :force-render="true"> |
| | | <FollowUp :biz-id="contractId" :biz-type="BizTypeEnum.CRM_CONTRACT" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane tab="åºæ¬ä¿¡æ¯" key="2" :force-render="true"> |
| | | <Tabs v-model:activeKey="activeTab"> |
| | | <Tabs.TabPane tab="åºæ¬ä¿¡æ¯" key="1" :force-render="true"> |
| | | <ContractDetailsInfo :contract="contract" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane tab="è·è¿è®°å½" key="2" :force-render="true"> |
| | | <FollowUp :biz-id="contractId" :biz-type="BizTypeEnum.CRM_CONTRACT" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane tab="产å" key="3" :force-render="true"> |
| | | <ProductDetailsList |
| | | :biz-id="contractId" |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmContractApi } from '#/api/crm/contract'; |
| | | |
| | | import { Divider } from 'ant-design-vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Button, Divider } from 'ant-design-vue'; |
| | | |
| | | import { useDescription } from '#/components/description'; |
| | | import { useFollowUpDetailSchema } from '#/views/crm/followup/data'; |
| | | |
| | | import { useDetailBaseSchema } from '../data'; |
| | | |
| | | defineProps<{ |
| | | const props = defineProps<{ |
| | | contract: CrmContractApi.Contract; // ååä¿¡æ¯ |
| | | }>(); |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const [BaseDescriptions] = useDescription({ |
| | | title: 'åºæ¬ä¿¡æ¯', |
| | |
| | | class: 'mx-4', |
| | | schema: useFollowUpDetailSchema(), |
| | | }); |
| | | |
| | | /** æ¥çéå®è®¢å */ |
| | | function handleViewOrder() { |
| | | if (props.contract.orderId) { |
| | | router.push({ |
| | | path: '/erp/sale/order', |
| | | query: { orderId: props.contract.orderId }, |
| | | }); |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <div> |
| | | <BaseDescriptions :data="contract" /> |
| | | <BaseDescriptions :data="contract"> |
| | | <template #orderNo="{ data }"> |
| | | <Button v-if="data.orderId" type="link" @click="handleViewOrder"> |
| | | {{ data.orderNo }} |
| | | </Button> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </BaseDescriptions> |
| | | <Divider /> |
| | | <SystemDescriptions :data="contract" /> |
| | | </div> |
| | |
| | | import { Page, useVbenModal } from '../../../packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '../../../packages/utils/src'; |
| | | |
| | | import { Button, message, Tabs } from 'ant-design-vue'; |
| | | import { Button, message, Modal, Select, Tabs, Tag, Tooltip } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteContract, |
| | | exportContract, |
| | | generateSaleOrder, |
| | | getContractApproveProcessList, |
| | | getContractPage, |
| | | submitContract, |
| | | } from '#/api/crm/contract'; |
| | |
| | | } |
| | | } |
| | | |
| | | /** æäº¤å®¡æ ¸ */ |
| | | /** æäº¤å®¡æ¹å¼¹çª */ |
| | | const submitModalVisible = ref(false); |
| | | const currentRow = ref<CrmContractApi.Contract>(); |
| | | const processList = ref<CrmContractApi.ApproveProcess[]>([]); |
| | | const selectedProcessKey = ref<string>(); |
| | | const submitLoading = ref(false); |
| | | |
| | | async function handleSubmit(row: CrmContractApi.Contract) { |
| | | const hideLoading = message.loading({ |
| | | content: 'æäº¤å®¡æ ¸ä¸...', |
| | | duration: 0, |
| | | }); |
| | | const res = await getContractApproveProcessList(); |
| | | processList.value = res; |
| | | currentRow.value = row; |
| | | selectedProcessKey.value = undefined; |
| | | submitModalVisible.value = true; |
| | | } |
| | | |
| | | async function confirmSubmit() { |
| | | if (!selectedProcessKey.value) { |
| | | message.warning('è¯·éæ©å®¡æ¹æµç¨'); |
| | | return; |
| | | } |
| | | submitLoading.value = true; |
| | | try { |
| | | await submitContract(row.id!); |
| | | message.success('æäº¤å®¡æ ¸æå'); |
| | | await submitContract(currentRow.value!.id!, selectedProcessKey.value); |
| | | message.success('æäº¤å®¡æ¹æå'); |
| | | submitModalVisible.value = false; |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | submitLoading.value = false; |
| | | } |
| | | } |
| | | |
| | |
| | | name: 'BpmProcessInstanceDetail', |
| | | query: { id: row.processInstanceId }, |
| | | }); |
| | | } |
| | | |
| | | /** çæéå®è®¢å */ |
| | | async function handleGenerateOrder(row: CrmContractApi.Contract) { |
| | | if (row.auditStatus !== 20) { |
| | | message.warning('åªæå·²å®¡æ¹çååæè½çæéå®è®¢å'); |
| | | return; |
| | | } |
| | | if (row.orderId) { |
| | | message.warning('该ååå·²çæéå®è®¢å'); |
| | | return; |
| | | } |
| | | const hideLoading = message.loading({ |
| | | content: 'æ£å¨çæéå®è®¢å...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | const orderId = await generateSaleOrder(row.id!); |
| | | message.success('çæéå®è®¢åæå'); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¥çéå®è®¢å */ |
| | | function handleViewOrder(row: CrmContractApi.Contract) { |
| | | if (row.orderId) { |
| | | push({ |
| | | name: 'ErpSaleOrder', |
| | | query: { id: row.orderId }, |
| | | }); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid> |
| | | <template #toolbar-actions> |
| | | <Tabs class="w-full" @change="handleChangeSceneType"> |
| | |
| | | {{ row.name }} |
| | | </Button> |
| | | </template> |
| | | <template #no="{ row }"> |
| | | <Tooltip v-if="row.auditStatus === 50" title="该ååå·²ä½åºï¼è®¢å已失æ"> |
| | | <span class="text-red-500 cursor-pointer" @click="handleDetail(row)"> |
| | | {{ row.no }} |
| | | </span> |
| | | </Tooltip> |
| | | <Button v-else type="link" @click="handleDetail(row)"> |
| | | {{ row.no }} |
| | | </Button> |
| | | </template> |
| | | <template #customerName="{ row }"> |
| | | <Button type="link" @click="handleCustomerDetail(row)"> |
| | | {{ row.customerName }} |
| | |
| | | {{ row.signContactName }} |
| | | </Button> |
| | | </template> |
| | | <template #auditStatus="{ row }"> |
| | | <Tag v-if="row.auditStatus === 0" color="default">æªæäº¤</Tag> |
| | | <Tag v-if="row.auditStatus === 10" color="warning">审æ¹ä¸</Tag> |
| | | <Tag v-if="row.auditStatus === 20" color="success">å®¡æ ¸éè¿</Tag> |
| | | <Tag v-if="row.auditStatus === 30" color="error">å®¡æ ¸ä¸éè¿</Tag> |
| | | <Tag v-if="row.auditStatus === 40" color="default">已忶</Tag> |
| | | <Tag v-if="row.auditStatus === 50" color="error">å·²ä½åº</Tag> |
| | | </template> |
| | | <template #orderNo="{ row }"> |
| | | <Button v-if="row.orderId" type="link" @click="handleViewOrder(row)"> |
| | | {{ row.orderNo }} |
| | | </Button> |
| | | <span v-else>-</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | ifShow: row.auditStatus === 0, |
| | | }, |
| | | { |
| | | label: 'æäº¤å®¡æ ¸', |
| | | label: 'æäº¤å®¡æ¹', |
| | | type: 'link', |
| | | auth: ['crm:contract:update'], |
| | | onClick: handleSubmit.bind(null, row), |
| | |
| | | ifShow: row.auditStatus !== 0, |
| | | }, |
| | | { |
| | | label: 'çæéå®è®¢å', |
| | | type: 'link', |
| | | auth: ['crm:contract:update'], |
| | | onClick: handleGenerateOrder.bind(null, row), |
| | | ifShow: row.auditStatus === 20 && !row.orderId, |
| | | }, |
| | | { |
| | | label: 'æ¥ç订å', |
| | | type: 'link', |
| | | auth: ['erp:sale-order:query'], |
| | | onClick: handleViewOrder.bind(null, row), |
| | | ifShow: !!row.orderId, |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | ifShow: row.auditStatus === 0, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | |
| | | <!-- æäº¤å®¡æ¹å¼¹çª --> |
| | | <Modal |
| | | v-model:open="submitModalVisible" |
| | | title="éæ©å®¡æ¹æµç¨" |
| | | width="400px" |
| | | @ok="confirmSubmit" |
| | | :confirmLoading="submitLoading" |
| | | > |
| | | <Select |
| | | v-model:value="selectedProcessKey" |
| | | placeholder="è¯·éæ©å®¡æ¹æµç¨" |
| | | class="w-full" |
| | | > |
| | | <Select.Option |
| | | v-for="item in processList" |
| | | :key="item.key" |
| | | :value="item.key" |
| | | > |
| | | {{ item.name }} |
| | | </Select.Option> |
| | | </Select> |
| | | </Modal> |
| | | </Page> |
| | | </template> |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-1/2"> |
| | | <Modal :title="getTitle" class="w-[80%]"> |
| | | <Form class="mx-4"> |
| | | <template #product="slotProps"> |
| | | <ProductEditTable |
| | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { DICT_TYPE } from '../../../../packages/constants/src'; |
| | | |
| | | /** 产å详æ
å表çåå®ä¹ */ |
| | | export function useDetailListColumns( |
| | | showBusinessPrice: boolean, |
| | | ): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { |
| | | field: 'productName', |
| | | field: 'itemName', |
| | | title: '产ååç§°', |
| | | }, |
| | | { |
| | | field: 'productNo', |
| | | title: 'äº§åæ¡ç ', |
| | | field: 'itemCode', |
| | | title: 'ç©æç¼ç ', |
| | | }, |
| | | { |
| | | field: 'productUnit', |
| | | title: '产ååä½', |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.CRM_PRODUCT_UNIT }, |
| | | }, |
| | | field: 'itemBarCode', |
| | | title: 'ç©ææ¡ç ', |
| | | }, |
| | | { |
| | | field: 'productPrice', |
| | | title: '产åä»·æ ¼ï¼å
ï¼', |
| | | field: 'itemUnitName', |
| | | title: 'åä½', |
| | | }, |
| | | { |
| | | field: 'itemPrice', |
| | | title: 'åä»·ï¼å
ï¼', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | |
| | | return [ |
| | | { type: 'seq', title: 'åºå·', minWidth: 50 }, |
| | | { |
| | | field: 'productId', |
| | | field: 'itemId', |
| | | title: '产ååç§°', |
| | | minWidth: 100, |
| | | slots: { default: 'productId' }, |
| | | slots: { default: 'itemId' }, |
| | | }, |
| | | { |
| | | field: 'productNo', |
| | | field: 'itemSpecification', |
| | | title: 'è§æ ¼åå·', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'itemBarCode', |
| | | title: 'æ¡ç ', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | field: 'productUnit', |
| | | field: 'itemUnitName', |
| | | title: 'åä½', |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.CRM_PRODUCT_UNIT }, |
| | | }, |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'productPrice', |
| | | title: 'ä»·æ ¼ï¼å
ï¼', |
| | | field: 'itemUnitName2', |
| | | title: 'è¾
åä½1', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'itemUnitName3', |
| | | title: 'è¾
åä½2', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'itemPrice', |
| | | title: 'åä»·ï¼å
ï¼', |
| | | minWidth: 100, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'sellingPrice', |
| | | title: 'å®ä»·ï¼å
ï¼', |
| | | field: 'contractPrice', |
| | | title: 'ååä»·ï¼å
ï¼', |
| | | minWidth: 100, |
| | | slots: { default: 'sellingPrice' }, |
| | | slots: { default: 'contractPrice' }, |
| | | }, |
| | | { |
| | | field: 'count', |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmBusinessApi } from '#/api/crm/business'; |
| | | import type { CrmContractApi } from '#/api/crm/contract'; |
| | | import type { CrmProductApi } from '#/api/crm/product'; |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | |
| | | import { nextTick, onMounted, ref, watch } from 'vue'; |
| | | |
| | |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { BizTypeEnum } from '#/api/crm/permission'; |
| | | import { getProductSimpleList } from '#/api/crm/product'; |
| | | import { getItemPage } from '#/api/mdm/item'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useProductEditTableColumns } from './data'; |
| | |
| | | } |
| | | |
| | | /** åæ¢äº§åæ¶åæ¥åºç¡ä¿¡æ¯ */ |
| | | function handleProductChange(productId: any, row: any) { |
| | | const product = productOptions.value.find((p) => p.id === productId); |
| | | function handleProductChange(itemId: any, row: any) { |
| | | const product = productOptions.value.find((p) => p.id === itemId); |
| | | if (!product) { |
| | | return; |
| | | } |
| | | row.productUnit = product.unit; |
| | | row.productNo = product.no; |
| | | row.productPrice = product.price; |
| | | row.sellingPrice = product.price; |
| | | row.count = 0; |
| | | row.totalPrice = 0; |
| | | row.itemId = itemId; |
| | | row.itemName = product.name; |
| | | row.itemCode = product.code; |
| | | row.itemBarCode = product.barCode; |
| | | row.itemUnitName = product.unitMeasureName; |
| | | row.itemUnitName2 = product.unitMeasureName2; |
| | | row.itemUnitName3 = product.unitMeasureName3; |
| | | row.itemSpecification = product.specification; |
| | | row.itemPrice = product.salesPrice; |
| | | row.contractPrice = product.salesPrice; |
| | | row.count = row.count || 1; |
| | | row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0; |
| | | handleUpdateValue(row); |
| | | } |
| | | |
| | | /** éé¢å卿¶éæ°è®¡ç®å计 */ |
| | | function handlePriceChange(row: any) { |
| | | row.totalPrice = erpPriceMultiply(row.sellingPrice, row.count) ?? 0; |
| | | row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0; |
| | | handleUpdateValue(row); |
| | | } |
| | | |
| | | /** å°ææ°æ°æ®ååå¹¶éç¥ç¶ç»ä»¶ */ |
| | | function handleUpdateValue(row: any) { |
| | | const index = tableData.value.findIndex((item) => item.id === row.id); |
| | | if (props.bizType === BizTypeEnum.CRM_BUSINESS) { |
| | | row.businessPrice = row.sellingPrice; |
| | | } else if (props.bizType === BizTypeEnum.CRM_CONTRACT) { |
| | | row.contractPrice = row.sellingPrice; |
| | | } |
| | | row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0; |
| | | if (index === -1) { |
| | | row.id = tableData.value.length + 1; |
| | | tableData.value.push(row); |
| | |
| | | } |
| | | await nextTick(); |
| | | tableData.value = products; |
| | | if (props.bizType === BizTypeEnum.CRM_BUSINESS) { |
| | | tableData.value.forEach((item) => { |
| | | item.sellingPrice = item.businessPrice; |
| | | }); |
| | | } else if (props.bizType === BizTypeEnum.CRM_CONTRACT) { |
| | | tableData.value.forEach((item) => { |
| | | item.sellingPrice = item.contractPrice; |
| | | }); |
| | | } |
| | | await gridApi.grid.reloadData(tableData.value); |
| | | }, |
| | | { |
| | |
| | | ); |
| | | |
| | | /** åå§å */ |
| | | const productOptions = ref<CrmProductApi.Product[]>([]); // 产å䏿é项 |
| | | const productOptions = ref<MdmItemApi.Item[]>([]); // ç©æä¸æé项 |
| | | onMounted(async () => { |
| | | productOptions.value = await getProductSimpleList(); |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | productOptions.value = res.list || []; |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Grid class="w-full"> |
| | | <template #productId="{ row }"> |
| | | <template #itemId="{ row }"> |
| | | <Select |
| | | v-model:value="row.productId" |
| | | v-model:value="row.itemId" |
| | | :options="productOptions" |
| | | :field-names="{ label: 'name', value: 'id' }" |
| | | class="w-full" |
| | | @change="handleProductChange($event, row)" |
| | | /> |
| | | </template> |
| | | <template #sellingPrice="{ row }"> |
| | | <template #contractPrice="{ row }"> |
| | | <InputNumber |
| | | v-model:value="row.sellingPrice" |
| | | v-model:value="row.contractPrice" |
| | | :min="0.001" |
| | | :precision="2" |
| | | @change="handlePriceChange(row)" |
| | |
| | | type: 'link', |
| | | danger: true, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.itemName]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmReportApi } from '#/api/crm/report'; |
| | | |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | |
| | | import { Page } from '../../../packages/effects/common-ui/src'; |
| | | |
| | | import { Card, Col, DatePicker, Row, Statistic, Table } from 'ant-design-vue'; |
| | | import dayjs from 'dayjs'; |
| | | |
| | | import { |
| | | getBusinessAnalyze, |
| | | getContractAnalyze, |
| | | getCustomerAnalyze, |
| | | getSalesFunnel, |
| | | getSalesPerformance, |
| | | } from '#/api/crm/report'; |
| | | |
| | | defineOptions({ name: 'CrmReportDashboard' }); |
| | | |
| | | const loading = ref(false); |
| | | const dateRange = ref<[string, string] | null>(null); |
| | | |
| | | const customerAnalyze = ref<CrmReportApi.CustomerAnalyze>(); |
| | | const businessAnalyze = ref<CrmReportApi.BusinessAnalyze>(); |
| | | const contractAnalyze = ref<CrmReportApi.ContractAnalyze>(); |
| | | const salesFunnel = ref<CrmReportApi.SalesFunnel>(); |
| | | const salesPerformance = ref<CrmReportApi.SalesPerformance>(); |
| | | |
| | | const startTime = computed(() => |
| | | dateRange.value?.[0] ? dayjs(dateRange.value[0]).format('YYYY-MM-DD') : undefined, |
| | | ); |
| | | const endTime = computed(() => |
| | | dateRange.value?.[1] ? dayjs(dateRange.value[1]).format('YYYY-MM-DD') : undefined, |
| | | ); |
| | | |
| | | const performanceColumns = [ |
| | | { title: 'å§å', dataIndex: 'userName', width: 120 }, |
| | | { title: 'é¨é¨', dataIndex: 'deptName', width: 150 }, |
| | | { title: 'ååæ°é', dataIndex: 'contractCount', width: 100 }, |
| | | { title: 'ååéé¢', dataIndex: 'contractPrice', width: 120, formatter: 'formatAmount2' }, |
| | | { title: '忬¾éé¢', dataIndex: 'receivablePrice', width: 120, formatter: 'formatAmount2' }, |
| | | { title: 'åæºæ°é', dataIndex: 'businessCount', width: 100 }, |
| | | { title: 'åæºéé¢', dataIndex: 'businessPrice', width: 120, formatter: 'formatAmount2' }, |
| | | ]; |
| | | |
| | | async function loadData() { |
| | | loading.value = true; |
| | | try { |
| | | const [customer, business, contract, funnel, performance] = await Promise.all([ |
| | | getCustomerAnalyze(startTime.value, endTime.value), |
| | | getBusinessAnalyze(startTime.value, endTime.value), |
| | | getContractAnalyze(startTime.value, endTime.value), |
| | | getSalesFunnel(), |
| | | getSalesPerformance(startTime.value, endTime.value), |
| | | ]); |
| | | customerAnalyze.value = customer; |
| | | businessAnalyze.value = business; |
| | | contractAnalyze.value = contract; |
| | | salesFunnel.value = funnel; |
| | | salesPerformance.value = performance; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | function handleDateChange() { |
| | | loadData(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData(); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <div class="p-4"> |
| | | <Card title="CRM æ°æ®æ¦è§" class="mb-4"> |
| | | <div class="mb-4"> |
| | | <DatePicker.RangePicker |
| | | v-model:value="dateRange" |
| | | :placeholder="['å¼å§æ¥æ', 'ç»ææ¥æ']" |
| | | format="YYYY-MM-DD" |
| | | @change="handleDateChange" |
| | | /> |
| | | </div> |
| | | </Card> |
| | | |
| | | <Row :gutter="16" class="mb-4"> |
| | | <Col :span="6"> |
| | | <Card title="客æ·åæ" :loading="loading"> |
| | | <Statistic title="æ»å®¢æ·æ°" :value="customerAnalyze?.totalCount || 0" /> |
| | | <Statistic title="仿¥æ°å¢" :value="customerAnalyze?.todayCount || 0" class="mt-2" /> |
| | | <Statistic title="æäº¤å®¢æ·" :value="customerAnalyze?.dealCount || 0" class="mt-2" /> |
| | | <Statistic |
| | | title="æäº¤ç" |
| | | :value="customerAnalyze?.dealRate || 0" |
| | | :precision="2" |
| | | suffix="%" |
| | | class="mt-2" |
| | | /> |
| | | </Card> |
| | | </Col> |
| | | <Col :span="6"> |
| | | <Card title="åæºåæ" :loading="loading"> |
| | | <Statistic title="æ»åæºæ°" :value="businessAnalyze?.totalCount || 0" /> |
| | | <Statistic title="åæºéé¢" :value="businessAnalyze?.totalPrice || 0" :precision="2" class="mt-2" /> |
| | | <Statistic title="æååæº" :value="businessAnalyze?.successCount || 0" class="mt-2" /> |
| | | <Statistic |
| | | title="æåç" |
| | | :value="businessAnalyze?.successRate || 0" |
| | | :precision="2" |
| | | suffix="%" |
| | | class="mt-2" |
| | | /> |
| | | </Card> |
| | | </Col> |
| | | <Col :span="6"> |
| | | <Card title="åååæ" :loading="loading"> |
| | | <Statistic title="æ»ååæ°" :value="contractAnalyze?.totalCount || 0" /> |
| | | <Statistic title="ååéé¢" :value="contractAnalyze?.totalPrice || 0" :precision="2" class="mt-2" /> |
| | | <Statistic title="审æ¹ä¸" :value="contractAnalyze?.processCount || 0" class="mt-2" /> |
| | | <Statistic |
| | | title="忬¾ç" |
| | | :value="contractAnalyze?.receivableRate || 0" |
| | | :precision="2" |
| | | suffix="%" |
| | | class="mt-2" |
| | | /> |
| | | </Card> |
| | | </Col> |
| | | <Col :span="6"> |
| | | <Card title="å¾
å¤ç" :loading="loading"> |
| | | <Statistic title="客æ·å
¬æµ·" :value="customerAnalyze?.poolCount || 0" /> |
| | | <Statistic title="éå®å®¢æ·" :value="customerAnalyze?.lockCount || 0" class="mt-2" /> |
| | | <Statistic title="è¿è¡ä¸åæº" :value="businessAnalyze?.processingCount || 0" class="mt-2" /> |
| | | <Statistic title="å¤±è´¥åæº" :value="businessAnalyze?.failCount || 0" class="mt-2" /> |
| | | </Card> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <Row :gutter="16"> |
| | | <Col :span="12"> |
| | | <Card title="é宿¼æ" :loading="loading"> |
| | | <Table |
| | | :dataSource="salesFunnel?.stages || []" |
| | | :columns="[ |
| | | { title: 'é¶æ®µ', dataIndex: 'name', width: 150 }, |
| | | { title: 'æ°é', dataIndex: 'count', width: 100 }, |
| | | { title: 'éé¢', dataIndex: 'price', width: 120 }, |
| | | { title: '转åç', dataIndex: 'conversionRate', width: 100, customRender: ({ text }) => `${text}%` }, |
| | | ]" |
| | | :pagination="false" |
| | | size="small" |
| | | rowKey="name" |
| | | /> |
| | | </Card> |
| | | </Col> |
| | | <Col :span="12"> |
| | | <Card title="éå®ä¸ç»©" :loading="loading"> |
| | | <Table |
| | | :dataSource="salesPerformance?.items || []" |
| | | :columns="performanceColumns" |
| | | :pagination="false" |
| | | size="small" |
| | | rowKey="userId" |
| | | /> |
| | | </Card> |
| | | </Col> |
| | | </Row> |
| | | </div> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { erpPriceMultiply } from '#/packages/utils/src'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getSimpleBusinessList } from '#/api/crm/business'; |
| | | import { getSimpleContactList } from '#/api/crm/contact'; |
| | | import { getCustomerSimpleList } from '#/api/crm/customer'; |
| | | import { getItemPage } from '#/api/mdm/item'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { useUserStore } from '#/packages/stores/src'; |
| | | |
| | | /** æ¥ä»·åç¶æ */ |
| | | export const QUOTATION_STATUS = { |
| | | DRAFT: 0, // è稿 |
| | | CONVERTED: 10, // 已转åå |
| | | }; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | const userStore = useUserStore(); |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'no', |
| | | label: 'æ¥ä»·åå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: 'ä¿åæ¶èªå¨çæ', |
| | | disabled: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'æ¥ä»·ååç§°', |
| | | component: 'Input', |
| | | rules: 'required', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æ¥ä»·ååç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'ownerUserId', |
| | | label: 'è´è´£äºº', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: getSimpleUserList, |
| | | labelField: 'nickname', |
| | | valueField: 'id', |
| | | }, |
| | | defaultValue: userStore.userInfo?.id, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'customerId', |
| | | label: '客æ·åç§°', |
| | | component: 'ApiSelect', |
| | | rules: 'required', |
| | | componentProps: { |
| | | api: getCustomerSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'businessId', |
| | | label: 'åæºåç§°', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: [], |
| | | placeholder: 'è¯·éæ©åæº', |
| | | allowClear: true, |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['customerId'], |
| | | disabled: (values) => !values.customerId, |
| | | async componentProps(values) { |
| | | if (!values.customerId) { |
| | | return { options: [], placeholder: 'è¯·éæ©å®¢æ·' }; |
| | | } |
| | | const res = await getSimpleBusinessList(); |
| | | const list = res.filter((item) => item.customerId === values.customerId); |
| | | return { |
| | | options: list.map((item) => ({ label: item.name, value: item.id })), |
| | | placeholder: 'è¯·éæ©åæº', |
| | | }; |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contactId', |
| | | label: 'è系人', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: [], |
| | | placeholder: 'è¯·éæ©è系人', |
| | | allowClear: true, |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['customerId'], |
| | | disabled: (values) => !values.customerId, |
| | | async componentProps(values) { |
| | | if (!values.customerId) { |
| | | return { options: [], placeholder: 'è¯·éæ©å®¢æ·' }; |
| | | } |
| | | const res = await getSimpleContactList(); |
| | | const list = res.filter((item) => item.customerId === values.customerId); |
| | | return { |
| | | options: list.map((item) => ({ label: item.name, value: item.id })), |
| | | placeholder: 'è¯·éæ©è系人', |
| | | }; |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'quotationTime', |
| | | label: 'æ¥ä»·æ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | placeholder: 'è¯·éæ©æ¥ä»·æ¥æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'validUntil', |
| | | label: 'æææè³', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | placeholder: 'è¯·éæ©æææ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'taxRate', |
| | | label: 'ç¨ç(%)', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | max: 100, |
| | | precision: 2, |
| | | placeholder: '请è¾å
¥ç¨ç', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'items', |
| | | label: 'ç©ææ¸
å', |
| | | component: 'Input', |
| | | formItemClass: 'col-span-3', |
| | | }, |
| | | { |
| | | fieldName: 'totalProductPrice', |
| | | label: 'ç©ææ»éé¢', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | disabled: true, |
| | | }, |
| | | rules: z.number().min(0).optional().default(0), |
| | | }, |
| | | { |
| | | fieldName: 'discountPercent', |
| | | label: 'æ´åææ£ï¼%ï¼', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | placeholder: '请è¾å
¥ææ£', |
| | | }, |
| | | rules: z.number().min(0).max(100).optional().default(0), |
| | | }, |
| | | { |
| | | fieldName: 'totalPrice', |
| | | label: 'ææ£åéé¢', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | disabled: true, |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['totalProductPrice', 'discountPercent'], |
| | | trigger(values, form) { |
| | | const discountPrice = |
| | | erpPriceMultiply(values.totalProductPrice, values.discountPercent / 100) ?? 0; |
| | | form.setFieldValue('totalPrice', values.totalProductPrice - discountPrice); |
| | | }, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'no', |
| | | label: 'æ¥ä»·åå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æ¥ä»·åå·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'æ¥ä»·ååç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æ¥ä»·ååç§°', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'customerId', |
| | | label: '客æ·', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: getCustomerSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | | options: [ |
| | | { label: 'è稿', value: 0 }, |
| | | { label: '已转åå', value: 10 }, |
| | | ], |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { |
| | | type: 'checkbox', |
| | | width: 50, |
| | | fixed: 'left', |
| | | }, |
| | | { |
| | | field: 'no', |
| | | title: 'æ¥ä»·åå·', |
| | | minWidth: 180, |
| | | fixed: 'left', |
| | | }, |
| | | { |
| | | field: 'name', |
| | | title: 'æ¥ä»·ååç§°', |
| | | minWidth: 160, |
| | | fixed: 'left', |
| | | }, |
| | | { |
| | | field: 'customerName', |
| | | title: '客æ·åç§°', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'businessName', |
| | | title: 'åæºåç§°', |
| | | minWidth: 130, |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'æ¥ä»·éé¢ï¼å
ï¼', |
| | | minWidth: 140, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'quotationTime', |
| | | title: 'æ¥ä»·æ¥æ', |
| | | minWidth: 120, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'validUntil', |
| | | title: 'æææè³', |
| | | minWidth: 120, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | minWidth: 100, |
| | | slots: { default: 'status' }, |
| | | }, |
| | | { |
| | | field: 'contractNo', |
| | | title: 'å
³èåå', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | field: 'ownerUserName', |
| | | title: 'è´è´£äºº', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | minWidth: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 260, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ç©ææç»è¡¨æ ¼å */ |
| | | export function useItemColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { type: 'seq', title: 'åºå·', minWidth: 50, fixed: 'left' }, |
| | | { |
| | | field: 'itemId', |
| | | title: 'ç©æåç§°', |
| | | minWidth: 200, |
| | | slots: { default: 'itemId' }, |
| | | }, |
| | | { |
| | | field: 'itemSpecification', |
| | | title: 'è§æ ¼åå·', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'itemBarCode', |
| | | title: 'æ¡ç ', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'itemUnitName', |
| | | title: 'åä½', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'itemUnitName2', |
| | | title: 'è¾
åä½1', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'itemUnitName3', |
| | | title: 'è¾
åä½2', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'itemPrice', |
| | | title: 'åä»·ï¼å
ï¼', |
| | | minWidth: 100, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'quotationPrice', |
| | | title: 'æ¥ä»·ï¼å
ï¼', |
| | | minWidth: 100, |
| | | slots: { default: 'quotationPrice' }, |
| | | }, |
| | | { |
| | | field: 'count', |
| | | title: 'æ°é', |
| | | minWidth: 100, |
| | | slots: { default: 'count' }, |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'å计', |
| | | minWidth: 100, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 50, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { CrmSaleQuotationApi } from '#/api/crm/saleQuotation'; |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { Button, message, Modal, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | convertSaleQuotationToContract, |
| | | deleteSaleQuotation, |
| | | getSaleQuotationPage, |
| | | } from '#/api/crm/saleQuotation'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { QUOTATION_STATUS, useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | const { push } = useRouter(); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** å建æ¥ä»·å */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** ç¼è¾æ¥ä»·å */ |
| | | function handleEdit(row: CrmSaleQuotationApi.SaleQuotation) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å 餿¥ä»·å */ |
| | | async function handleDelete(row: CrmSaleQuotationApi.SaleQuotation) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteSaleQuotation(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¥çæ¥ä»·å详æ
*/ |
| | | function handleDetail(row: CrmSaleQuotationApi.SaleQuotation) { |
| | | push({ name: 'CrmSaleQuotationDetail', params: { id: row.id } }); |
| | | } |
| | | |
| | | /** æ¥ç客æ·è¯¦æ
*/ |
| | | function handleCustomerDetail(row: CrmSaleQuotationApi.SaleQuotation) { |
| | | push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); |
| | | } |
| | | |
| | | /** æ¥çåæºè¯¦æ
*/ |
| | | function handleBusinessDetail(row: CrmSaleQuotationApi.SaleQuotation) { |
| | | if (row.businessId) { |
| | | push({ name: 'CrmBusinessDetail', params: { id: row.businessId } }); |
| | | } |
| | | } |
| | | |
| | | /** æ¥çåå详æ
*/ |
| | | function handleContractDetail(row: CrmSaleQuotationApi.SaleQuotation) { |
| | | if (row.contractId) { |
| | | push({ name: 'CrmContractDetail', params: { id: row.contractId } }); |
| | | } |
| | | } |
| | | |
| | | /** 转为åå */ |
| | | async function handleConvertContract(row: CrmSaleQuotationApi.SaleQuotation) { |
| | | Modal.confirm({ |
| | | title: '确认转åå', |
| | | content: '确认å°è¯¥æ¥ä»·å转为ååï¼', |
| | | async onOk() { |
| | | const contractId = await convertSaleQuotationToContract(row.id!); |
| | | message.success('转ååæå'); |
| | | push({ name: 'CrmContractDetail', params: { id: contractId } }); |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getSaleQuotationPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<CrmSaleQuotationApi.SaleQuotation>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['æ¥ä»·å']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['crm:sale-quotation:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #name="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)"> |
| | | {{ row.name }} |
| | | </Button> |
| | | </template> |
| | | <template #customerName="{ row }"> |
| | | <Button type="link" @click="handleCustomerDetail(row)"> |
| | | {{ row.customerName }} |
| | | </Button> |
| | | </template> |
| | | <template #businessName="{ row }"> |
| | | <Button v-if="row.businessId" type="link" @click="handleBusinessDetail(row)"> |
| | | {{ row.businessName }} |
| | | </Button> |
| | | <span v-else>-</span> |
| | | </template> |
| | | <template #contractNo="{ row }"> |
| | | <Button v-if="row.contractId" type="link" @click="handleContractDetail(row)"> |
| | | {{ row.contractNo }} |
| | | </Button> |
| | | <span v-else>-</span> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <Tag v-if="row.status === QUOTATION_STATUS.DRAFT" color="default">è稿</Tag> |
| | | <Tag v-if="row.status === QUOTATION_STATUS.CONVERTED" color="success">已转åå</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['crm:sale-quotation:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | ifShow: row.status === QUOTATION_STATUS.DRAFT, |
| | | }, |
| | | { |
| | | label: '转åå', |
| | | type: 'link', |
| | | auth: ['crm:sale-quotation:update'], |
| | | onClick: handleConvertContract.bind(null, row), |
| | | ifShow: row.status === QUOTATION_STATUS.DRAFT && !row.contractId, |
| | | }, |
| | | { |
| | | label: 'æ¥çåå', |
| | | type: 'link', |
| | | onClick: handleContractDetail.bind(null, row), |
| | | ifShow: !!row.contractId, |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | auth: ['crm:sale-quotation:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | ifShow: row.status === QUOTATION_STATUS.DRAFT, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmSaleQuotationApi } from '#/api/crm/saleQuotation'; |
| | | |
| | | import { computed, ref, watch } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { erpPriceMultiply } from '#/packages/utils/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createSaleQuotation, |
| | | getSaleQuotation, |
| | | updateSaleQuotation, |
| | | } from '#/api/crm/saleQuotation'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import ItemForm from './item-form.vue'; |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<CrmSaleQuotationApi.SaleQuotation>(); |
| | | const itemFormRef = ref(); |
| | | |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['æ¥ä»·å']) |
| | | : $t('ui.actionTitle.create', ['æ¥ä»·å']); |
| | | }); |
| | | |
| | | const isEdit = computed(() => !!formData.value?.id); |
| | | const isDisabled = computed(() => isEdit.value && formData.value?.status !== 0); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-3', |
| | | layout: 'vertical', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | /** 计ç®ç©ææ»éé¢ */ |
| | | async function calculateTotalPrice() { |
| | | const items = itemFormRef.value?.getData() || []; |
| | | const totalProductPrice = items.reduce( |
| | | (prev: number, curr: any) => prev + (curr.totalPrice || 0), |
| | | 0, |
| | | ); |
| | | await formApi.setFieldValue('totalProductPrice', totalProductPrice); |
| | | const discountPercent = (await formApi.getValues())?.discountPercent || 0; |
| | | const discountPrice = erpPriceMultiply(totalProductPrice, discountPercent / 100) ?? 0; |
| | | await formApi.setFieldValue('totalPrice', totalProductPrice - discountPrice); |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | try { |
| | | await itemFormRef.value?.validate(); |
| | | } catch (e: any) { |
| | | message.error(e.message); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as CrmSaleQuotationApi.SaleQuotation; |
| | | data.items = itemFormRef.value?.getData(); |
| | | try { |
| | | await (formData.value?.id ? updateSaleQuotation(data) : createSaleQuotation(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<CrmSaleQuotationApi.SaleQuotation>(); |
| | | if (!data || !data.id) { |
| | | formData.value = {} as CrmSaleQuotationApi.SaleQuotation; |
| | | await formApi.resetForm(); |
| | | // æ°å¢æ¶ï¼é置并添å ä¸è¡ç©ºæ°æ® |
| | | itemFormRef.value?.resetData(); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getSaleQuotation(data.id); |
| | | await formApi.setValues(formData.value); |
| | | // ç¼è¾æ¶è®¾ç½®ç©ææ°æ® |
| | | itemFormRef.value?.setData(formData.value.items || []); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-[80%]"> |
| | | <Form class="mx-4"> |
| | | <template #items> |
| | | <ItemForm |
| | | ref="itemFormRef" |
| | | class="w-full" |
| | | :disabled="isDisabled" |
| | | @change="calculateTotalPrice" |
| | | /> |
| | | </template> |
| | | </Form> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | import type { CrmSaleQuotationApi } from '#/api/crm/saleQuotation'; |
| | | |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | |
| | | import { erpPriceMultiply } from '#/packages/utils/src'; |
| | | |
| | | import { InputNumber, Select } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getItemPage } from '#/api/mdm/item'; |
| | | |
| | | import { useItemColumns } from '../data'; |
| | | |
| | | interface Props { |
| | | disabled?: boolean; |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | disabled: false, |
| | | }); |
| | | |
| | | const emit = defineEmits(['change']); |
| | | |
| | | const tableData = ref<CrmSaleQuotationApi.SaleQuotationItem[]>([]); |
| | | const itemOptions = ref<MdmItemApi.Item[]>([]); |
| | | |
| | | /** è·åè¡¨æ ¼åè®¡æ°æ® */ |
| | | const summaries = computed(() => { |
| | | return tableData.value.reduce( |
| | | (acc, item) => { |
| | | acc.count += item.count || 0; |
| | | acc.totalPrice += item.totalPrice || 0; |
| | | return acc; |
| | | }, |
| | | { count: 0, totalPrice: 0 }, |
| | | ); |
| | | }); |
| | | |
| | | /** è¡¨æ ¼é
ç½® */ |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: useItemColumns(), |
| | | data: [], |
| | | minHeight: 250, |
| | | autoResize: true, |
| | | border: true, |
| | | rowConfig: { |
| | | keyField: 'seq', |
| | | isHover: true, |
| | | }, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | toolbarConfig: { |
| | | enabled: false, |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | /** 触ååæ´äºä»¶ */ |
| | | function triggerChange() { |
| | | emit('change'); |
| | | } |
| | | |
| | | /** å¤çæ°å¢ */ |
| | | function handleAdd() { |
| | | tableData.value.push({ |
| | | itemId: undefined, |
| | | itemName: undefined, |
| | | itemCode: undefined, |
| | | itemBarCode: undefined, |
| | | itemSpecification: undefined, |
| | | itemUnitName: undefined, |
| | | itemUnitName2: undefined, |
| | | itemUnitName3: undefined, |
| | | itemPrice: undefined, |
| | | quotationPrice: undefined, |
| | | count: 1, |
| | | totalPrice: 0, |
| | | }); |
| | | gridApi.grid.loadData(tableData.value); |
| | | } |
| | | |
| | | /** å¤çå é¤ */ |
| | | function handleDelete(row: CrmSaleQuotationApi.SaleQuotationItem) { |
| | | const index = tableData.value.findIndex((item) => item.seq === row.seq); |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1); |
| | | gridApi.grid.loadData(tableData.value); |
| | | triggerChange(); |
| | | } |
| | | } |
| | | |
| | | /** å¤çç©æåæ´ */ |
| | | function handleItemChange(itemId: number, row: any) { |
| | | const item = itemOptions.value.find((p) => p.id === itemId); |
| | | if (!item) { |
| | | return; |
| | | } |
| | | row.itemId = itemId; |
| | | row.itemName = item.name; |
| | | row.itemCode = item.code; |
| | | row.itemBarCode = item.barCode; |
| | | row.itemSpecification = item.specification; |
| | | row.itemUnitName = item.unitMeasureName; |
| | | row.itemUnitName2 = item.unitMeasureName2; |
| | | row.itemUnitName3 = item.unitMeasureName3; |
| | | row.itemPrice = item.salesPrice; |
| | | row.quotationPrice = item.salesPrice; |
| | | row.totalPrice = erpPriceMultiply(row.quotationPrice, row.count) ?? 0; |
| | | triggerChange(); |
| | | } |
| | | |
| | | /** å¤çæ¥ä»·åæ´ */ |
| | | function handleQuotationPriceChange(value: number, row: any) { |
| | | row.quotationPrice = value; |
| | | row.totalPrice = erpPriceMultiply(value, row.count) ?? 0; |
| | | triggerChange(); |
| | | } |
| | | |
| | | /** å¤çæ°éåæ´ */ |
| | | function handleCountChange(value: number, row: any) { |
| | | row.count = value; |
| | | row.totalPrice = erpPriceMultiply(row.quotationPrice, value) ?? 0; |
| | | triggerChange(); |
| | | } |
| | | |
| | | /** è¡¨åæ ¡éª */ |
| | | function validate() { |
| | | for (let i = 0; i < tableData.value.length; i++) { |
| | | const item = tableData.value[i]; |
| | | if (item) { |
| | | if (!item.itemId) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼ç©æä¸è½ä¸ºç©º`); |
| | | } |
| | | if (!item.count || item.count <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼æ°éä¸è½ä¸ºç©º`); |
| | | } |
| | | if (!item.quotationPrice || item.quotationPrice <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼æ¥ä»·ä¸è½ä¸ºç©º`); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** è®¾ç½®æ°æ®ï¼ä¾ç¶ç»ä»¶è°ç¨ï¼ */ |
| | | function setData(items: CrmSaleQuotationApi.SaleQuotationItem[]) { |
| | | items.forEach((item) => { |
| | | if (item.quotationPrice && item.count) { |
| | | item.totalPrice = erpPriceMultiply(item.quotationPrice, item.count) ?? 0; |
| | | } |
| | | }); |
| | | tableData.value = [...items]; |
| | | gridApi.grid.loadData(tableData.value); |
| | | } |
| | | |
| | | /** è·åæ°æ® */ |
| | | function getData() { |
| | | return [...tableData.value]; |
| | | } |
| | | |
| | | /** éç½®æ°æ® */ |
| | | function resetData() { |
| | | tableData.value = []; |
| | | handleAdd(); |
| | | } |
| | | |
| | | defineExpose({ |
| | | validate, |
| | | setData, |
| | | getData, |
| | | resetData, |
| | | }); |
| | | |
| | | /** åå§å */ |
| | | onMounted(async () => { |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | itemOptions.value = res.list || []; |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Grid class="w-full"> |
| | | <template #itemId="{ row }"> |
| | | <Select |
| | | v-if="!disabled" |
| | | v-model:value="row.itemId" |
| | | :options="itemOptions" |
| | | :field-names="{ label: 'name', value: 'id' }" |
| | | class="w-full" |
| | | placeholder="è¯·éæ©ç©æ" |
| | | @change="(val) => handleItemChange(val, row)" |
| | | /> |
| | | <span v-else>{{ row.itemName || '-' }}</span> |
| | | </template> |
| | | <template #quotationPrice="{ row }"> |
| | | <InputNumber |
| | | v-if="!disabled" |
| | | v-model:value="row.quotationPrice" |
| | | :min="0" |
| | | :precision="2" |
| | | @change="(val) => handleQuotationPriceChange(val, row)" |
| | | /> |
| | | <span v-else>{{ row.quotationPrice || '-' }}</span> |
| | | </template> |
| | | <template #count="{ row }"> |
| | | <InputNumber |
| | | v-if="!disabled" |
| | | v-model:value="row.count" |
| | | :min="0.001" |
| | | :precision="3" |
| | | @change="(val) => handleCountChange(val, row)" |
| | | /> |
| | | <span v-else>{{ row.count || '-' }}</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | v-if="!disabled" |
| | | :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>æ°éï¼{{ summaries.count }}</span> |
| | | <span>éé¢ï¼{{ summaries.totalPrice.toFixed(2) }}å
</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <TableAction |
| | | v-if="!disabled" |
| | | class="mt-2 flex justify-center" |
| | | :actions="[ |
| | | { |
| | | label: 'æ·»å ç©æ', |
| | | type: 'default', |
| | | onClick: handleAdd, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| | |
| | | |
| | | /** ç¶æå¸¸é */ |
| | | export const PURCHASE_REQUEST_STATUS = { |
| | | PENDING: 0, // å¾
å®¡æ¹ |
| | | APPROVED: 1, // å·²å®¡æ¹ |
| | | REJECTED: 2, // å·²æç» |
| | | CONVERTED: 3, // 已转订å |
| | | DRAFT: 0, // æªæäº¤ï¼èç¨¿ï¼ |
| | | PROCESSING: 10, // 审æ¹ä¸ |
| | | APPROVED: 20, // å®¡æ ¸éè¿ |
| | | REJECTED: 30, // å®¡æ ¸ä¸éè¿ |
| | | CANCELLED: 40, // 已忶 |
| | | }; |
| | | |
| | | /** 表åçé
置项 */ |
| | |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | label: 'ä¾åºå', |
| | | label: 'ä¾åºåï¼å¯éï¼', |
| | | fieldName: 'supplierId', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'reason', |
| | | fieldName: 'discountPercent', |
| | | label: '伿 ç(%)', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¼æ ç', |
| | | min: 0, |
| | | max: 100, |
| | | precision: 2, |
| | | disabled: formType === 'detail', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'requestReason', |
| | | label: 'ç³è¯·çç±', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç³è¯·çç±', |
| | | autoSize: { minRows: 1, maxRows: 1 }, |
| | | disabled: formType === 'detail', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'fileUrl', |
| | | label: 'éä»¶', |
| | | component: 'FileUpload', |
| | | componentProps: { |
| | | placeholder: '请ä¸ä¼ éä»¶', |
| | | maxNumber: 5, |
| | | maxSize: 20, |
| | | accept: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'png'], |
| | | disabled: formType === 'detail', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'requestCount', |
| | | title: 'ç³è¯·æ°é', |
| | | field: 'count', |
| | | title: 'æ°é', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | slots: { default: 'requestCount' }, |
| | | slots: { default: 'count' }, |
| | | }, |
| | | { |
| | | field: 'estimatedPrice', |
| | | title: 'é¢ä¼°åä»·', |
| | | field: 'productPrice', |
| | | title: 'åä»·ï¼åèä»·ï¼', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | slots: { default: 'estimatedPrice' }, |
| | | slots: { default: 'productPrice' }, |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'requiredDate', |
| | | field: 'taxPercent', |
| | | title: 'ç¨ç(%)', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'taxPercent' }, |
| | | }, |
| | | { |
| | | field: 'demandTime', |
| | | title: 'éæ±æ¥æ', |
| | | minWidth: 140, |
| | | fixed: 'right', |
| | | slots: { default: 'requiredDate' }, |
| | | slots: { default: 'demandTime' }, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'requesterId', |
| | | fieldName: 'requestUserId', |
| | | label: 'ç³è¯·äºº', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: [ |
| | | { label: 'å¾
审æ¹', value: 0 }, |
| | | { label: '已审æ¹', value: 1 }, |
| | | { label: 'å·²æç»', value: 2 }, |
| | | { label: '已转订å', value: 3 }, |
| | | { label: 'æªæäº¤', value: 0 }, |
| | | { label: '审æ¹ä¸', value: 10 }, |
| | | { label: 'å®¡æ ¸éè¿', value: 20 }, |
| | | { label: 'å®¡æ ¸ä¸éè¿', value: 30 }, |
| | | { label: '已忶', value: 40 }, |
| | | ], |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'requesterName', |
| | | field: 'requestUserName', |
| | | title: 'ç³è¯·äºº', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'estimatedAmount', |
| | | field: 'requestDeptName', |
| | | title: 'ç³è¯·é¨é¨', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'totalCount', |
| | | title: 'ç³è¯·æ°é', |
| | | minWidth: 120, |
| | | formatter: 'formatAmount3', |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'é¢ä¼°éé¢', |
| | | formatter: 'formatAmount2', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'productNames', |
| | | title: 'ç³è¯·äº§å', |
| | | minWidth: 150, |
| | | showOverflow: 'tooltip', |
| | | }, |
| | | { |
| | | field: 'orderNo', |
| | | title: 'éè´è®¢å', |
| | | minWidth: 180, |
| | | }, |
| | | { |
| | | field: 'status', |
| | |
| | | slots: { default: 'status' }, |
| | | }, |
| | | { |
| | | field: 'reason', |
| | | field: 'requestReason', |
| | | title: 'ç³è¯·çç±', |
| | | minWidth: 150, |
| | | showOverflow: 'tooltip', |
| | |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | width: 240, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src'; |
| | | import { confirm, 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, |
| | | getApproveProcessList, |
| | | getPurchaseRequestPage, |
| | | submitPurchaseRequest, |
| | | } from '#/api/erp/purchase/request'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema, PURCHASE_REQUEST_STATUS } from './data'; |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | import OrderForm from './modules/order-form.vue'; |
| | | import ProcessSelectModal from './modules/process-select-modal.vue'; |
| | | |
| | | /** ERP éè´ç³è¯·å表 */ |
| | | defineOptions({ name: 'ErpPurchaseRequest' }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [OrderFormModal, orderFormModalApi] = useVbenModal({ |
| | | connectedComponent: OrderForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ProcessModal, processModalApi] = useVbenModal({ |
| | | connectedComponent: ProcessSelectModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** 审æ¹éè´ç³è¯· */ |
| | | async function handleApprove(row: ErpPurchaseRequestApi.PurchaseRequest, status: number) { |
| | | /** æäº¤å®¡æ¹ - å
è·åæµç¨å表 */ |
| | | async function handleSubmit(row: ErpPurchaseRequestApi.PurchaseRequest) { |
| | | const hideLoading = message.loading({ |
| | | content: `ç¡®å®${status === 1 ? '审æ¹éè¿' : 'å®¡æ¹æç»'}该ç³è¯·åï¼`, |
| | | content: 'è·åå®¡æ¹æµç¨...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await approvePurchaseRequest({ id: row.id!, status, remark: '' }); |
| | | message.success(`${status === 1 ? '审æ¹éè¿' : 'å®¡æ¹æç»'}æå`); |
| | | handleRefresh(); |
| | | } finally { |
| | | const processList = await getApproveProcessList(row.id!); |
| | | hideLoading(); |
| | | if (!processList || processList.length === 0) { |
| | | message.warning('ææ å¯ç¨å®¡æ¹æµç¨ï¼è¯·å
é
ç½® BPM æµç¨'); |
| | | return; |
| | | } |
| | | // æå¼æµç¨éæ©å¼¹çª |
| | | processModalApi.setData({ id: row.id, processList }).open(); |
| | | } catch { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** 转为éè´è®¢å */ |
| | | async function handleConvert(row: ErpPurchaseRequestApi.PurchaseRequest) { |
| | | const hideLoading = message.loading({ |
| | | content: 'æ£å¨è½¬ä¸ºéè´è®¢å...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await convertToPurchaseOrder(row.id!); |
| | | message.success('å·²æå转为éè´è®¢å'); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | /** çæéè´è®¢å */ |
| | | function handleGenerateOrder(row: ErpPurchaseRequestApi.PurchaseRequest) { |
| | | orderFormModalApi.setData({ id: row.id, supplierId: row.supplierId }).open(); |
| | | } |
| | | |
| | | const checkedIds = ref<number[]>([]); |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <OrderFormModal @success="handleRefresh" /> |
| | | <ProcessModal @success="handleRefresh" /> |
| | | <Grid table-title="éè´ç³è¯·å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | /> |
| | | </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> |
| | | <Tag v-if="row.status === 0" color="default">æªæäº¤</Tag> |
| | | <Tag v-else-if="row.status === 10" color="processing">审æ¹ä¸</Tag> |
| | | <Tag v-else-if="row.status === 20" color="success">å®¡æ ¸éè¿</Tag> |
| | | <Tag v-else-if="row.status === 30" color="error">å®¡æ ¸ä¸éè¿</Tag> |
| | | <Tag v-else-if="row.status === 40" color="warning">已忶</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: '审æ¹éè¿', |
| | | label: 'æäº¤', |
| | | type: 'link', |
| | | auth: ['erp:purchase-request:approve'], |
| | | ifShow: () => row.status === 0, |
| | | popConfirm: { |
| | | title: '确认审æ¹éè¿è¯¥ç³è¯·åï¼', |
| | | confirm: handleApprove.bind(null, row, 1), |
| | | }, |
| | | ifShow: () => row.status === 0 || row.status === 30, |
| | | onClick: handleSubmit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'å®¡æ¹æç»', |
| | | 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), |
| | | }, |
| | | auth: ['erp:purchase-request:generate-order'], |
| | | ifShow: () => row.status === 20 && !row.orderId, |
| | | onClick: handleGenerateOrder.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| | | </template> |
| | |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | |
| | | } from '#/api/erp/purchase/request'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema, PURCHASE_REQUEST_STATUS } from '../data'; |
| | | import { useFormSchema } from '../data'; |
| | | import PurchaseRequestItemForm from './item-form.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | |
| | | const data = |
| | | (await formApi.getValues()) as ErpPurchaseRequestApi.PurchaseRequest; |
| | | data.items = formData.value?.items?.map((item) => ({ |
| | | ...item, |
| | | id: undefined, |
| | | productId: item.productId, |
| | | productUnitId: item.productUnitId, |
| | | productPrice: item.productPrice, |
| | | count: item.count, |
| | | taxPercent: item.taxPercent, |
| | | demandTime: item.demandTime, |
| | | remark: item.remark, |
| | | })); |
| | | try { |
| | | await (formType.value === 'create' |
| | |
| | | /** è·åè¡¨æ ¼åè®¡æ°æ® */ |
| | | const summaries = computed(() => { |
| | | return { |
| | | count: tableData.value.reduce((sum, item) => sum + (item.requestCount || 0), 0), |
| | | count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0), |
| | | totalPrice: tableData.value.reduce( |
| | | (sum, item) => { |
| | | const price = erpPriceMultiply(item.estimatedPrice || 0, item.requestCount || 0) ?? 0; |
| | | const price = erpPriceMultiply(item.productPrice || 0, item.count || 0) ?? 0; |
| | | return sum + price; |
| | | }, |
| | | 0, |
| | |
| | | const newRow = { |
| | | id: undefined, |
| | | productId: undefined, |
| | | productUnitId: undefined, |
| | | productUnitName: undefined, |
| | | productBarCode: undefined, |
| | | requestCount: 1, |
| | | estimatedPrice: undefined, |
| | | requiredDate: undefined, |
| | | count: 1, |
| | | productPrice: undefined, |
| | | taxPercent: undefined, |
| | | demandTime: undefined, |
| | | remark: undefined, |
| | | totalPrice: 0, |
| | | }; |
| | |
| | | } |
| | | row.productId = productId; |
| | | row.productBarCode = product.barCode; |
| | | row.productUnitId = product.unitMeasureId; |
| | | row.productUnitName = product.unitMeasureName; |
| | | row.productName = product.name; |
| | | row.estimatedPrice = product.purchasePrice || 0; |
| | | row.requestCount = row.requestCount || 1; |
| | | row.productPrice = product.purchasePrice || 0; |
| | | row.count = row.count || 1; |
| | | handleRowChange(row); |
| | | } |
| | | |
| | |
| | | tableData.value[index] = row; |
| | | } |
| | | // 计ç®é¢ä¼°éé¢ |
| | | if (row.estimatedPrice && row.requestCount) { |
| | | row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0; |
| | | if (row.productPrice && row.count) { |
| | | row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; |
| | | } |
| | | emit('update:items', [...tableData.value]); |
| | | } |
| | | |
| | | /** åå§åè¡æ°æ® */ |
| | | function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) { |
| | | if (row.estimatedPrice && row.requestCount) { |
| | | row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0; |
| | | if (row.productPrice && row.count) { |
| | | row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; |
| | | } |
| | | } |
| | | |
| | |
| | | if (!item.productId) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼äº§åä¸è½ä¸ºç©º`); |
| | | } |
| | | if (!item.requestCount || item.requestCount <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼ç³è¯·æ°éä¸è½ä¸ºç©º`); |
| | | if (!item.count || item.count <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼æ°éä¸è½ä¸ºç©º`); |
| | | } |
| | | } |
| | | } |
| | |
| | | /> |
| | | <span v-else>{{ row.productName || '-' }}</span> |
| | | </template> |
| | | <template #requestCount="{ row }"> |
| | | <template #count="{ row }"> |
| | | <InputNumber |
| | | v-if="!disabled" |
| | | v-model:value="row.requestCount" |
| | | v-model:value="row.count" |
| | | :min="0" |
| | | :precision="3" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | <span v-else>{{ erpCountInputFormatter(row.requestCount) || '-' }}</span> |
| | | <span v-else>{{ erpCountInputFormatter(row.count) || '-' }}</span> |
| | | </template> |
| | | <template #estimatedPrice="{ row }"> |
| | | <template #productPrice="{ row }"> |
| | | <InputNumber |
| | | v-if="!disabled" |
| | | v-model:value="row.estimatedPrice" |
| | | v-model:value="row.productPrice" |
| | | :min="0" |
| | | :precision="2" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | <span v-else>{{ erpPriceInputFormatter(row.estimatedPrice) || '-' }}</span> |
| | | <span v-else>{{ erpPriceInputFormatter(row.productPrice) || '-' }}</span> |
| | | </template> |
| | | <template #requiredDate="{ row }"> |
| | | <template #taxPercent="{ row }"> |
| | | <InputNumber |
| | | v-if="!disabled" |
| | | v-model:value="row.taxPercent" |
| | | :min="0" |
| | | :max="100" |
| | | :precision="2" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | <span v-else>{{ row.taxPercent ? `${row.taxPercent}%` : '-' }}</span> |
| | | </template> |
| | | <template #demandTime="{ row }"> |
| | | <DatePicker |
| | | v-if="!disabled" |
| | | v-model:value="row.requiredDate" |
| | | v-model:value="row.demandTime" |
| | | valueFormat="YYYY-MM-DD" |
| | | class="w-full" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | <span v-else>{{ row.requiredDate || '-' }}</span> |
| | | <span v-else>{{ row.demandTime || '-' }}</span> |
| | | </template> |
| | | <template #remark="{ row }"> |
| | | <Input v-if="!disabled" v-model:value="row.remark" class="w-full" @change="handleRowChange(row)" /> |
| | |
| | | <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>æ°éï¼{{ erpCountInputFormatter(summaries.count) }}</span> |
| | | <span>é¢ä¼°éé¢ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }}</span> |
| | | </div> |
| | | </div> |
| | |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request'; |
| | | |
| | | import { ref, computed } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message, Select } from 'ant-design-vue'; |
| | | |
| | | import { generatePurchaseOrder } from '#/api/erp/purchase/request'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const requestId = ref<number>(); |
| | | const supplierId = ref<number>(); |
| | | const supplierOptions = ref<{ id: number; name: string }[]>([]); |
| | | |
| | | const supplierSelectOptions = computed(() => { |
| | | return supplierOptions.value.map((s) => ({ |
| | | label: s.name, |
| | | value: s.id, |
| | | })); |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (!supplierId.value) { |
| | | message.warning('è¯·éæ©ä¾åºå'); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | const orderId = await generatePurchaseOrder(requestId.value!, supplierId.value!); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success(`çæéè´è®¢åæåï¼è®¢åç¼å·ï¼${orderId}`); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | requestId.value = undefined; |
| | | supplierId.value = undefined; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ id: number; supplierId?: number }>(); |
| | | requestId.value = data.id; |
| | | supplierId.value = data.supplierId; |
| | | // è·åä¾åºåå表 |
| | | supplierOptions.value = await getSupplierSimpleList(); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="çæéè´è®¢å" class="w-1/3"> |
| | | <div class="mb-4"> |
| | | <span class="mr-2">éæ©ä¾åºåï¼</span> |
| | | <Select |
| | | v-model:value="supplierId" |
| | | :options="supplierSelectOptions" |
| | | style="width: 200px" |
| | | placeholder="è¯·éæ©ä¾åºå" |
| | | show-search |
| | | :filter-option="(input: string, option: any) => option.label.toLowerCase().includes(input.toLowerCase())" |
| | | /> |
| | | </div> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request'; |
| | | |
| | | import { ref, computed } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message, Select } from 'ant-design-vue'; |
| | | |
| | | import { submitPurchaseRequest } from '#/api/erp/purchase/request'; |
| | | |
| | | interface ProcessDefinition { |
| | | id: string; |
| | | name: string; |
| | | version: number; |
| | | key: string; |
| | | } |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const requestId = ref<number>(); |
| | | const processList = ref<ProcessDefinition[]>([]); |
| | | const selectedProcessKey = ref<string>(''); |
| | | |
| | | const processOptions = computed(() => { |
| | | return processList.value.map((p) => ({ |
| | | label: `${p.name} (V${p.version})`, |
| | | value: p.key, |
| | | })); |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (!selectedProcessKey.value) { |
| | | message.warning('è¯·éæ©å®¡æ¹æµç¨'); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | await submitPurchaseRequest(requestId.value!, selectedProcessKey.value); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success('æäº¤æå'); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | requestId.value = undefined; |
| | | selectedProcessKey.value = ''; |
| | | processList.value = []; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ id: number; processList: ProcessDefinition[] }>(); |
| | | requestId.value = data.id; |
| | | processList.value = data.processList || []; |
| | | // é»è®¤éæ©ç¬¬ä¸ä¸ªæµç¨ |
| | | if (processList.value.length > 0) { |
| | | selectedProcessKey.value = processList.value[0].key; |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="éæ©å®¡æ¹æµç¨" class="w-1/3"> |
| | | <div class="mb-4"> |
| | | <span class="mr-2">å®¡æ¹æµç¨ï¼</span> |
| | | <Select |
| | | v-model:value="selectedProcessKey" |
| | | :options="processOptions" |
| | | style="width: 200px" |
| | | placeholder="è¯·éæ©å®¡æ¹æµç¨" |
| | | /> |
| | | </div> |
| | | </Modal> |
| | | </template> |
| | |
| | | rules: z.number().default(0), |
| | | }, |
| | | { |
| | | fieldName: 'sort', |
| | | label: 'æåº', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æåº', |
| | | min: 0, |
| | | class: '!w-full', |
| | | }, |
| | | rules: z.number().default(0), |
| | | }, |
| | | { |
| | | fieldName: 'qualification', |
| | | label: 'èµè´¨ä¿¡æ¯', |
| | | component: 'Textarea', |
| | |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | labelWidth: 100, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | |
| | | 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 { z } from '#/adapter/form'; |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | | import { getProductSimpleList } from '#/api/erp/product/product'; |
| | | import { getCustomerSimpleList } from '#/api/erp/sale/customer'; |
| | | import { getItemPage } from '#/api/mdm/item'; |
| | | import { getCustomerSimpleList as getCrmCustomerSimpleList } from '#/api/crm/customer'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getCustomerSimpleList, |
| | | api: getCrmCustomerSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | slots: { default: 'productId' }, |
| | | }, |
| | | { |
| | | field: 'productSpecification', |
| | | title: 'è§æ ¼åå·', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'stockCount', |
| | | title: 'åºå', |
| | | minWidth: 80, |
| | |
| | | { |
| | | field: 'productUnitName', |
| | | title: 'åä½', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'productUnitName2', |
| | | title: 'è¾
åä½1', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'productUnitName3', |
| | | title: 'è¾
åä½2', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | |
| | | placeholder: 'è¯·éæ©äº§å', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getProductSimpleList, |
| | | api: async () => { |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | return res.list || []; |
| | | }, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getCustomerSimpleList, |
| | | api: getCrmCustomerSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), |
| | | options: [ |
| | | { label: 'æªå®¡æ ¸', value: 10 }, |
| | | { label: 'å·²å®¡æ ¸', value: 20 }, |
| | | { label: 'å·²ä½åº', value: 30 }, |
| | | ], |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | | }, |
| | |
| | | title: '订ååå·', |
| | | width: 200, |
| | | fixed: 'left', |
| | | slots: { default: 'no' }, |
| | | }, |
| | | { |
| | | field: 'productNames', |
| | |
| | | field: 'customerName', |
| | | title: '客æ·', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'contractNo', |
| | | title: 'å
³èåå', |
| | | minWidth: 150, |
| | | slots: { default: 'contractNo' }, |
| | | }, |
| | | { |
| | | field: 'orderTime', |
| | |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | minWidth: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, |
| | | }, |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'status' }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | |
| | | import type { ErpSaleOrderApi } from '#/api/erp/sale/order'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | 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, Tooltip } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | cancelSaleOrder, |
| | | deleteSaleOrder, |
| | | enableSaleOrder, |
| | | exportSaleOrder, |
| | | getSaleOrderPage, |
| | | updateSaleOrderStatus, |
| | |
| | | |
| | | /** ERP éå®è®¢åå表 */ |
| | | defineOptions({ name: 'ErpSaleOrder' }); |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | |
| | | } |
| | | } |
| | | |
| | | /** ä½åºéå®è®¢å */ |
| | | async function handleCancel(row: ErpSaleOrderApi.SaleOrder) { |
| | | const hideLoading = message.loading({ |
| | | content: 'æ£å¨ä½åºè¯¥è®¢å...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await cancelSaleOrder(row.id!); |
| | | message.success('ä½åºæå'); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** å¯ç¨éå®è®¢åï¼æ¢å¤å·²ä½åºè®¢åï¼ */ |
| | | async function handleEnable(row: ErpSaleOrderApi.SaleOrder) { |
| | | const hideLoading = message.loading({ |
| | | content: 'æ£å¨å¯ç¨è¯¥è®¢å...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await enableSaleOrder(row.id!); |
| | | message.success('å¯ç¨æå'); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const checkedIds = ref<number[]>([]); |
| | | function handleRowCheckboxChange({ |
| | | records, |
| | |
| | | /** æ¥ç详æ
*/ |
| | | function handleDetail(row: ErpSaleOrderApi.SaleOrder) { |
| | | formModalApi.setData({ formType: 'detail', id: row.id }).open(); |
| | | } |
| | | |
| | | /** æ¥çå
³èåå */ |
| | | function handleViewContract(row: ErpSaleOrderApi.SaleOrder) { |
| | | if (row.contractId) { |
| | | router.push({ |
| | | path: '/crm/contract/detail', |
| | | query: { id: row.contractId }, |
| | | }); |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #no="{ row }"> |
| | | <Tooltip v-if="row.status === 30" title="该订åå·²ä½åºï¼è®¢å已失æ"> |
| | | <span class="text-red-500 cursor-pointer" @click="handleDetail(row)"> |
| | | {{ row.no }} |
| | | </span> |
| | | </Tooltip> |
| | | <a v-else class="text-primary cursor-pointer" @click="handleDetail(row)"> |
| | | {{ row.no }} |
| | | </a> |
| | | </template> |
| | | <template #contractNo="{ row }"> |
| | | <a v-if="row.contractId" class="text-primary cursor-pointer" @click="handleViewContract(row)"> |
| | | {{ row.contractNo }} |
| | | </a> |
| | | <span v-else>-</span> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <Tag v-if="row.status === 10" color="warning">æªå®¡æ ¸</Tag> |
| | | <Tag v-if="row.status === 20" color="success">å·²å®¡æ ¸</Tag> |
| | | <Tag v-if="row.status === 30" color="error">å·²ä½åº</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['erp:sale-order:update'], |
| | | ifShow: () => row.status !== 20, |
| | | ifShow: () => row.status === 10, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: row.status === 10 ? '审æ¹' : 'å审æ¹', |
| | | label: '审æ¹', |
| | | type: 'link', |
| | | icon: ACTION_ICON.AUDIT, |
| | | auth: ['erp:sale-order:update-status'], |
| | | ifShow: () => row.status === 10, |
| | | popConfirm: { |
| | | title: `确认${row.status === 10 ? '审æ¹' : 'å审æ¹'}${row.no}åï¼`, |
| | | confirm: handleUpdateStatus.bind( |
| | | null, |
| | | row, |
| | | row.status === 10 ? 20 : 10, |
| | | ), |
| | | title: `确认审æ¹${row.no}åï¼`, |
| | | confirm: handleUpdateStatus.bind(null, row, 20), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'å审æ¹', |
| | | type: 'link', |
| | | icon: ACTION_ICON.AUDIT, |
| | | auth: ['erp:sale-order:update-status'], |
| | | ifShow: () => row.status === 20, |
| | | popConfirm: { |
| | | title: `确认å审æ¹${row.no}åï¼`, |
| | | confirm: handleUpdateStatus.bind(null, row, 10), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'ä½åº', |
| | | type: 'link', |
| | | icon: 'ant-design:stop-outlined', |
| | | auth: ['erp:sale-order:cancel'], |
| | | ifShow: () => row.status === 20, |
| | | popConfirm: { |
| | | title: `确认ä½åº${row.no}åï¼ä½åºåä¸å¯æ¢å¤ï¼`, |
| | | confirm: handleCancel.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'å¯ç¨', |
| | | type: 'link', |
| | | icon: 'ant-design:check-circle-outlined', |
| | | auth: ['erp:sale-order:enable'], |
| | | ifShow: () => row.status === 30, |
| | | popConfirm: { |
| | | title: `确认å¯ç¨${row.no}åï¼`, |
| | | confirm: handleEnable.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['erp:sale-order:delete'], |
| | | ifShow: () => row.status === 10, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.no]), |
| | | confirm: handleDelete.bind(null, [row.id!]), |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpProductApi } from '#/api/erp/product/product'; |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | import type { ErpSaleOrderApi } from '#/api/erp/sale/order'; |
| | | |
| | | import { computed, nextTick, onMounted, ref, watch } from 'vue'; |
| | |
| | | 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'; |
| | |
| | | ]); |
| | | |
| | | const tableData = ref<ErpSaleOrderApi.SaleOrderItem[]>([]); // è¡¨æ ¼æ°æ® |
| | | const productOptions = ref<ErpProductApi.Product[]>([]); // 产å䏿é项 |
| | | const productOptions = ref<MdmItemApi.Item[]>([]); // ç©æä¸æé项 |
| | | |
| | | /** è·åè¡¨æ ¼åè®¡æ°æ® */ |
| | | const summaries = computed(() => { |
| | |
| | | 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.productUnitName2 = product.unitMeasureName2; |
| | | row.productUnitName3 = product.unitMeasureName3; |
| | | row.productSpecification = product.specification; |
| | | row.productName = product.name; |
| | | row.stockCount = (await getStockCount(productId)) || 0; |
| | | row.productPrice = product.salePrice || 0; |
| | | row.productPrice = product.salesPrice || 0; |
| | | row.count = row.count || 1; |
| | | handleRowChange(row); |
| | | } |
| | |
| | | |
| | | /** åå§å */ |
| | | 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(); |
| | |
| | | <Grid class="w-full"> |
| | | <template #productId="{ row }"> |
| | | <Select |
| | | v-if="!disabled" |
| | | v-model:value="row.productId" |
| | | :options="productOptions" |
| | | :field-names="{ label: 'name', value: 'id' }" |
| | |
| | | show-search |
| | | @change="handleProductChange($event, row)" |
| | | /> |
| | | <span v-else>{{ row.productName || '-' }}</span> |
| | | </template> |
| | | <template #count="{ row }"> |
| | | <InputNumber |
| | |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | | import { getProductSimpleList } from '#/api/erp/product/product'; |
| | | import { getCustomerSimpleList } from '#/api/erp/sale/customer'; |
| | | import { getItemPage } from '#/api/mdm/item'; |
| | | import { getCustomerSimpleList as getCrmCustomerSimpleList } from '#/api/crm/customer'; |
| | | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getCustomerSimpleList, |
| | | api: getCrmCustomerSimpleList, |
| | | fieldNames: { |
| | | label: 'name', |
| | | value: 'id', |
| | |
| | | placeholder: 'è¯·éæ©äº§å', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getProductSimpleList, |
| | | fieldNames: { |
| | | label: 'name', |
| | | value: 'id', |
| | | api: async () => { |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | return res.list || []; |
| | | }, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getCustomerSimpleList, |
| | | api: getCrmCustomerSimpleList, |
| | | fieldNames: { |
| | | label: 'name', |
| | | value: 'id', |
| | |
| | | placeholder: 'è¯·éæ©äº§å', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getProductSimpleList, |
| | | api: async () => { |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | return res.list || []; |
| | | }, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useOrderGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { |
| | |
| | | <script lang="ts" setup> |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | import type { ErpSaleOutApi } from '#/api/erp/sale/out'; |
| | | |
| | | import { computed, nextTick, onMounted, ref, watch } from 'vue'; |
| | |
| | | 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 { getWarehouseStockCount } from '#/api/erp/stock/stock'; |
| | | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; |
| | | |
| | |
| | | ]); |
| | | |
| | | const tableData = ref<ErpSaleOutApi.SaleOutItem[]>([]); // è¡¨æ ¼æ°æ® |
| | | const productOptions = ref<any[]>([]); // 产å䏿é项 |
| | | const productOptions = ref<MdmItemApi.Item[]>([]); // ç©æä¸æé项 |
| | | const warehouseOptions = ref<any[]>([]); // ä»åºä¸æé项 |
| | | |
| | | /** è·åè¡¨æ ¼åè®¡æ°æ® */ |
| | |
| | | |
| | | /** åå§å */ |
| | | onMounted(async () => { |
| | | productOptions.value = await getProductSimpleList(); |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | productOptions.value = res.list || []; |
| | | warehouseOptions.value = await getWarehouseSimpleList(); |
| | | }); |
| | | </script> |
| | |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | | import { getProductSimpleList } from '#/api/erp/product/product'; |
| | | import { getCustomerSimpleList } from '#/api/erp/sale/customer'; |
| | | import { getItemPage } from '#/api/mdm/item'; |
| | | import { getCustomerSimpleList as getCrmCustomerSimpleList } from '#/api/crm/customer'; |
| | | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getCustomerSimpleList, |
| | | api: getCrmCustomerSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | placeholder: 'è¯·éæ©äº§å', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getProductSimpleList, |
| | | api: async () => { |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | return res.list || []; |
| | | }, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getCustomerSimpleList, |
| | | api: getCrmCustomerSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | placeholder: 'è¯·éæ©äº§å', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getProductSimpleList, |
| | | api: async () => { |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | return res.list || []; |
| | | }, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | |
| | | <script lang="ts" setup> |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | import type { ErpSaleReturnApi } from '#/api/erp/sale/return'; |
| | | |
| | | import { computed, nextTick, onMounted, ref, watch } from 'vue'; |
| | |
| | | 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 { getWarehouseStockCount } from '#/api/erp/stock/stock'; |
| | | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; |
| | | |
| | |
| | | ]); |
| | | |
| | | const tableData = ref<ErpSaleReturnApi.SaleReturnItem[]>([]); // è¡¨æ ¼æ°æ® |
| | | const productOptions = ref<any[]>([]); // 产å䏿é项 |
| | | const productOptions = ref<MdmItemApi.Item[]>([]); // ç©æä¸æé项 |
| | | const warehouseOptions = ref<any[]>([]); // ä»åºä¸æé项 |
| | | |
| | | /** è·åè¡¨æ ¼åè®¡æ°æ® */ |
| | |
| | | |
| | | /** åå§å */ |
| | | onMounted(async () => { |
| | | productOptions.value = await getProductSimpleList(); |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | productOptions.value = res.list || []; |
| | | warehouseOptions.value = await getWarehouseSimpleList(); |
| | | }); |
| | | </script> |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-2/5"> |
| | | <Modal :title="getTitle" class="w-[900px]"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | component: 'IconPicker', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©èå徿 ', |
| | | prefix: 'carbon', |
| | | prefix: 'ep', |
| | | }, |
| | | rules: 'required', |
| | | dependencies: { |
| | |
| | | }, |
| | | server: { |
| | | allowedHosts: true, |
| | | watch: { |
| | | ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'], |
| | | }, |
| | | proxy: { |
| | | "/admin-api": { |
| | | changeOrigin: true, |