银川
1.不合格品处理页面开发联调
2.招投标管理 - 可配置版前端联调方案
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesQcNcrApi { |
| | | /** MES ä¸åæ ¼åå¤çå */ |
| | | export interface Ncr { |
| | | id?: number; |
| | | code?: string; |
| | | name?: string; |
| | | qcType?: number; |
| | | qcId?: number; |
| | | qcCode?: string; |
| | | itemId?: number; |
| | | itemCode?: string; |
| | | itemName?: string; |
| | | itemSpecification?: string; |
| | | defectLevel?: number; |
| | | unqualifiedQuantity?: number; |
| | | defectDescription?: string; |
| | | disposition?: number; |
| | | responsibleDeptId?: number; |
| | | responsibleDeptName?: string; |
| | | reviewerUserId?: number; |
| | | reviewerNickname?: string; |
| | | reviewDate?: string; |
| | | reviewOpinion?: string; |
| | | handlerUserId?: number; |
| | | handlerNickname?: string; |
| | | handleDate?: string; |
| | | handleResult?: string; |
| | | rootCause?: string; |
| | | correctiveAction?: string; |
| | | preventiveAction?: string; |
| | | closeUserId?: number; |
| | | closeUserNickname?: string; |
| | | closeDate?: string; |
| | | closeOpinion?: string; |
| | | status?: number; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¸åæ ¼åå¤çåå页 */ |
| | | export function getNcrPage(params: PageParam) { |
| | | return requestClient.get<PageResult<MesQcNcrApi.Ncr>>('/mes/qc/ncr/page', { |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¸åæ ¼åå¤çå详æ
*/ |
| | | export function getNcr(id: number) { |
| | | return requestClient.get<MesQcNcrApi.Ncr>(`/mes/qc/ncr/get?id=${id}`); |
| | | } |
| | | |
| | | /** æ°å¢ä¸åæ ¼åå¤çå */ |
| | | export function createNcr(data: MesQcNcrApi.Ncr) { |
| | | return requestClient.post('/mes/qc/ncr/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹ä¸åæ ¼åå¤çå */ |
| | | export function updateNcr(data: MesQcNcrApi.Ncr) { |
| | | return requestClient.put('/mes/qc/ncr/update', data); |
| | | } |
| | | |
| | | /** å é¤ä¸åæ ¼åå¤çå */ |
| | | export function deleteNcr(id: number) { |
| | | return requestClient.delete(`/mes/qc/ncr/delete?id=${id}`); |
| | | } |
| | | |
| | | /** è¯å®¡ä¸åæ ¼åå¤çå */ |
| | | export function reviewNcr(data: { defectLevel?: number; disposition: number; id: number; responsibleDeptId?: number; reviewOpinion?: string }) { |
| | | return requestClient.put('/mes/qc/ncr/review', data); |
| | | } |
| | | |
| | | /** å¤ç½®ä¸åæ ¼åå¤çå */ |
| | | export function disposeNcr(data: { handleResult?: string; handlerUserId?: number; id: number }) { |
| | | return requestClient.put('/mes/qc/ncr/dispose', data); |
| | | } |
| | | |
| | | /** å
³éä¸åæ ¼åå¤çå */ |
| | | export function closeNcr(data: { closeOpinion?: string; id: number }) { |
| | | return requestClient.put('/mes/qc/ncr/close', data); |
| | | } |
| | | |
| | | /** 导åºä¸åæ ¼åå¤çå */ |
| | | export function exportNcr(params: any) { |
| | | return requestClient.download('/mes/qc/ncr/export-excel', { params }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmTenderConfigApi { |
| | | export interface TenderConfigVO { |
| | | id?: number; |
| | | defaultBiddingMode?: string; |
| | | } |
| | | } |
| | | |
| | | export function getTenderConfig() { |
| | | return requestClient.get<SrmTenderConfigApi.TenderConfigVO>('/srm/tender-config/get'); |
| | | } |
| | | |
| | | export function saveTenderConfig(data: SrmTenderConfigApi.TenderConfigVO) { |
| | | return requestClient.put('/srm/tender-config/save', data); |
| | | } |
| | |
| | | purchaseRequestId?: number; |
| | | awardId?: number; |
| | | purchaseOrderNo?: string; |
| | | biddingMode?: string; |
| | | createTime?: string; |
| | | } |
| | | |
| | |
| | | return requestClient.put(`/srm/tender-project/start-evaluation?id=${id}`); |
| | | } |
| | | |
| | | export function startPriceComparing(id: number) { |
| | | return requestClient.put(`/srm/tender-project/start-price-comparing?id=${id}`); |
| | | } |
| | | |
| | | export function confirmTenderProject(data: { id: number; bidId: number }) { |
| | | return requestClient.put('/srm/tender-project/confirm', data); |
| | | } |
| | | |
| | | // ========== ç©æå表 ========== |
| | | |
| | | export function getMaterialList(tenderProjectId: number) { |
| | |
| | | RQC: 4, // éè´§æ£éª |
| | | } as const; |
| | | |
| | | /** MES è´¨æ£æ£æµç»ææä¸¾ */ |
| | | export const MesQcCheckResultEnum = { |
| | | PASS: 1, // æ ¡éªéè¿ |
| | | FAIL: 2, // æ ¡éªä¸éè¿ |
| | | } as const; |
| | | |
| | | /** MES è´¨æ£åç¶ææä¸¾ */ |
| | | export const MesQcStatusEnum = { |
| | | DRAFT: MesOrderStatusConstants.DRAFT, |
| | |
| | | MES_WM_TRANSFER_TYPE: 'mes_wm_transfer_type', // MES 转移åç±»å |
| | | MES_PD_PARAM_TYPE: 'mes_pd_param_type', // MES å·¥èºåæ°ç±»å |
| | | MES_PD_TEMPLATE_TYPE: 'mes_pd_template_type', // MES å·¥èºæ¨¡æ¿ç±»å |
| | | MES_NCR_STATUS: 'mes_ncr_status', // MES ä¸åæ ¼åå¤çåç¶æ |
| | | MES_NCR_DISPOSITION: 'mes_ncr_disposition', // MES ä¸åæ ¼åå¤ç½®æ¹å¼ |
| | | } as const; |
| | | |
| | | /** ========== WMS - ä»å¨ç®¡ç模å ========== */ |
| | |
| | | title: 'åºå', |
| | | minWidth: 80, |
| | | formatter: 'formatAmount3', |
| | | visible: false, |
| | | }, |
| | | { |
| | | field: 'productUnitName', |
| | |
| | | field: 'productUnitName2', |
| | | title: 'è¾
åä½1', |
| | | minWidth: 80, |
| | | visible: false, |
| | | }, |
| | | { |
| | | field: 'productUnitName3', |
| | | title: 'è¾
åä½2', |
| | | minWidth: 80, |
| | | visible: false, |
| | | }, |
| | | { |
| | | field: 'needProduction', |
| | |
| | | title: '夿³¨', |
| | | minWidth: 150, |
| | | slots: { default: 'remark' }, |
| | | visible: false, |
| | | }, |
| | | { |
| | | field: 'count', |
| | |
| | | <template> |
| | | <Modal |
| | | :title="getTitle" |
| | | class="w-3/4" |
| | | class="w-5/6" |
| | | :show-confirm-button="formType !== 'detail'" |
| | | > |
| | | <Form class="mx-3"> |
| | |
| | | precision: 2, |
| | | }, |
| | | rules: 'required', |
| | | help: 'ä¸åæ ¼åæ°é大äº0æ¶ï¼ä¿ååå°èªå¨çæä¸åæ ¼å¤çå', |
| | | }, |
| | | { |
| | | fieldName: 'laborScrapQuantity', |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | |
| | | import { Button, Descriptions, message, Tabs } from 'ant-design-vue'; |
| | | |
| | |
| | | return false; |
| | | } |
| | | const data = (await formApi.getValues()) as MesQcIpqcApi.Ipqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return false; |
| | | } |
| | | if (formData.value?.id) { |
| | | await updateIpqc({ ...data, id: formData.value.id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | |
| | | const current = JSON.stringify(await formApi.getValues()); |
| | | if (current !== originalSnapshot.value) { |
| | | const data = (await formApi.getValues()) as MesQcIpqcApi.Ipqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return; |
| | | } |
| | | await updateIpqc({ ...data, id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | | originalSnapshot.value = current; |
| | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | | import { MdVendorSelect } from '#/views/mes/md/vendor/components'; |
| | | |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | |
| | | { |
| | | fieldName: 'vendorId', |
| | | label: 'ä¾åºå', |
| | | component: markRaw(MdVendorSelect), |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: getSupplierSimpleList, |
| | | labelField: 'name', |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | valueField: 'id', |
| | | }, |
| | | rules: 'selectRequired', |
| | | dependencies: { |
| | | triggerFields: ['sourceDocId'], |
| | | componentProps: (values) => ({ |
| | | disabled: !!values.sourceDocId, |
| | | allowClear: true, |
| | | api: getSupplierSimpleList, |
| | | labelField: 'name', |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | valueField: 'id', |
| | | }), |
| | | }, |
| | | }, |
| | |
| | | placeholder: '请è¾å
¥ä¸åæ ¼åæ°é', |
| | | }, |
| | | rules: 'required', |
| | | help: 'ä¸åæ ¼åæ°é大äº0æ¶ï¼ä¿ååå°èªå¨çæä¸åæ ¼å¤çå', |
| | | }, |
| | | { |
| | | fieldName: 'receiveDate', |
| | |
| | | { |
| | | fieldName: 'vendorId', |
| | | label: 'ä¾åºå', |
| | | component: markRaw(MdVendorSelect), |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: getSupplierSimpleList, |
| | | labelField: 'name', |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | |
| | | import { Button, Descriptions, message, Tabs } from 'ant-design-vue'; |
| | | |
| | |
| | | return false; |
| | | } |
| | | const data = (await formApi.getValues()) as MesQcIqcApi.Iqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return false; |
| | | } |
| | | if (formData.value?.id) { |
| | | await updateIqc({ ...data, id: formData.value.id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | |
| | | const current = JSON.stringify(await formApi.getValues()); |
| | | if (current !== originalSnapshot.value) { |
| | | const data = (await formApi.getValues()) as MesQcIqcApi.Iqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return; |
| | | } |
| | | await updateIqc({ ...data, id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | | originalSnapshot.value = current; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesQcNcrApi } from '#/api/mes/qc/ncr'; |
| | | |
| | | import { h, markRaw } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { DeptTreeSelect } from '#/views/system/dept/components'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | |
| | | /** NCR ç¶ææä¸¾ */ |
| | | export const NCR_STATUS = { |
| | | PENDING_REVIEW: 0, |
| | | REVIEWED: 1, |
| | | DISPOSED: 2, |
| | | CLOSED: 3, |
| | | } as const; |
| | | |
| | | export const NCR_STATUS_OPTIONS = [ |
| | | { label: 'å¾
è¯å®¡', value: 0 }, |
| | | { label: 'å·²è¯å®¡', value: 1 }, |
| | | { label: 'å·²å¤ç½®', value: 2 }, |
| | | { label: 'å·²å
³é', value: 3 }, |
| | | ]; |
| | | |
| | | /** æ£éªç±»åæä¸¾ */ |
| | | export const QC_TYPE_OPTIONS = [ |
| | | { label: 'IQC', value: 1 }, |
| | | { label: 'IPQC', value: 2 }, |
| | | { label: 'OQC', value: 3 }, |
| | | { label: 'RQC', value: 4 }, |
| | | ]; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema( |
| | | formApi?: VbenFormApi, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'qcId', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'NCRåå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥NCRåå·', |
| | | }, |
| | | rules: 'required', |
| | | suffix: () => |
| | | h( |
| | | Button, |
| | | { |
| | | type: 'default', |
| | | onClick: async () => { |
| | | const now = new Date(); |
| | | const pad = (n: number) => String(n).padStart(2, '0'); |
| | | const code = `NCR${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`; |
| | | await formApi?.setFieldValue('code', code); |
| | | }, |
| | | }, |
| | | { default: () => 'èªå¨çæ' }, |
| | | ), |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'NCRåç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥NCRåç§°', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'qcType', |
| | | label: 'æ£éªç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: QC_TYPE_OPTIONS, |
| | | placeholder: 'è¯·éæ©æ£éªç±»å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'itemId', |
| | | label: 'ä¸åæ ¼ç©æ', |
| | | component: markRaw(MdItemSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ä¸åæ ¼ç©æ', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'defectLevel', |
| | | label: '缺é·ç级', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'), |
| | | placeholder: 'è¯·éæ©ç¼ºé·ç级', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'unqualifiedQuantity', |
| | | label: 'ä¸åæ ¼æ°é', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | placeholder: '请è¾å
¥ä¸åæ ¼æ°é', |
| | | precision: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'defectDescription', |
| | | label: 'ä¸åæ ¼æè¿°', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¸åæ ¼æè¿°', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'rootCause', |
| | | label: 'æ ¹æ¬åå ', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æ ¹æ¬åå ', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'correctiveAction', |
| | | label: 'çº æ£æªæ½', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥çº æ£æªæ½', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'preventiveAction', |
| | | label: 'é¢é²æªæ½', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥é¢é²æªæ½', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** è¯å®¡è¡¨å */ |
| | | export function useReviewFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'disposition', |
| | | label: 'å¤ç½®æ¹å¼', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_NCR_DISPOSITION, 'number'), |
| | | placeholder: 'è¯·éæ©å¤ç½®æ¹å¼', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'defectLevel', |
| | | label: '缺é·ç级', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'), |
| | | placeholder: 'è¯·éæ©ç¼ºé·ç级', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'responsibleDeptId', |
| | | label: '责任é¨é¨', |
| | | component: markRaw(DeptTreeSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è´£ä»»é¨é¨', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'reviewOpinion', |
| | | label: 'è¯å®¡æè§', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è¯å®¡æè§', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å¤ç½®è¡¨å */ |
| | | export function useDisposeFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'handlerUserId', |
| | | label: 'å¤ç人', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: getSimpleUserList, |
| | | labelField: 'nickname', |
| | | placeholder: 'è¯·éæ©å¤ç人', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'handleResult', |
| | | label: 'å¤çç»æè¯´æ', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤çç»æè¯´æ', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å
³é表å */ |
| | | export function useCloseFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'closeOpinion', |
| | | label: 'å
³éæè§', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å
³éæè§', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'NCRåå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥NCRåå·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'qcType', |
| | | label: 'æ£éªç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: QC_TYPE_OPTIONS, |
| | | placeholder: 'è¯·éæ©æ£éªç±»å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'itemId', |
| | | label: 'ä¸åæ ¼ç©æ', |
| | | component: markRaw(MdItemSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç©æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'defectLevel', |
| | | label: '缺é·ç级', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'), |
| | | placeholder: 'è¯·éæ©ç¼ºé·ç级', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'disposition', |
| | | label: 'å¤ç½®æ¹å¼', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_NCR_DISPOSITION, 'number'), |
| | | placeholder: 'è¯·éæ©å¤ç½®æ¹å¼', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: NCR_STATUS_OPTIONS, |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'createTime', |
| | | label: 'å建æ¶é´', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | allowClear: true, |
| | | class: '!w-full', |
| | | placeholder: 'è¯·éæ©å建æ¶é´', |
| | | showTime: true, |
| | | type: 'date', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesQcNcrApi.Ncr>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: 'NCRåå·', |
| | | width: 200, |
| | | }, |
| | | { |
| | | field: 'qcCode', |
| | | title: 'æ¥æºæ£éªåç¼å·', |
| | | width: 180, |
| | | }, |
| | | { |
| | | field: 'itemName', |
| | | title: 'ç©æåç§°', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | field: 'itemSpecification', |
| | | title: 'è§æ ¼åå·', |
| | | width: 130, |
| | | }, |
| | | { |
| | | field: 'qcType', |
| | | title: 'æ£éªç±»å', |
| | | width: 100, |
| | | slots: { default: 'qcType' }, |
| | | }, |
| | | { |
| | | field: 'defectLevel', |
| | | title: '缺é·ç级', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_DEFECT_LEVEL }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'unqualifiedQuantity', |
| | | title: 'ä¸åæ ¼æ°é', |
| | | width: 110, |
| | | }, |
| | | { |
| | | field: 'disposition', |
| | | title: 'å¤ç½®æ¹å¼', |
| | | width: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_NCR_DISPOSITION }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 90, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_NCR_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 220, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesQcNcrApi } from '#/api/mes/qc/ncr'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | closeNcr, |
| | | deleteNcr, |
| | | disposeNcr, |
| | | exportNcr, |
| | | getNcrPage, |
| | | reviewNcr, |
| | | } from '#/api/mes/qc/ncr'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { |
| | | NCR_STATUS, |
| | | QC_TYPE_OPTIONS, |
| | | useCloseFormSchema, |
| | | useDisposeFormSchema, |
| | | useGridColumns, |
| | | useGridFormSchema, |
| | | useReviewFormSchema, |
| | | } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | defineOptions({ name: 'NcrIndex' }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** è¯å®¡å¼¹çª */ |
| | | const [ReviewForm, reviewFormApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-2', |
| | | labelWidth: 120, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useReviewFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | const [ReviewModal, reviewModalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await reviewFormApi.validate(); |
| | | if (!valid) return; |
| | | reviewModalApi.lock(); |
| | | try { |
| | | await reviewNcr( |
| | | (await reviewFormApi.getValues()) as { |
| | | defectLevel?: number; |
| | | disposition: number; |
| | | id: number; |
| | | responsibleDeptId?: number; |
| | | reviewOpinion?: string; |
| | | }, |
| | | ); |
| | | await reviewModalApi.close(); |
| | | message.success('è¯å®¡æå'); |
| | | handleRefresh(); |
| | | } finally { |
| | | reviewModalApi.unlock(); |
| | | } |
| | | }, |
| | | onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) return; |
| | | const data = reviewModalApi.getData<MesQcNcrApi.Ncr>(); |
| | | reviewFormApi.setValues({ id: data?.id }); |
| | | }, |
| | | }); |
| | | |
| | | /** å¤ç½®å¼¹çª */ |
| | | const [DisposeForm, disposeFormApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-2', |
| | | labelWidth: 120, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useDisposeFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | const [DisposeModal, disposeModalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await disposeFormApi.validate(); |
| | | if (!valid) return; |
| | | disposeModalApi.lock(); |
| | | try { |
| | | await disposeNcr( |
| | | (await disposeFormApi.getValues()) as { |
| | | handleResult?: string; |
| | | handlerUserId?: number; |
| | | id: number; |
| | | }, |
| | | ); |
| | | await disposeModalApi.close(); |
| | | message.success('å¤ç½®æå'); |
| | | handleRefresh(); |
| | | } finally { |
| | | disposeModalApi.unlock(); |
| | | } |
| | | }, |
| | | onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) return; |
| | | const data = disposeModalApi.getData<MesQcNcrApi.Ncr>(); |
| | | disposeFormApi.setValues({ id: data?.id }); |
| | | }, |
| | | }); |
| | | |
| | | /** å
³éå¼¹çª */ |
| | | const [CloseForm, closeFormApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | formItemClass: 'col-span-2', |
| | | labelWidth: 120, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useCloseFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | const [CloseModal, closeModalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await closeFormApi.validate(); |
| | | if (!valid) return; |
| | | closeModalApi.lock(); |
| | | try { |
| | | await closeNcr( |
| | | (await closeFormApi.getValues()) as { |
| | | closeOpinion?: string; |
| | | id: number; |
| | | }, |
| | | ); |
| | | await closeModalApi.close(); |
| | | message.success('å
³éæå'); |
| | | handleRefresh(); |
| | | } finally { |
| | | closeModalApi.unlock(); |
| | | } |
| | | }, |
| | | onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) return; |
| | | const data = closeModalApi.getData<MesQcNcrApi.Ncr>(); |
| | | closeFormApi.setValues({ id: data?.id }); |
| | | }, |
| | | }); |
| | | |
| | | function getQcTypeLabel(type?: number): string { |
| | | return QC_TYPE_OPTIONS.find((o) => o.value === type)?.label || ''; |
| | | } |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** å建 */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** ç¼è¾ */ |
| | | function handleEdit(row: MesQcNcrApi.Ncr) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å é¤ */ |
| | | async function handleDelete(row: MesQcNcrApi.Ncr) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteNcr(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** è¯å®¡ */ |
| | | function handleReview(row: MesQcNcrApi.Ncr) { |
| | | reviewModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å¤ç½® */ |
| | | function handleDispose(row: MesQcNcrApi.Ncr) { |
| | | disposeModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å
³é */ |
| | | function handleClose(row: MesQcNcrApi.Ncr) { |
| | | closeModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å¯¼åº */ |
| | | async function handleExport() { |
| | | const data = await exportNcr(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'ä¸åæ ¼åå¤çå.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getNcrPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesQcNcrApi.Ncr>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ReviewModal title="è¯å®¡ä¸åæ ¼åå¤çå" class="w-2/5"> |
| | | <ReviewForm class="mx-4" /> |
| | | </ReviewModal> |
| | | <DisposeModal title="å¤ç½®ä¸åæ ¼åå¤çå" class="w-2/5"> |
| | | <DisposeForm class="mx-4" /> |
| | | </DisposeModal> |
| | | <CloseModal title="å
³éä¸åæ ¼åå¤çå" class="w-2/5"> |
| | | <CloseForm class="mx-4" /> |
| | | </CloseModal> |
| | | |
| | | <Grid table-title="ä¸åæ ¼åå¤ç"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ä¸åæ ¼åå¤çå']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:qc-ncr:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:qc-ncr:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #qcType="{ row }"> |
| | | <Tag v-if="row.qcType !== undefined && row.qcType !== null"> |
| | | {{ getQcTypeLabel(row.qcType) }} |
| | | </Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | v-if="row.status === NCR_STATUS.PENDING_REVIEW" |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:qc-ncr:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:qc-ncr:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'è¯å®¡', |
| | | type: 'link', |
| | | icon: 'lucide:clipboard-check', |
| | | auth: ['mes:qc-ncr:review'], |
| | | onClick: handleReview.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | <TableAction |
| | | v-else-if="row.status === NCR_STATUS.REVIEWED" |
| | | :actions="[ |
| | | { |
| | | label: 'å¤ç½®', |
| | | type: 'link', |
| | | icon: 'lucide:play', |
| | | auth: ['mes:qc-ncr:dispose'], |
| | | onClick: handleDispose.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | <TableAction |
| | | v-else-if="row.status === NCR_STATUS.DISPOSED" |
| | | :actions="[ |
| | | { |
| | | label: 'å
³é', |
| | | type: 'link', |
| | | icon: 'lucide:check-circle', |
| | | auth: ['mes:qc-ncr:close'], |
| | | onClick: handleClose.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | <span v-else class="text-gray-400">--</span> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesQcNcrApi } from '#/api/mes/qc/ncr'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createNcr, getNcr, updateNcr } from '#/api/mes/qc/ncr'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | defineOptions({ name: 'NcrForm' }); |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MesQcNcrApi.Ncr>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['ä¸åæ ¼åå¤çå']) |
| | | : $t('ui.actionTitle.create', ['ä¸åæ ¼åå¤çå']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | labelWidth: 120, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as MesQcNcrApi.Ncr; |
| | | try { |
| | | await (formData.value?.id ? updateNcr(data) : createNcr(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; |
| | | } |
| | | formApi.setState({ schema: useFormSchema(formApi) }); |
| | | const data = modalApi.getData<MesQcNcrApi.Ncr>(); |
| | | if (!data || !data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getNcr(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | placeholder: '请è¾å
¥ä¸åæ ¼åæ°é', |
| | | }, |
| | | rules: 'required', |
| | | help: 'ä¸åæ ¼åæ°é大äº0æ¶ï¼ä¿ååå°èªå¨çæä¸åæ ¼å¤çå', |
| | | }, |
| | | { |
| | | fieldName: 'inspectorUserId', |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | |
| | | import { Button, Descriptions, message, Tabs } from 'ant-design-vue'; |
| | | |
| | |
| | | return false; |
| | | } |
| | | const data = (await formApi.getValues()) as MesQcOqcApi.Oqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return false; |
| | | } |
| | | if (formData.value?.id) { |
| | | await updateOqc({ ...data, id: formData.value.id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | |
| | | const current = JSON.stringify(await formApi.getValues()); |
| | | if (current !== originalSnapshot.value) { |
| | | const data = (await formApi.getValues()) as MesQcOqcApi.Oqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return; |
| | | } |
| | | await updateOqc({ ...data, id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | | originalSnapshot.value = current; |
| | |
| | | precision: 2, |
| | | }, |
| | | rules: 'required', |
| | | help: 'ä¸åæ ¼åæ°é大äº0æ¶ï¼ä¿ååå°èªå¨çæä¸åæ ¼å¤çå', |
| | | }, |
| | | { |
| | | fieldName: 'inspectDate', |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | |
| | | import { Button, Descriptions, message, Tabs } from 'ant-design-vue'; |
| | | |
| | |
| | | return false; |
| | | } |
| | | const data = (await formApi.getValues()) as MesQcRqcApi.Rqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return false; |
| | | } |
| | | if (formData.value?.id) { |
| | | await updateRqc({ ...data, id: formData.value.id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | |
| | | const current = JSON.stringify(await formApi.getValues()); |
| | | if (current !== originalSnapshot.value) { |
| | | const data = (await formApi.getValues()) as MesQcRqcApi.Rqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return; |
| | | } |
| | | await updateRqc({ ...data, id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | | originalSnapshot.value = current; |
| | |
| | | labels: { 1: 'å
¬å¼ææ ', 2: 'éè¯·ææ ', 3: 'ç«äºæ§è°å¤' }, |
| | | }; |
| | | |
| | | /** ææ é¡¹ç®ç¶æ â å¯¹é½ TenderStatusEnum (0=è稿 1=åå¸ä¸ 2=ææ ä¸ 3=è¯æ ä¸ 4=已宿 5=å·²å
³é) */ |
| | | /** ææ é¡¹ç®ç¶æ â å¯¹é½ TenderStatusEnum (0=è稿 1=åå¸ä¸ 2=ææ ä¸ 3=è¯æ ä¸ 4=已宿 5=å·²å
³é 10=æ¯ä»·ä¸ 20=已确认) */ |
| | | export const TENDER_STATUS = { |
| | | DRAFT: 0, |
| | | PUBLISHED: 1, |
| | |
| | | EVALUATING: 3, |
| | | AWARDED: 4, |
| | | CLOSED: 5, |
| | | PRICE_COMPARING: 10, |
| | | CONFIRMED: 20, |
| | | } as const; |
| | | |
| | | export const TENDER_STATUS_OPTIONS: EnumOption[] = [ |
| | |
| | | { label: 'è¯æ ä¸', value: 3 }, |
| | | { label: '已宿 ', value: 4 }, |
| | | { label: 'å·²å
³é', value: 5 }, |
| | | { label: 'æ¯ä»·ä¸', value: 10 }, |
| | | { label: '已确认', value: 20 }, |
| | | ]; |
| | | |
| | | export const TENDER_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = { |
| | |
| | | { value: 3, color: 'orange' }, |
| | | { value: 4, color: 'green' }, |
| | | { value: 5, color: 'red' }, |
| | | { value: 10, color: 'purple' }, |
| | | { value: 20, color: 'green' }, |
| | | ], |
| | | labels: { 0: 'è稿', 1: 'åå¸ä¸', 2: 'ææ ä¸', 3: 'è¯æ ä¸', 4: '已宿 ', 5: 'å·²å
³é' }, |
| | | labels: { 0: 'è稿', 1: 'åå¸ä¸', 2: 'ææ ä¸', 3: 'è¯æ ä¸', 4: '已宿 ', 5: 'å·²å
³é', 10: 'æ¯ä»·ä¸', 20: '已确认' }, |
| | | }; |
| | | |
| | | /** ææ ç¶æ Ant Design Tag é¢è²æ å° */ |
| | |
| | | [TENDER_STATUS.EVALUATING]: 'orange', |
| | | [TENDER_STATUS.AWARDED]: 'green', |
| | | [TENDER_STATUS.CLOSED]: 'red', |
| | | [TENDER_STATUS.PRICE_COMPARING]: 'purple', |
| | | [TENDER_STATUS.CONFIRMED]: 'green', |
| | | }; |
| | | |
| | | // ========== æææ æ¨¡å¼ â FULL=宿´ç SIMPLE=ç®æç ========== |
| | | |
| | | export const BIDDING_MODE = { |
| | | FULL: 'FULL', |
| | | SIMPLE: 'SIMPLE', |
| | | } as const; |
| | | |
| | | export const BIDDING_MODE_OPTIONS: EnumOption[] = [ |
| | | { label: '宿´ç', value: 'FULL' }, |
| | | { label: 'ç®æç', value: 'SIMPLE' }, |
| | | ]; |
| | | |
| | | export const BIDDING_MODE_MAP: Record<string, string> = { |
| | | FULL: '宿´ç', |
| | | SIMPLE: 'ç®æç', |
| | | }; |
| | | |
| | | // ========== ææ â å¯¹é½ BidStatusEnum (0=æ¥å 10=å·²ææ 20=æ¤æ 30=䏿 40=æªä¸æ ) ========== |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import { ref } from 'vue'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { message, Radio, Button, Card, Spin } from 'ant-design-vue'; |
| | | |
| | | import { getTenderConfig, saveTenderConfig } from '#/api/srm/tender-config'; |
| | | import { BIDDING_MODE, BIDDING_MODE_OPTIONS } from '#/views/srm/enums'; |
| | | |
| | | defineOptions({ name: 'SrmTenderConfig' }); |
| | | |
| | | const loading = ref(false); |
| | | const saving = ref(false); |
| | | const defaultBiddingMode = ref<string>(BIDDING_MODE.FULL); |
| | | |
| | | async function loadConfig() { |
| | | loading.value = true; |
| | | try { |
| | | const res = await getTenderConfig(); |
| | | if (res?.defaultBiddingMode) { |
| | | defaultBiddingMode.value = res.defaultBiddingMode; |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | async function handleSave() { |
| | | saving.value = true; |
| | | try { |
| | | await saveTenderConfig({ defaultBiddingMode: defaultBiddingMode.value }); |
| | | message.success('ä¿åæå'); |
| | | await loadConfig(); |
| | | } finally { |
| | | saving.value = false; |
| | | } |
| | | } |
| | | |
| | | loadConfig(); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page> |
| | | <Spin :spinning="loading"> |
| | | <Card title="æææ é»è®¤æ¨¡å¼é
ç½®" class="max-w-lg"> |
| | | <div class="mb-4 text-gray-500 text-sm"> |
| | | 设置æ°å»ºææ é¡¹ç®æ¶çé»è®¤æææ 模å¼ï¼ç¨æ·å¨åå»ºé¡¹ç®æ¶ä»å¯æå¨æ´æ¹ã |
| | | </div> |
| | | <div class="flex items-center gap-4"> |
| | | <span class="font-medium">é»è®¤æ¨¡å¼ï¼</span> |
| | | <Radio.Group |
| | | v-model:value="defaultBiddingMode" |
| | | :options="BIDDING_MODE_OPTIONS" |
| | | /> |
| | | </div> |
| | | <div class="mt-6"> |
| | | <Button type="primary" :loading="saving" @click="handleSave"> |
| | | ä¿å |
| | | </Button> |
| | | </div> |
| | | </Card> |
| | | </Spin> |
| | | </Page> |
| | | </template> |
| | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { |
| | | BIDDING_MODE_MAP, |
| | | BIDDING_MODE_OPTIONS, |
| | | TENDER_STATUS_CELLTAG, |
| | | TENDER_STATUS_OPTIONS, |
| | | TENDER_TYPE_CELLTAG, |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'biddingMode', |
| | | label: 'æææ 模å¼', |
| | | component: 'Select', |
| | | rules: 'required', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©æææ 模å¼', |
| | | options: BIDDING_MODE_OPTIONS, |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['_partial', 'id'], |
| | | show: (values) => !values._partial, |
| | | componentProps: (values) => ({ |
| | | disabled: !!values.id, |
| | | placeholder: 'è¯·éæ©æææ 模å¼', |
| | | options: BIDDING_MODE_OPTIONS, |
| | | }), |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'tenderDesc', |
| | | label: 'ææ è¯´æ', |
| | | component: 'Textarea', |
| | |
| | | options: TENDER_STATUS_OPTIONS, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'biddingMode', |
| | | label: 'æææ 模å¼', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©æææ 模å¼', |
| | | allowClear: true, |
| | | options: BIDDING_MODE_OPTIONS, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | |
| | | minWidth: 100, |
| | | cellRender: { name: 'CellTag', props: TENDER_STATUS_CELLTAG }, |
| | | }, |
| | | { |
| | | field: 'biddingMode', |
| | | title: 'ææ æ¨¡å¼', |
| | | minWidth: 100, |
| | | formatter: ({ cellValue }) => BIDDING_MODE_MAP[cellValue] || cellValue, |
| | | }, |
| | | { field: 'budgetAmount', title: 'é¢ç®éé¢', minWidth: 120 }, |
| | | { field: 'purchaseOrg', title: 'éè´ç»ç»', minWidth: 100 }, |
| | | { field: 'startTime', title: 'å¼å§æ¶é´', minWidth: 160 }, |
| | |
| | | createBidOpen, getBidOpen, |
| | | getEvaluationList, createBidEvaluation, updateBidEvaluation, calculateRanking, |
| | | createAward, deleteAward, getAward, getAwardByProject, approveAward, generatePurchaseOrder, |
| | | confirmTenderProject, |
| | | } from '#/api/srm/tender'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getItemSimpleList } from '#/api/mdm/item'; |
| | |
| | | AWARD_STATUS, |
| | | AWARD_STATUS_MAP, |
| | | getLabel, |
| | | BIDDING_MODE, |
| | | } from '../enums'; |
| | | |
| | | defineOptions({ name: 'SrmTenderDetail' }); |
| | |
| | | loadProject(); |
| | | } |
| | | |
| | | // ========== ç®ææ¨¡å¼ï¼ç¡®è®¤ä¸æ ========== |
| | | const confirmBidId = ref<number>(); |
| | | const confirmModalVisible = ref(false); |
| | | |
| | | async function handleConfirmBidAsWinner(bidId: number) { |
| | | await confirmTenderProject({ id: projectId, bidId }); |
| | | message.success('ç¡®è®¤ä¸æ æåï¼éè´è®¢åå·²èªå¨çæ'); |
| | | loadProject(); |
| | | loadBids(); |
| | | loadAward(); |
| | | } |
| | | |
| | | function openSimpleConfirmModal() { |
| | | confirmBidId.value = undefined; |
| | | confirmModalVisible.value = true; |
| | | } |
| | | |
| | | async function handleSimpleConfirm() { |
| | | if (!confirmBidId.value) { |
| | | message.warning('è¯·éæ©ä¸æ çææ è®°å½'); |
| | | return; |
| | | } |
| | | await confirmTenderProject({ id: projectId, bidId: confirmBidId.value }); |
| | | message.success('ç¡®è®¤ä¸æ æåï¼éè´è®¢åå·²èªå¨çæ'); |
| | | confirmModalVisible.value = false; |
| | | loadProject(); |
| | | loadBids(); |
| | | loadAward(); |
| | | } |
| | | |
| | | // ========== åå§å ========== |
| | | loadProject(); |
| | | loadMaterials(); |
| | |
| | | const canApproveAward = computed(() => award.value?.awardStatus === AWARD_STATUS.PENDING_APPROVAL); |
| | | const canGeneratePO = computed(() => award.value?.awardStatus === AWARD_STATUS.AWARDED); |
| | | |
| | | const stepItems = [ |
| | | // ç®ææ¨¡å¼ |
| | | const isSimpleMode = computed(() => project.value?.biddingMode === BIDDING_MODE.SIMPLE); |
| | | const isPriceComparing = computed(() => project.value?.tenderStatus === TENDER_STATUS.PRICE_COMPARING); |
| | | |
| | | const stepItems = computed(() => { |
| | | if (isSimpleMode.value) { |
| | | return [ |
| | | { title: 'è稿', status: TENDER_STATUS.DRAFT }, |
| | | { title: 'æ¯ä»·ä¸', status: TENDER_STATUS.PRICE_COMPARING }, |
| | | { title: '已确认', status: TENDER_STATUS.CONFIRMED }, |
| | | ]; |
| | | } |
| | | return [ |
| | | { title: 'è稿', status: TENDER_STATUS.DRAFT }, |
| | | { title: 'åå¸', status: TENDER_STATUS.PUBLISHED }, |
| | | { title: 'ææ ä¸', status: TENDER_STATUS.BIDDING }, |
| | | { title: 'è¯æ ä¸', status: TENDER_STATUS.EVALUATING }, |
| | | { title: '已宿 ', status: TENDER_STATUS.AWARDED }, |
| | | ]; |
| | | }); |
| | | const currentStep = computed(() => { |
| | | const s = project.value?.tenderStatus ?? 0; |
| | | if (s === TENDER_STATUS.CLOSED) return 4; |
| | | const idx = stepItems.findIndex((item) => item.status >= s); |
| | | const items = stepItems.value; |
| | | if (s === TENDER_STATUS.CLOSED) return items.length - 1; |
| | | const idx = items.findIndex((item) => item.status >= s); |
| | | return idx === -1 ? 0 : idx; |
| | | }); |
| | | const stepStatus = computed(() => { |
| | | const s = project.value?.tenderStatus; |
| | | if (s === TENDER_STATUS.CLOSED) return 'error'; |
| | | if (s === TENDER_STATUS.AWARDED) return 'finish'; |
| | | if (s === TENDER_STATUS.AWARDED || s === TENDER_STATUS.CONFIRMED) return 'finish'; |
| | | return 'process'; |
| | | }); |
| | | |
| | |
| | | { title: 'ææ éé¢', dataIndex: 'bidTotalAmount', width: 120 }, |
| | | { title: 'ææ æ¶é´', dataIndex: 'bidTime', width: 160 }, |
| | | { title: 'ç¶æ', dataIndex: 'bidStatus', width: 100, customRender: ({ text }: any) => BID_STATUS_MAP[text] || text }, |
| | | { title: 'æä½', key: 'actions', width: 180 }, |
| | | { title: 'æä½', key: 'actions', width: 240 }, |
| | | ]; |
| | | |
| | | const quoteColumns = [ |
| | |
| | | <span class="text-gray-400 text-xs">{{ project.tenderNo }}</span> |
| | | <a-tag :color="statusColor">{{ statusLabel }}</a-tag> |
| | | <a-tag color="blue">{{ typeLabel }}</a-tag> |
| | | <a-tag v-if="isSimpleMode" color="purple">ç®æç</a-tag> |
| | | <div class="flex-1" /> |
| | | <a-button |
| | | v-if="isPriceComparing" |
| | | type="primary" |
| | | size="small" |
| | | @click="openSimpleConfirmModal" |
| | | > |
| | | ç¡®è®¤ä¸æ |
| | | </a-button> |
| | | <a-button size="small" @click="loadProject(); loadMaterials(); loadBids(); loadEvaluations(); loadAward();"> |
| | | <template #icon><IconifyIcon icon="ant-design:reload-outlined" /></template> |
| | | å·æ° |
| | |
| | | </template> |
| | | <div class="flex items-center justify-between mb-3"> |
| | | <span class="text-xs text-gray-400">å
± <b class="text-gray-600">{{ bidCount }}</b> ä¸ªææ </span> |
| | | <a-button v-if="isPublishedOrBidding" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true"> |
| | | <a-button v-if="isPublishedOrBidding || isDraft || isPriceComparing" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | æ°å¢ææ |
| | | </a-button> |
| | |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-space> |
| | | <a-button size="small" type="link" @click="loadQuotes(record.id); activeTab = 'quote'"><IconifyIcon icon="ant-design:dollar-outlined" />æ¥ä»·</a-button> |
| | | <a-popconfirm v-if="isPublishedOrBidding && record.bidStatus === BID_STATUS.REGISTERED" title="ç¡®è®¤æ¤æ ï¼" @confirm="handleWithdrawBid(record.id)"> |
| | | <a-popconfirm v-if="isPriceComparing && record.bidStatus !== BID_STATUS.WITHDRAWN" title="ç¡®è®¤éæ©è¯¥ææ ä¸ºä¸æ ï¼å°èªå¨çæéè´è®¢åã" ok-text="ç¡®è®¤ä¸æ " cancel-text="åæ¶" @confirm="handleConfirmBidAsWinner(record.id)"> |
| | | <a-button size="small" type="link" style="color: #52c41a;"><IconifyIcon icon="ant-design:check-circle-outlined" />ç¡®è®¤ä¸æ </a-button> |
| | | </a-popconfirm> |
| | | <a-popconfirm v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && record.bidStatus === BID_STATUS.REGISTERED" title="ç¡®è®¤æ¤æ ï¼" @confirm="handleWithdrawBid(record.id)"> |
| | | <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:close-circle-outlined" />æ¤æ </a-button> |
| | | </a-popconfirm> |
| | | </a-space> |
| | |
| | | <a-button size="small" type="link" class="ml-auto" @click="activeTab = 'bid'">åæ¢ææ </a-button> |
| | | </div> |
| | | <div class="text-right mb-3"> |
| | | <a-button v-if="isPublishedOrBidding && selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote"> |
| | | <a-button v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | æ°å¢æ¥ä»· |
| | | </a-button> |
| | |
| | | {{ record.warrantyPeriod != null ? `${record.warrantyPeriod} 个æ` : '-' }} |
| | | </template> |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-button v-if="isPublishedOrBidding && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />ç¼è¾</a-button> |
| | | <a-button v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />ç¼è¾</a-button> |
| | | </template> |
| | | </template> |
| | | </a-table> |
| | |
| | | </a-tab-pane> |
| | | |
| | | <!-- Tab4: 弿 --> |
| | | <a-tab-pane key="bidOpen"> |
| | | <a-tab-pane v-if="!isSimpleMode" key="bidOpen"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:unlock-outlined" />弿 </a-space> |
| | | </template> |
| | |
| | | </a-tab-pane> |
| | | |
| | | <!-- Tab5: è¯æ --> |
| | | <a-tab-pane key="evaluation"> |
| | | <a-tab-pane v-if="!isSimpleMode" key="evaluation"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:trophy-outlined" />è¯æ </a-space> |
| | | </template> |
| | |
| | | </a-tab-pane> |
| | | |
| | | <!-- Tab6: 宿 --> |
| | | <a-tab-pane key="award"> |
| | | <a-tab-pane v-if="!isSimpleMode" key="award"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:check-circle-outlined" />宿 </a-space> |
| | | </template> |
| | |
| | | </a-form> |
| | | </a-modal> |
| | | |
| | | <!-- ç®ææ¨¡å¼ï¼ç¡®è®¤ä¸æ å¼¹çª --> |
| | | <a-modal v-model:open="confirmModalVisible" title="ç¡®è®¤ä¸æ " :width="460" @ok="handleSimpleConfirm"> |
| | | <a-form layout="vertical" class="mt-4"> |
| | | <a-form-item label="䏿 ææ è®°å½" required> |
| | | <a-select |
| | | v-model:value="confirmBidId" |
| | | show-search |
| | | placeholder="鿩䏿 çææ è®°å½" |
| | | :filter-option="filterOption" |
| | | > |
| | | <a-select-option |
| | | v-for="b in bidList.filter((i) => i.bidStatus !== BID_STATUS.WITHDRAWN)" |
| | | :key="b.id" |
| | | :value="b.id" |
| | | :label="`${b.supplierName} - ${b.bidNo}`" |
| | | > |
| | | {{ b.supplierName }} - {{ b.bidNo }} (Â¥{{ b.bidTotalAmount?.toLocaleString?.() ?? b.bidTotalAmount }}) |
| | | </a-select-option> |
| | | </a-select> |
| | | </a-form-item> |
| | | <div class="text-gray-400 text-xs"> |
| | | 确认åå°èªå¨çæéè´è®¢åï¼è¯¥æä½ä¸å¯æ¤éã |
| | | </div> |
| | | </a-form> |
| | | </a-modal> |
| | | |
| | | <!-- 宿 表å --> |
| | | <a-modal v-model:open="awardFormVisible" title="åå»ºå®æ " :width="500" @ok="handleCreateAward"> |
| | | <a-form layout="vertical" class="mt-4"> |
| | |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message, Select } from 'ant-design-vue'; |
| | | import { ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | closeTender, |
| | | confirmTenderProject, |
| | | deleteTenderProject, |
| | | generatePurchaseOrder, |
| | | getBidListByProject, |
| | | getTenderProjectPage, |
| | | publishTender, |
| | | startBidding, |
| | | startEvaluation, |
| | | startPriceComparing, |
| | | } from '#/api/srm/tender'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import TenderForm from './modules/form.vue'; |
| | | import { TENDER_STATUS } from '../enums'; |
| | | import { BIDDING_MODE, TENDER_STATUS } from '../enums'; |
| | | |
| | | defineOptions({ name: 'SrmTender' }); |
| | | |
| | |
| | | handleRefresh(); |
| | | } |
| | | |
| | | async function handleStartPriceComparing(row: SrmTenderApi.TenderProjectVO) { |
| | | await startPriceComparing(row.id!); |
| | | message.success('å·²å¼å§æ¯ä»·'); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | // ç¡®è®¤ä¸æ ç¸å
³ |
| | | const confirmTarget = ref<SrmTenderApi.TenderProjectVO | null>(null); |
| | | const confirmBidId = ref<number>(); |
| | | const confirmBidOptions = ref<{ label: string; value: number }[]>([]); |
| | | const confirmLoading = ref(false); |
| | | |
| | | async function openConfirmModal(row: SrmTenderApi.TenderProjectVO) { |
| | | confirmTarget.value = row; |
| | | confirmBidId.value = undefined; |
| | | confirmLoading.value = true; |
| | | try { |
| | | const bids = await getBidListByProject(row.id!); |
| | | confirmBidOptions.value = (bids || []).map((b: any) => ({ |
| | | label: `${b.supplierName} - ${b.bidNo} (Â¥${b.bidTotalAmount ?? 0})`, |
| | | value: b.id!, |
| | | })); |
| | | } finally { |
| | | confirmLoading.value = false; |
| | | } |
| | | confirmModalApi.open(); |
| | | } |
| | | |
| | | async function handleConfirmBid() { |
| | | if (!confirmTarget.value || !confirmBidId.value) { |
| | | message.warning('è¯·éæ©ä¸æ ä¾åºåçææ è®°å½'); |
| | | return; |
| | | } |
| | | confirmLoading.value = true; |
| | | try { |
| | | await confirmTenderProject({ |
| | | id: confirmTarget.value.id!, |
| | | bidId: confirmBidId.value, |
| | | }); |
| | | message.success('ç¡®è®¤ä¸æ æåï¼éè´è®¢åå·²èªå¨çæ'); |
| | | confirmModalApi.close(); |
| | | handleRefresh(); |
| | | } finally { |
| | | confirmLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | const [ConfirmModal, confirmModalApi] = useVbenModal({ |
| | | title: 'ç¡®è®¤ä¸æ ', |
| | | onConfirm: handleConfirmBid, |
| | | onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | confirmTarget.value = null; |
| | | confirmBidId.value = undefined; |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: TenderForm, |
| | | destroyOnClose: true, |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ConfirmModal> |
| | | <div class="flex items-center gap-2"> |
| | | <span class="font-medium whitespace-nowrap">䏿 ææ è®°å½ï¼</span> |
| | | <Select |
| | | v-model:value="confirmBidId" |
| | | :options="confirmBidOptions" |
| | | :loading="confirmLoading" |
| | | placeholder="è¯·éæ©ä¸æ çææ è®°å½" |
| | | class="flex-1" |
| | | /> |
| | | </div> |
| | | </ConfirmModal> |
| | | <Grid table-title="ææ é¡¹ç®å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | label: 'åå¸', |
| | | type: 'link', |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.DRAFT, |
| | | ifShow: row.tenderStatus === TENDER_STATUS.DRAFT && row.biddingMode !== BIDDING_MODE.SIMPLE, |
| | | popConfirm: { |
| | | title: '确认è¦åå¸è¯¥é¡¹ç®åï¼åå¸åæ æ³ä¿®æ¹ã', |
| | | confirm: handlePublish.bind(null, row), |
| | |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING, |
| | | ifShow: (row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING) && row.biddingMode !== BIDDING_MODE.SIMPLE, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | |
| | | label: 'å¼å§ææ ', |
| | | type: 'link', |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED, |
| | | ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED && row.biddingMode !== BIDDING_MODE.SIMPLE, |
| | | popConfirm: { |
| | | title: '确认è¦å¼å§ææ åï¼', |
| | | confirm: handleStartBidding.bind(null, row), |
| | |
| | | type: 'link', |
| | | danger: true, |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING || row.tenderStatus === TENDER_STATUS.EVALUATING, |
| | | ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING || row.tenderStatus === TENDER_STATUS.EVALUATING || row.tenderStatus === TENDER_STATUS.PRICE_COMPARING, |
| | | popConfirm: { |
| | | title: '确认è¦å
³é该项ç®åï¼', |
| | | confirm: handleClose.bind(null, row), |
| | |
| | | label: 'å¼å§è¯æ ', |
| | | type: 'link', |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.BIDDING, |
| | | ifShow: row.tenderStatus === TENDER_STATUS.BIDDING && row.biddingMode !== BIDDING_MODE.SIMPLE, |
| | | popConfirm: { |
| | | title: '确认è¦å¼å§è¯æ åï¼', |
| | | confirm: handleStartEvaluation.bind(null, row), |
| | |
| | | label: 'çæéè´è®¢å', |
| | | type: 'link', |
| | | auth: ['srm:bid-award:update'], |
| | | ifShow: !!row.awardId && !row.purchaseOrderNo, |
| | | ifShow: !!row.awardId && !row.purchaseOrderNo && row.biddingMode !== BIDDING_MODE.SIMPLE, |
| | | popConfirm: { |
| | | title: '确认è¦çæéè´è®¢ååï¼', |
| | | confirm: handleGeneratePurchaseOrder.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'å¼å§æ¯ä»·', |
| | | type: 'link', |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.biddingMode === BIDDING_MODE.SIMPLE && row.tenderStatus === TENDER_STATUS.DRAFT, |
| | | popConfirm: { |
| | | title: '确认è¦å¼å§æ¯ä»·åï¼', |
| | | confirm: handleStartPriceComparing.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'ç¡®è®¤ä¸æ ', |
| | | type: 'link', |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.biddingMode === BIDDING_MODE.SIMPLE && row.tenderStatus === TENDER_STATUS.PRICE_COMPARING, |
| | | onClick: openConfirmModal.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | |
| | | updateTenderProject, |
| | | updateTenderProjectPartial, |
| | | } from '#/api/srm/tender'; |
| | | import { getTenderConfig } from '#/api/srm/tender-config'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { TENDER_STATUS } from '../../enums'; |
| | |
| | | } |
| | | const data = modalApi.getData<SrmTenderApi.TenderProjectVO>(); |
| | | if (!data || !data.id) { |
| | | // æ°å»ºæ¨¡å¼ï¼å è½½é
ç½®è·åé»è®¤æææ æ¨¡å¼ |
| | | try { |
| | | const config = await getTenderConfig(); |
| | | if (config?.defaultBiddingMode) { |
| | | await formApi.setFieldValue('biddingMode', config.defaultBiddingMode); |
| | | } |
| | | } catch { |
| | | // é
ç½®å 载失败ï¼ä½¿ç¨é»è®¤å¼ |
| | | } |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | |
| | | <script lang="ts" setup> |
| | | import type { NumberDictDataType } from '@vben/hooks'; |
| | | |
| | | import { ref, watch } from 'vue'; |
| | | import { computed, ref, watch } from 'vue'; |
| | | |
| | | import { DICT_TYPE, WlsStockTakingParamTypeEnum } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | |
| | | defineProps<{ |
| | | modelValue?: number; |
| | | type?: number; |
| | | value?: number; |
| | | valueCode?: string; |
| | | }>(), |
| | | { |
| | | modelValue: undefined, |
| | | type: undefined, |
| | | value: undefined, |
| | | valueCode: undefined, |
| | | }, |
| | | ); |
| | | const emit = defineEmits<{ |
| | | 'update:modelValue': [value?: number]; |
| | | 'update:value': [value?: number]; |
| | | valueChange: [ |
| | | payload: { valueCode?: string; valueId?: number; valueName?: string }, |
| | | ]; |
| | |
| | | 'number', |
| | | ) as NumberDictDataType[]; |
| | | |
| | | /** å
¼å®¹ VbenForm é»è®¤ v-model prop ('value') å Vue 3 æ å ('modelValue') */ |
| | | const effectiveValue = computed(() => props.value ?? props.modelValue); |
| | | |
| | | const locationWarehouseId = ref<number>(); // åºåºéæ©å¨ä¸´æ¶æ°æ®ï¼éä»åºåä¼ ç»åºåºéæ©å¨ |
| | | const areaWarehouseId = ref<number>(); // åºä½éæ©å¨ä¸´æ¶æ°æ®ï¼éä»åºåä¼ ç»åºåºéæ©å¨ |
| | | const areaLocationId = ref<number>(); // åºä½éæ©å¨ä¸´æ¶æ°æ®ï¼éåºåºåä¼ ç»åºä½éæ©å¨ |
| | |
| | | /** éç¨éæ©å¨ååï¼åå¡«æ¡ä»¶å¼ç¼ç ãåç§° */ |
| | | function handleSelectorChange(item?: any) { |
| | | emit('update:modelValue', item?.id); |
| | | emit('update:value', item?.id); |
| | | emit('valueChange', { |
| | | valueId: item?.id, |
| | | valueCode: item?.code || '', |
| | |
| | | /** æ¹æ¬¡éæ©å¨åå */ |
| | | function handleBatchChange(batch?: any) { |
| | | emit('update:modelValue', batch?.id); |
| | | emit('update:value', batch?.id); |
| | | emit('valueChange', { |
| | | valueId: batch?.id, |
| | | valueCode: batch?.code || '', |
| | |
| | | qualityStatusValue.value = value; |
| | | const selected = qualityStatusOptions.find((item) => item.value === value); |
| | | emit('update:modelValue', undefined); |
| | | emit('update:value', undefined); |
| | | emit('valueChange', { |
| | | valueId: undefined, |
| | | valueCode: value === null ? '' : String(value), |
| | |
| | | /** åºåºä»åºéæ©åè°ï¼æ¸
空åºåº */ |
| | | function handleLocationWarehouseChange() { |
| | | emit('update:modelValue', undefined); |
| | | emit('update:value', undefined); |
| | | emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' }); |
| | | } |
| | | |
| | |
| | | function handleAreaWarehouseChange() { |
| | | areaLocationId.value = undefined; |
| | | emit('update:modelValue', undefined); |
| | | emit('update:value', undefined); |
| | | emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' }); |
| | | } |
| | | |
| | | /** åºä½åºåºéæ©åè°ï¼æ¸
空åºä½ */ |
| | | function handleAreaLocationChange() { |
| | | emit('update:modelValue', undefined); |
| | | emit('update:value', undefined); |
| | | emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' }); |
| | | } |
| | | |
| | |
| | | : undefined; |
| | | return; |
| | | } |
| | | if (!props.modelValue) { |
| | | if (!effectiveValue.value) { |
| | | return; |
| | | } |
| | | if (props.type === WlsStockTakingParamTypeEnum.LOCATION) { |
| | | const location = await getWarehouseLocation(props.modelValue); |
| | | const location = await getWarehouseLocation(effectiveValue.value); |
| | | locationWarehouseId.value = location?.warehouseId; |
| | | } else if (props.type === WlsStockTakingParamTypeEnum.AREA) { |
| | | const area = await getWarehouseArea(props.modelValue); |
| | | const area = await getWarehouseArea(effectiveValue.value); |
| | | areaWarehouseId.value = area?.warehouseId; |
| | | areaLocationId.value = area?.locationId; |
| | | } |
| | |
| | | <template> |
| | | <WmWarehouseSelect |
| | | v-if="type === WlsStockTakingParamTypeEnum.WAREHOUSE" |
| | | :model-value="modelValue" |
| | | :model-value="effectiveValue" |
| | | @change="handleSelectorChange" |
| | | /> |
| | | <div |
| | |
| | | /> |
| | | <WmWarehouseLocationSelect |
| | | v-if="locationWarehouseId" |
| | | :model-value="modelValue" |
| | | :model-value="effectiveValue" |
| | | :warehouse-id="locationWarehouseId" |
| | | placeholder="è¯·éæ©åºåº" |
| | | @change="handleSelectorChange" |
| | |
| | | /> |
| | | <WmWarehouseAreaSelect |
| | | v-if="areaLocationId" |
| | | :model-value="modelValue" |
| | | :model-value="effectiveValue" |
| | | :location-id="areaLocationId" |
| | | placeholder="è¯·éæ©åºä½" |
| | | @change="handleSelectorChange" |
| | |
| | | </div> |
| | | <MdItemSelect |
| | | v-else-if="type === WlsStockTakingParamTypeEnum.ITEM" |
| | | :model-value="modelValue" |
| | | :model-value="effectiveValue" |
| | | @change="handleSelectorChange" |
| | | /> |
| | | <WmBatchSelect |
| | | v-else-if="type === WlsStockTakingParamTypeEnum.BATCH" |
| | | :model-value="modelValue" |
| | | :model-value="effectiveValue" |
| | | @change="handleBatchChange" |
| | | /> |
| | | <Select |
| | |
| | | component: markRaw(StockTakingPlanConditionValueInput), |
| | | // æ¡ä»¶å¼æ§ä»¶å
鍿æ¡ä»¶ç±»å忢鿩å¨ï¼ä»
éæ©ç±»ååå±ç¤º |
| | | dependencies: { |
| | | triggerFields: ['type'], |
| | | if: (values) => values.type !== null, |
| | | triggerFields: ['type', 'valueCode'], |
| | | show: (values) => values.type != null, |
| | | componentProps: (values) => ({ |
| | | type: values.type, |
| | | valueCode: values.valueCode, |