fix(datepicker): 修复日期选择器时间格式化问题
- 将所有 DatePicker 组件的 valueFormat 从 'x' 修改为标准格式 'YYYY-MM-DD HH:mm:ss' 或 'YYYY-MM-DD'
- 统一了日期时间格式以确保数据一致性和正确显示
- 修复了 BPM、CRM、ERP、MES、WLS 等模块中的日期时间字段格式问题
- 更新了 leave、business、clue、contact、contract、customer、followup 等业务模块的日期格式
- 调整了 finance、purchase、sale、stock、checkplan、checkrecord、repair 等模块的时间格式
- 修正了项目管理、生产制造、质量控制等模块中的日期时间显示问题
- 统一了系统租户、到货通知、物料收货等页面的日期格式化方式
| | |
| | | }); |
| | | |
| | | // è¡¨æ ¼é
置项å¯ä»¥ç¨ cellRender: { name: 'CellTag' }, |
| | | // props: { data: [{value, color}], labels?: {[value]: label} } |
| | | // æ¯æä¸¤ç§å¹é
模å¼: |
| | | // 1. 精确å¹é
: data å labels ä¸é½æ¯ç²¾ç¡®å¼ (ç¶æ/ç±»åæä¸¾) |
| | | // 2. éå¼å¹é
: data 为éåºéå¼, label æ¾ç¤ºåå§æ°å¼ (è¯å) |
| | | vxeUI.renderer.add('CellTag', { |
| | | renderTableDefault(renderOpts, params) { |
| | | const { props } = renderOpts; |
| | | const { column, row } = params; |
| | | return h(Tag, { color: props?.color }, () => row[column.field]); |
| | | const rawValue = row[column.field]; |
| | | |
| | | // æ¥æ¾æ¾ç¤ºææ¬ |
| | | let label = rawValue; |
| | | if (props?.labels && rawValue !== undefined && rawValue !== null) { |
| | | label = props.labels[rawValue] ?? rawValue; |
| | | } |
| | | |
| | | // æ¥æ¾é¢è²: ä¼å
精确å¹é
ï¼å¦åéå¼å¹é
|
| | | let color: string | undefined; |
| | | if (props?.data) { |
| | | const exact = props.data.find( |
| | | (item: any) => item.value === rawValue, |
| | | ); |
| | | if (exact) { |
| | | color = exact.color; |
| | | } else { |
| | | const sorted = [...props.data] |
| | | .filter((item: any) => typeof item.value === 'number') |
| | | .sort((a: any, b: any) => b.value - a.value); |
| | | const threshold = sorted.find( |
| | | (item: any) => Number(rawValue) >= item.value, |
| | | ); |
| | | if (threshold) color = threshold.color; |
| | | } |
| | | } |
| | | |
| | | return h(Tag, { color }, () => label); |
| | | }, |
| | | }); |
| | | |
| | |
| | | }); |
| | | } |
| | | |
| | | /** æ¥è¯¢ç©æå表 */ |
| | | export function getItemList(params?: Partial<MdmItemApi.Item>) { |
| | | return requestClient.get<MdmItemApi.Item[]>('/mdm/item/list', { params }); |
| | | /** æ¥è¯¢ç©æåè¡¨ï¼æIDåè¡¨ï¼ */ |
| | | export function getItemList(ids: number[]) { |
| | | return requestClient.get<MdmItemApi.Item[]>('/mdm/item/list-by-ids', { params: { ids: ids.join(',') } }); |
| | | } |
| | | |
| | | /** è·åç©æç²¾ç®å表ï¼å¯ç¨ç¶æï¼ç¨äºä¸æéæ©ï¼ */ |
| | | export function getItemSimpleList() { |
| | | return requestClient.get<MdmItemApi.Item[]>('/mdm/item/simple-list'); |
| | | } |
| | | |
| | | /** æ¥è¯¢ç©æè¯¦æ
*/ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmStatisticsApi { |
| | | export interface TenderStatisticsVO { |
| | | totalTenderCount?: number; |
| | | totalTenderAmount?: number; |
| | | totalAwardAmount?: number; |
| | | savedAmount?: number; |
| | | winRate?: number; |
| | | supplierCount?: number; |
| | | period?: string; |
| | | } |
| | | } |
| | | |
| | | /** è·åææ ç»è®¡ */ |
| | | export function getTenderStatistics(year: string) { |
| | | return requestClient.get<SrmStatisticsApi.TenderStatisticsVO>(`/srm/statistics/tender?year=${year}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmSupplierApi { |
| | | export interface SupplierVO { |
| | | id?: number; |
| | | code: string; |
| | | name: string; |
| | | type: number; |
| | | status: number; |
| | | contactName?: string; |
| | | contactMobile?: string; |
| | | contactEmail?: string; |
| | | address?: string; |
| | | bankName?: string; |
| | | bankAccount?: string; |
| | | taxNo?: string; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | } |
| | | |
| | | /** å页æ¥è¯¢ä¾åºå */ |
| | | export function getSupplierPage(params: PageParam) { |
| | | return requestClient.get<PageResult<SrmSupplierApi.SupplierVO>>('/srm/supplier/page', { params }); |
| | | } |
| | | |
| | | /** è·åä¾åºåç²¾ç®å表ï¼ä»
è¿åå·²éè¿åå
¥å®¡æ¹çä¾åºåï¼ */ |
| | | export function getSupplierSimpleList() { |
| | | return requestClient.get<SrmSupplierApi.SupplierVO[]>('/srm/supplier/simple-list'); |
| | | } |
| | | |
| | | /** è·åä¾åºå详æ
*/ |
| | | export function getSupplier(id: number) { |
| | | return requestClient.get<SrmSupplierApi.SupplierVO>(`/srm/supplier/get?id=${id}`); |
| | | } |
| | | |
| | | /** å建ä¾åºå */ |
| | | export function createSupplier(data: SrmSupplierApi.SupplierVO) { |
| | | return requestClient.post('/srm/supplier/create', data); |
| | | } |
| | | |
| | | /** æ´æ°ä¾åºå */ |
| | | export function updateSupplier(data: SrmSupplierApi.SupplierVO) { |
| | | return requestClient.put('/srm/supplier/update', data); |
| | | } |
| | | |
| | | /** å é¤ä¾åºå */ |
| | | export function deleteSupplier(id: number) { |
| | | return requestClient.delete(`/srm/supplier/delete?id=${id}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmSupplierApplyApi { |
| | | export interface ApplyVO { |
| | | id?: number; |
| | | applyNo?: string; |
| | | supplierId?: number; |
| | | supplierName: string; |
| | | contactName?: string; |
| | | contactMobile?: string; |
| | | contactEmail?: string; |
| | | address?: string; |
| | | bankName?: string; |
| | | bankAccount?: string; |
| | | taxNo?: string; |
| | | businessLicense?: string; |
| | | taxCertificate?: string; |
| | | orgCodeCertificate?: string; |
| | | applyStatus?: number; |
| | | applyStatusLabel?: string; |
| | | auditComment?: string; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | |
| | | export interface ReviewVO { |
| | | id: number; |
| | | passed: boolean; |
| | | comment?: string; |
| | | } |
| | | } |
| | | |
| | | /** å页æ¥è¯¢åå
¥ç³è¯· */ |
| | | export function getApplyPage(params: PageParam) { |
| | | return requestClient.get<PageResult<SrmSupplierApplyApi.ApplyVO>>('/srm/supplier-apply/page', { params }); |
| | | } |
| | | |
| | | /** è·ååå
¥ç³è¯·è¯¦æ
*/ |
| | | export function getApply(id: number) { |
| | | return requestClient.get<SrmSupplierApplyApi.ApplyVO>(`/srm/supplier-apply/get?id=${id}`); |
| | | } |
| | | |
| | | /** å建åå
¥ç³è¯· */ |
| | | export function createApply(data: SrmSupplierApplyApi.ApplyVO) { |
| | | return requestClient.post('/srm/supplier-apply/create', data); |
| | | } |
| | | |
| | | /** æ´æ°åå
¥ç³è¯· */ |
| | | export function updateApply(data: SrmSupplierApplyApi.ApplyVO) { |
| | | return requestClient.put('/srm/supplier-apply/update', data); |
| | | } |
| | | |
| | | /** å é¤åå
¥ç³è¯· */ |
| | | export function deleteApply(id: number) { |
| | | return requestClient.delete(`/srm/supplier-apply/delete?id=${id}`); |
| | | } |
| | | |
| | | /** æäº¤å®¡æ ¸ */ |
| | | export function submitApply(id: number) { |
| | | return requestClient.put(`/srm/supplier-apply/submit?id=${id}`); |
| | | } |
| | | |
| | | /** éè´å®¡æ ¸ */ |
| | | export function reviewPurchase(data: SrmSupplierApplyApi.ReviewVO) { |
| | | return requestClient.put('/srm/supplier-apply/review-purchase', data); |
| | | } |
| | | |
| | | /** è´¨éå®¡æ ¸ */ |
| | | export function reviewQuality(data: SrmSupplierApplyApi.ReviewVO) { |
| | | return requestClient.put('/srm/supplier-apply/review-quality', data); |
| | | } |
| | | |
| | | /** è´¢å¡å®¡æ ¸ */ |
| | | export function reviewFinance(data: SrmSupplierApplyApi.ReviewVO) { |
| | | return requestClient.put('/srm/supplier-apply/review-finance', data); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmSupplierCertificateApi { |
| | | export interface CertificateVO { |
| | | id?: number; |
| | | supplierId: number; |
| | | supplierName?: string; |
| | | certificateName: string; |
| | | certificateNo?: string; |
| | | certificateType: number; |
| | | issuingAuthority?: string; |
| | | issueDate: string; |
| | | expireDate: string; |
| | | expireStatus?: number; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | } |
| | | |
| | | /** å页æ¥è¯¢èµè´¨ */ |
| | | export function getCertificatePage(params: PageParam) { |
| | | return requestClient.get<PageResult<SrmSupplierCertificateApi.CertificateVO>>('/srm/supplier-certificate/page', { params }); |
| | | } |
| | | |
| | | /** è·åèµè´¨è¯¦æ
*/ |
| | | export function getCertificate(id: number) { |
| | | return requestClient.get<SrmSupplierCertificateApi.CertificateVO>(`/srm/supplier-certificate/get?id=${id}`); |
| | | } |
| | | |
| | | /** å建èµè´¨ */ |
| | | export function createCertificate(data: SrmSupplierCertificateApi.CertificateVO) { |
| | | return requestClient.post('/srm/supplier-certificate/create', data); |
| | | } |
| | | |
| | | /** æ´æ°èµè´¨ */ |
| | | export function updateCertificate(data: SrmSupplierCertificateApi.CertificateVO) { |
| | | return requestClient.put('/srm/supplier-certificate/update', data); |
| | | } |
| | | |
| | | /** å é¤èµè´¨ */ |
| | | export function deleteCertificate(id: number) { |
| | | return requestClient.delete(`/srm/supplier-certificate/delete?id=${id}`); |
| | | } |
| | | |
| | | /** æä¾åºåè·åèµè´¨å表 */ |
| | | export function getCertificateListBySupplier(supplierId: number) { |
| | | return requestClient.get<SrmSupplierCertificateApi.CertificateVO[]>(`/srm/supplier-certificate/list-by-supplier?supplierId=${supplierId}`); |
| | | } |
| | | |
| | | /** è·åå³å°å°æçèµè´¨å表 */ |
| | | export function getExpiringCertificates() { |
| | | return requestClient.get<SrmSupplierCertificateApi.CertificateVO[]>('/srm/supplier-certificate/expiring-list'); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmSupplierEvaluationApi { |
| | | export interface EvaluationVO { |
| | | id?: number; |
| | | supplierId: number; |
| | | supplierName?: string; |
| | | evaluationPeriod: string; |
| | | qualityScore: number; |
| | | deliveryScore: number; |
| | | priceScore: number; |
| | | serviceScore: number; |
| | | compositeScore?: number; |
| | | grade?: string; |
| | | evaluatorId?: number; |
| | | evaluatorName?: string; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | } |
| | | |
| | | /** å页æ¥è¯¢ç»©æè¯ä»· */ |
| | | export function getEvaluationPage(params: PageParam) { |
| | | return requestClient.get<PageResult<SrmSupplierEvaluationApi.EvaluationVO>>('/srm/supplier-evaluation/page', { params }); |
| | | } |
| | | |
| | | /** è·å绩æè¯ä»·è¯¦æ
*/ |
| | | export function getEvaluation(id: number) { |
| | | return requestClient.get<SrmSupplierEvaluationApi.EvaluationVO>(`/srm/supplier-evaluation/get?id=${id}`); |
| | | } |
| | | |
| | | /** å建绩æè¯ä»· */ |
| | | export function createEvaluation(data: SrmSupplierEvaluationApi.EvaluationVO) { |
| | | return requestClient.post('/srm/supplier-evaluation/create', data); |
| | | } |
| | | |
| | | /** æ´æ°ç»©æè¯ä»· */ |
| | | export function updateEvaluation(data: SrmSupplierEvaluationApi.EvaluationVO) { |
| | | return requestClient.put('/srm/supplier-evaluation/update', data); |
| | | } |
| | | |
| | | /** å é¤ç»©æè¯ä»· */ |
| | | export function deleteEvaluation(id: number) { |
| | | return requestClient.delete(`/srm/supplier-evaluation/delete?id=${id}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace SrmTenderApi { |
| | | export interface TenderProjectVO { |
| | | id?: number; |
| | | tenderNo?: string; |
| | | tenderName: string; |
| | | tenderType: number; |
| | | purchaseOrg?: string; |
| | | projectLeaderId?: number; |
| | | projectLeaderName?: string; |
| | | tenderStatus?: number; |
| | | budgetAmount?: number; |
| | | startTime?: string; |
| | | endTime?: string; |
| | | bidOpenTime?: string; |
| | | tenderDesc?: string; |
| | | qualificationRequirements?: string; |
| | | purchaseRequestId?: number; |
| | | awardId?: number; |
| | | purchaseOrderNo?: string; |
| | | createTime?: string; |
| | | } |
| | | |
| | | export interface TenderMaterialVO { |
| | | id?: number; |
| | | tenderProjectId: number; |
| | | productId?: number; |
| | | productCode?: string; |
| | | productName: string; |
| | | productSpec?: string; |
| | | unit?: string; |
| | | quantity?: number; |
| | | techRequirement?: string; |
| | | estimatedPrice?: number; |
| | | sort?: number; |
| | | } |
| | | |
| | | export interface TenderBidVO { |
| | | id?: number; |
| | | tenderProjectId: number; |
| | | supplierId: number; |
| | | supplierName?: string; |
| | | bidNo: string; |
| | | bidStatus?: number; |
| | | bidTime?: string; |
| | | bidTotalAmount?: number; |
| | | bidFileName?: string; |
| | | bidFileUrl?: string; |
| | | createTime?: string; |
| | | } |
| | | |
| | | export interface QuoteVO { |
| | | id?: number; |
| | | tenderProjectId?: number; |
| | | supplierId?: number; |
| | | bidId: number; |
| | | tenderMaterialId: number; |
| | | materialName?: string; |
| | | quotePrice?: number; |
| | | taxRate?: number; |
| | | deliveryCycle?: number; |
| | | paymentTerms?: string; |
| | | warrantyPeriod?: number; |
| | | remark?: string; |
| | | } |
| | | |
| | | export interface BidEvaluationVO { |
| | | id?: number; |
| | | tenderProjectId: number; |
| | | supplierId: number; |
| | | supplierName?: string; |
| | | bidId: number; |
| | | priceScore?: number; |
| | | techScore?: number; |
| | | deliveryScore?: number; |
| | | serviceScore?: number; |
| | | compositeScore?: number; |
| | | rank?: number; |
| | | evaluatorId?: number; |
| | | evaluatorName?: string; |
| | | evaluationOpinion?: string; |
| | | } |
| | | |
| | | export interface BidAwardVO { |
| | | id?: number; |
| | | tenderProjectId: number; |
| | | supplierId: number; |
| | | supplierName?: string; |
| | | bidId: number; |
| | | awardNo: string; |
| | | awardStatus?: number; |
| | | awardAmount?: number; |
| | | awardTime?: string; |
| | | noticeFileName?: string; |
| | | noticeFileUrl?: string; |
| | | purchaseOrderId?: number; |
| | | remark?: string; |
| | | } |
| | | } |
| | | |
| | | // ========== ææ é¡¹ç® ========== |
| | | |
| | | export function getTenderProjectPage(params: PageParam) { |
| | | return requestClient.get<PageResult<SrmTenderApi.TenderProjectVO>>('/srm/tender-project/page', { params }); |
| | | } |
| | | |
| | | export function getTenderProject(id: number) { |
| | | return requestClient.get<SrmTenderApi.TenderProjectVO>(`/srm/tender-project/get?id=${id}`); |
| | | } |
| | | |
| | | export function createTenderProject(data: SrmTenderApi.TenderProjectVO) { |
| | | return requestClient.post('/srm/tender-project/create', data); |
| | | } |
| | | |
| | | export function updateTenderProject(data: SrmTenderApi.TenderProjectVO) { |
| | | return requestClient.put('/srm/tender-project/update', data); |
| | | } |
| | | |
| | | export function deleteTenderProject(id: number) { |
| | | return requestClient.delete(`/srm/tender-project/delete?id=${id}`); |
| | | } |
| | | |
| | | export function publishTender(id: number) { |
| | | return requestClient.put(`/srm/tender-project/publish?id=${id}`); |
| | | } |
| | | |
| | | export function closeTender(id: number) { |
| | | return requestClient.put(`/srm/tender-project/close?id=${id}`); |
| | | } |
| | | |
| | | export function startBidding(id: number) { |
| | | return requestClient.put(`/srm/tender-project/start-bidding?id=${id}`); |
| | | } |
| | | |
| | | export function startEvaluation(id: number) { |
| | | return requestClient.put(`/srm/tender-project/start-evaluation?id=${id}`); |
| | | } |
| | | |
| | | // ========== ç©æå表 ========== |
| | | |
| | | export function getMaterialList(tenderProjectId: number) { |
| | | return requestClient.get<SrmTenderApi.TenderMaterialVO[]>(`/srm/tender-project/material/list?tenderProjectId=${tenderProjectId}`); |
| | | } |
| | | |
| | | export function createMaterial(data: SrmTenderApi.TenderMaterialVO) { |
| | | return requestClient.post('/srm/tender-project/material/create', data); |
| | | } |
| | | |
| | | export function updateMaterial(data: SrmTenderApi.TenderMaterialVO) { |
| | | return requestClient.put('/srm/tender-project/material/update', data); |
| | | } |
| | | |
| | | export function deleteMaterial(id: number) { |
| | | return requestClient.delete(`/srm/tender-project/material/delete?id=${id}`); |
| | | } |
| | | |
| | | // ========== ææ ç®¡ç ========== |
| | | |
| | | export function getBidListByProject(tenderProjectId: number) { |
| | | return requestClient.get<SrmTenderApi.TenderBidVO[]>(`/srm/tender-bid/list-by-project?tenderProjectId=${tenderProjectId}`); |
| | | } |
| | | |
| | | export function getBid(id: number) { |
| | | return requestClient.get<SrmTenderApi.TenderBidVO>(`/srm/tender-bid/get?id=${id}`); |
| | | } |
| | | |
| | | export function createBid(data: SrmTenderApi.TenderBidVO) { |
| | | return requestClient.post('/srm/tender-bid/create', data); |
| | | } |
| | | |
| | | export function withdrawBid(id: number) { |
| | | return requestClient.put(`/srm/tender-bid/withdraw?id=${id}`); |
| | | } |
| | | |
| | | // ========== æ¥ä»·ç®¡ç ========== |
| | | |
| | | export function getQuoteList(bidId: number) { |
| | | return requestClient.get<SrmTenderApi.QuoteVO[]>(`/srm/tender-bid/quote/list-by-bid?bidId=${bidId}`); |
| | | } |
| | | |
| | | export function createQuote(data: SrmTenderApi.QuoteVO) { |
| | | return requestClient.post('/srm/tender-bid/quote/create', data); |
| | | } |
| | | |
| | | export function updateQuote(data: SrmTenderApi.QuoteVO) { |
| | | return requestClient.put('/srm/tender-bid/quote/update', data); |
| | | } |
| | | |
| | | // ========== è¯æ 管ç ========== |
| | | |
| | | export function getEvaluationList(tenderProjectId: number) { |
| | | return requestClient.get<SrmTenderApi.BidEvaluationVO[]>(`/srm/bid-evaluation/list-by-project?tenderProjectId=${tenderProjectId}`); |
| | | } |
| | | |
| | | export function createBidEvaluation(data: SrmTenderApi.BidEvaluationVO) { |
| | | return requestClient.post('/srm/bid-evaluation/create', data); |
| | | } |
| | | |
| | | export function calculateRanking(tenderProjectId: number) { |
| | | return requestClient.put(`/srm/bid-evaluation/calculate-ranking?tenderProjectId=${tenderProjectId}`); |
| | | } |
| | | |
| | | // ========== 弿 ========== |
| | | |
| | | export function createBidOpen(tenderProjectId: number) { |
| | | return requestClient.post(`/srm/bid-evaluation/bid-open/create?tenderProjectId=${tenderProjectId}`); |
| | | } |
| | | |
| | | export function getBidOpen(id: number) { |
| | | return requestClient.get(`/srm/bid-evaluation/bid-open/get?id=${id}`); |
| | | } |
| | | |
| | | // ========== 宿 管ç ========== |
| | | |
| | | export function createAward(data: SrmTenderApi.BidAwardVO) { |
| | | return requestClient.post('/srm/bid-award/create', data); |
| | | } |
| | | |
| | | export function getAward(id: number) { |
| | | return requestClient.get<SrmTenderApi.BidAwardVO>(`/srm/bid-award/get?id=${id}`); |
| | | } |
| | | |
| | | export function approveAward(id: number) { |
| | | return requestClient.put(`/srm/bid-award/approve?id=${id}`); |
| | | } |
| | | |
| | | export function generatePurchaseOrder(id: number) { |
| | | return requestClient.put(`/srm/bid-award/generate-purchase-order/${id}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { RouteRecordRaw } from 'vue-router'; |
| | | |
| | | const routes: RouteRecordRaw[] = [ |
| | | { |
| | | path: '/srm', |
| | | name: 'SrmCenter', |
| | | meta: { |
| | | title: 'SRM ä¾åºå管ç', |
| | | icon: 'lucide:package-search', |
| | | keepAlive: true, |
| | | hideInMenu: true, |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'tender/detail', |
| | | name: 'SrmTenderDetail', |
| | | meta: { |
| | | title: 'ææ è¯¦æ
', |
| | | activePath: '/srm/tender', |
| | | }, |
| | | component: () => import('#/views/srm/tender/detail.vue'), |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | export default routes; |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©å¼å§æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç»ææ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©é¢è®¡æäº¤æ¥æ', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©ä¸æ¬¡èç³»æ¶é´', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©ä¸æ¬¡èç³»æ¶é´', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©ä¸åæ¥æ', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©ååå¼å§æ¶é´', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©ååç»ææ¶é´', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©ä¸æ¬¡èç³»æ¶é´', |
| | | allowClear: true, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©ä¸æ¬¡èç³»æ¶é´', |
| | | }, |
| | | rules: 'required', |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©åæ¬¾æ¥æ', |
| | | showTime: false, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | format: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è®¡å忬¾æ¥æ', |
| | | showTime: false, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | format: 'YYYY-MM-DD', |
| | | }, |
| | | defaultValue: new Date(), |
| | |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æ¥ä»·æ¥æ', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æææ', |
| | | }, |
| | | }, |
| | |
| | | import { erpPriceInputFormatter } from '@vben/utils'; |
| | | |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | |
| | | placeholder: '鿩仿¬¾æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | placeholder: 'éæ©æ¶æ¬¾æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | import { z } from '#/adapter/form'; |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | | import { getProductSimpleList } from '#/api/erp/product/product'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | |
| | | placeholder: 'éæ©å
¥åºæ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | |
| | | placeholder: 'éæ©è®¢åæ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | |
| | | placeholder: 'éæ©ç³è¯·æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | disabled: formType === 'detail', |
| | | }, |
| | | rules: 'required', |
| | |
| | | import { message, Select } from 'ant-design-vue'; |
| | | |
| | | import { generatePurchaseOrder } from '#/api/erp/purchase/request'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | |
| | | import { z } from '#/adapter/form'; |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | | import { getProductSimpleList } from '#/api/erp/product/product'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | |
| | | placeholder: 'éæ©éè´§æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getSupplierStatistics } from '#/api/erp/purchase/statistics'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getUncompletedOrders } from '#/api/erp/purchase/statistics'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©åä½å¼å§æ¶é´', |
| | | class: '!w-full', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | placeholder: 'éæ©è®¢åæ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | placeholder: 'éæ©åºåºæ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | placeholder: 'éæ©éè´§æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | placeholder: 'éæ©çç¹æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { getProductSimpleList } from '#/api/erp/product/product'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | |
| | | placeholder: 'éæ©å
¥åºæ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | placeholder: 'éæ©è°åº¦æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | placeholder: 'éæ©åºåºæ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©åºçå¹´', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©åºçæ¥æ', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | componentProps: { |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©åºçæ¥æ', |
| | | }, |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©å¼å§æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©ç»ææ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©å¼å§æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©ç»ææ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©ç¹æ£æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | disabled: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['id'], |
| | |
| | | disabled: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['id'], |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©ä¿å
»æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©æ¥ä¿®æ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©å®ææ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | // ç»´ä¿®ä¸(â¥CONFIRMED)æå±ç¤ºï¼ä»
"å®æç»´ä¿®"å¼¹çªå¯ç¼è¾å¹¶å¿
å¡«ï¼å
¶ä½æåªè¯»åæ¾ |
| | | dependencies: { |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©éªæ¶æ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | // éªæ¶ä¿¡æ¯ä¸ºå·²ç¡®è®¤(â¥FINISHED)æèªå¨äº§ççåªè¯»åæ¾å段 |
| | | dependencies: { |
| | |
| | | document.body, |
| | | inputReadOnly: true, |
| | | placeholder: options?.placeholder ?? "è¯·éæ©æ¥æ", |
| | | valueFormat: "x", |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }; |
| | | } |
| | | |
| | |
| | | fieldName: 'planFinishDate', |
| | | label: '计å宿', |
| | | component: 'DatePicker', |
| | | componentProps: { disabled: readonly, class: 'w-full', valueFormat: 'x' }, |
| | | componentProps: { disabled: readonly, class: 'w-full', valueFormat: 'YYYY-MM-DD' }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | |
| | | fieldName: 'planFinishDate', |
| | | label: '计å宿', |
| | | component: 'DatePicker', |
| | | componentProps: { class: 'w-full', valueFormat: 'x' }, |
| | | componentProps: { class: 'w-full', valueFormat: 'YYYY-MM-DD' }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©å¤ç½®æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | isUpdate |
| | |
| | | class: '!w-full', |
| | | placeholder: 'è¯·éæ©è¿å
¥å·¥åºæ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | class: '!w-full', |
| | | placeholder: 'è¯·éæ©åºå·¥åºæ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©æ¥å·¥æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | class: '!w-full', |
| | | placeholder: 'è¯·éæ©éæ±æ¥æ', |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | disabled: !isCreate && !isEdit, |
| | | }, |
| | | rules: 'required', |
| | |
| | | class: '!w-full', |
| | | disabled: true, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | class: '!w-full', |
| | | placeholder: 'è¯·éæ©å¼å§æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | // å¼å§æ¶é´åæ´ï¼éæ°è®¡ç®ç»ææ¶é´ |
| | | onChange: () => recalcEndTime(formApi), |
| | | }, |
| | |
| | | class: '!w-full', |
| | | disabled: true, |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | disabled: headerReadonly, |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©éæ±æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æ£æµæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æ¥ææ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æ£æµæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åºè´§æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æ£æµæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©æ£æµæ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['maintenType'], |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©éæ±æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©éæ±æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©éææ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©éææ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ============================================================ |
| | | // SRM ç»ä¸æä¸¾å¸¸é |
| | | // ææå¼ä¸¥æ ¼å¯¹é½å端æä¸¾å®ä¹ (yudao-module-srm/enums/) |
| | | // ============================================================ |
| | | |
| | | /** é项类å (Select/RadioGroupéç¨) */ |
| | | export interface EnumOption { |
| | | label: string; |
| | | value: number | string; |
| | | } |
| | | |
| | | /** CellTag æ°æ®ç±»å */ |
| | | export interface CellTagItem { |
| | | value: number | string; |
| | | color: string; |
| | | } |
| | | |
| | | // ========== ä¾åºå (åæ®µå¼å¯¹é½ CommonStatusEnum + SupplierTypeEnum) ========== |
| | | |
| | | /** ä¾åºåç¶æ â å¯¹é½ CommonStatusEnum (0=å¼å¯ 1=å
³é) */ |
| | | export const SUPPLIER_STATUS = { |
| | | ENABLE: 0, |
| | | DISABLE: 1, |
| | | } as const; |
| | | |
| | | export const SUPPLIER_STATUS_OPTIONS: EnumOption[] = [ |
| | | { label: 'å¼å¯', value: 0 }, |
| | | { label: 'å
³é', value: 1 }, |
| | | ]; |
| | | |
| | | export const SUPPLIER_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = { |
| | | data: [ |
| | | { value: 0, color: 'green' }, |
| | | { value: 1, color: 'red' }, |
| | | ], |
| | | labels: { 0: 'å¼å¯', 1: 'å
³é' }, |
| | | }; |
| | | |
| | | /** ä¾åºåç±»å â å¯¹é½ SupplierTypeEnum (1=ç产å 2=è´¸æå 3=æå¡å 4=å
¶ä») */ |
| | | export const SUPPLIER_TYPE = { |
| | | PRODUCTION: 1, |
| | | TRADING: 2, |
| | | SERVICE: 3, |
| | | OTHER: 4, |
| | | } as const; |
| | | |
| | | export const SUPPLIER_TYPE_OPTIONS: EnumOption[] = [ |
| | | { label: 'ç产å', value: 1 }, |
| | | { label: 'è´¸æå', value: 2 }, |
| | | { label: 'æå¡å', value: 3 }, |
| | | { label: 'å
¶ä»', value: 4 }, |
| | | ]; |
| | | |
| | | export const SUPPLIER_TYPE_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = { |
| | | data: [ |
| | | { value: 1, color: 'blue' }, |
| | | { value: 2, color: 'green' }, |
| | | { value: 3, color: 'orange' }, |
| | | { value: 4, color: 'default' }, |
| | | ], |
| | | labels: { 1: 'ç产å', 2: 'è´¸æå', 3: 'æå¡å', 4: 'å
¶ä»' }, |
| | | }; |
| | | |
| | | // ========== åå
¥ç³è¯· â å¯¹é½ SupplierApplyStatusEnum (0/10/20/30/40/50) ========== |
| | | |
| | | export const APPLY_STATUS = { |
| | | DRAFT: 0, |
| | | PENDING_PURCHASE: 10, |
| | | PENDING_QUALITY: 20, |
| | | PENDING_FINANCE: 30, |
| | | APPROVED: 40, |
| | | REJECTED: 50, |
| | | } as const; |
| | | |
| | | export const APPLY_STATUS_OPTIONS: EnumOption[] = [ |
| | | { label: 'å¾
æäº¤', value: 0 }, |
| | | { label: 'å¾
éè´å®¡æ ¸', value: 10 }, |
| | | { label: 'å¾
è´¨éå®¡æ ¸', value: 20 }, |
| | | { label: 'å¾
è´¢å¡å®¡æ ¸', value: 30 }, |
| | | { label: 'å·²éè¿', value: 40 }, |
| | | { label: 'å·²æç»', value: 50 }, |
| | | ]; |
| | | |
| | | export const APPLY_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = { |
| | | data: [ |
| | | { value: 0, color: 'gray' }, |
| | | { value: 10, color: 'blue' }, |
| | | { value: 20, color: 'orange' }, |
| | | { value: 30, color: 'purple' }, |
| | | { value: 40, color: 'green' }, |
| | | { value: 50, color: 'red' }, |
| | | ], |
| | | labels: { 0: 'å¾
æäº¤', 10: 'å¾
éè´å®¡æ ¸', 20: 'å¾
è´¨éå®¡æ ¸', 30: 'å¾
è´¢å¡å®¡æ ¸', 40: 'å·²éè¿', 50: 'å·²æç»' }, |
| | | }; |
| | | |
| | | // ========== èµè´¨è¯ä¹¦ â å¯¹é½ CertificateTypeEnum (1=è¥ä¸æ§ç
§ 2=认è¯è¯ä¹¦ 3=æ£æµæ¥å 4=å
¶ä») ========== |
| | | |
| | | export const CERTIFICATE_TYPE = { |
| | | BUSINESS_LICENSE: 1, |
| | | CERTIFICATION: 2, |
| | | INSPECTION_REPORT: 3, |
| | | OTHER: 4, |
| | | } as const; |
| | | |
| | | export const CERTIFICATE_TYPE_OPTIONS: EnumOption[] = [ |
| | | { label: 'è¥ä¸æ§ç
§', value: 1 }, |
| | | { label: '认è¯è¯ä¹¦', value: 2 }, |
| | | { label: 'æ£æµæ¥å', value: 3 }, |
| | | { label: 'å
¶ä»', value: 4 }, |
| | | ]; |
| | | |
| | | export const CERTIFICATE_TYPE_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = { |
| | | data: [ |
| | | { value: 1, color: 'blue' }, |
| | | { value: 2, color: 'green' }, |
| | | { value: 3, color: 'orange' }, |
| | | { value: 4, color: 'default' }, |
| | | ], |
| | | labels: { 1: 'è¥ä¸æ§ç
§', 2: '认è¯è¯ä¹¦', 3: 'æ£æµæ¥å', 4: 'å
¶ä»' }, |
| | | }; |
| | | |
| | | // ========== 绩æè¯ä»· â å端 evaluationLevel 为 String ç±»å (A/B/C/D) ========== |
| | | |
| | | export const EVALUATION_GRADE = { |
| | | A: 'A', |
| | | B: 'B', |
| | | C: 'C', |
| | | D: 'D', |
| | | } as const; |
| | | |
| | | export const EVALUATION_GRADE_CELLTAG: { data: CellTagItem[] } = { |
| | | data: [ |
| | | { value: 'A', color: 'green' }, |
| | | { value: 'B', color: 'blue' }, |
| | | { value: 'C', color: 'orange' }, |
| | | { value: 'D', color: 'red' }, |
| | | ], |
| | | }; |
| | | |
| | | /** 综åè¯ååºé´ â å端计ç®: >=90âA, >=80âB, >=70âC, <70âD */ |
| | | export const COMPOSITE_SCORE_CELLTAG: { data: CellTagItem[] } = { |
| | | data: [ |
| | | { value: 90, color: 'green' }, |
| | | { value: 80, color: 'blue' }, |
| | | { value: 70, color: 'orange' }, |
| | | { value: 0, color: 'red' }, |
| | | ], |
| | | }; |
| | | |
| | | // ========== ææ é¡¹ç® â å¯¹é½ TenderStatusEnum (0/1/2/3/4/5) + TenderTypeEnum (1/2/3) ========== |
| | | |
| | | /** ææ ç±»å â å¯¹é½ TenderTypeEnum (1=å
¬å¼ææ 2=éè¯·ææ 3=ç«äºæ§è°å¤) */ |
| | | export const TENDER_TYPE = { |
| | | PUBLIC: 1, |
| | | INVITATION: 2, |
| | | COMPETITIVE_NEGOTIATION: 3, |
| | | } as const; |
| | | |
| | | export const TENDER_TYPE_OPTIONS: EnumOption[] = [ |
| | | { label: 'å
¬å¼ææ ', value: 1 }, |
| | | { label: 'éè¯·ææ ', value: 2 }, |
| | | { label: 'ç«äºæ§è°å¤', value: 3 }, |
| | | ]; |
| | | |
| | | export const TENDER_TYPE_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = { |
| | | data: [ |
| | | { value: 1, color: 'blue' }, |
| | | { value: 2, color: 'blue' }, |
| | | { value: 3, color: 'green' }, |
| | | ], |
| | | labels: { 1: 'å
¬å¼ææ ', 2: 'éè¯·ææ ', 3: 'ç«äºæ§è°å¤' }, |
| | | }; |
| | | |
| | | /** ææ é¡¹ç®ç¶æ â å¯¹é½ TenderStatusEnum (0=è稿 1=åå¸ä¸ 2=ææ ä¸ 3=è¯æ ä¸ 4=已宿 5=å·²å
³é) */ |
| | | export const TENDER_STATUS = { |
| | | DRAFT: 0, |
| | | PUBLISHED: 1, |
| | | BIDDING: 2, |
| | | EVALUATING: 3, |
| | | AWARDED: 4, |
| | | CLOSED: 5, |
| | | } as const; |
| | | |
| | | export const TENDER_STATUS_OPTIONS: EnumOption[] = [ |
| | | { label: 'è稿', value: 0 }, |
| | | { label: 'åå¸ä¸', value: 1 }, |
| | | { label: 'ææ ä¸', value: 2 }, |
| | | { label: 'è¯æ ä¸', value: 3 }, |
| | | { label: '已宿 ', value: 4 }, |
| | | { label: 'å·²å
³é', value: 5 }, |
| | | ]; |
| | | |
| | | export const TENDER_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = { |
| | | data: [ |
| | | { value: 0, color: 'gray' }, |
| | | { value: 1, color: 'blue' }, |
| | | { value: 2, color: 'cyan' }, |
| | | { value: 3, color: 'orange' }, |
| | | { value: 4, color: 'green' }, |
| | | { value: 5, color: 'red' }, |
| | | ], |
| | | labels: { 0: 'è稿', 1: 'åå¸ä¸', 2: 'ææ ä¸', 3: 'è¯æ ä¸', 4: '已宿 ', 5: 'å·²å
³é' }, |
| | | }; |
| | | |
| | | /** ææ ç¶æ Ant Design Tag é¢è²æ å° */ |
| | | export const TENDER_STATUS_COLORS: Record<number, string> = { |
| | | [TENDER_STATUS.DRAFT]: 'default', |
| | | [TENDER_STATUS.PUBLISHED]: 'blue', |
| | | [TENDER_STATUS.BIDDING]: 'cyan', |
| | | [TENDER_STATUS.EVALUATING]: 'orange', |
| | | [TENDER_STATUS.AWARDED]: 'green', |
| | | [TENDER_STATUS.CLOSED]: 'red', |
| | | }; |
| | | |
| | | // ========== ææ â å¯¹é½ BidStatusEnum (0=æ¥å 10=å·²ææ 20=æ¤æ 30=䏿 40=æªä¸æ ) ========== |
| | | |
| | | export const BID_STATUS = { |
| | | REGISTERED: 0, |
| | | BID: 10, |
| | | WITHDRAWN: 20, |
| | | WIN: 30, |
| | | LOSE: 40, |
| | | } as const; |
| | | |
| | | export const BID_STATUS_MAP: Record<number, string> = { |
| | | 0: 'æ¥å', |
| | | 10: 'å·²ææ ', |
| | | 20: 'æ¤æ ', |
| | | 30: '䏿 ', |
| | | 40: 'æªä¸æ ', |
| | | }; |
| | | |
| | | // ========== 宿 â å¯¹é½ AwardStatusEnum (0=å¾
å®¡æ¹ 10=已宿 20=å·²çæè®¢å) ========== |
| | | |
| | | export const AWARD_STATUS = { |
| | | PENDING_APPROVAL: 0, |
| | | AWARDED: 10, |
| | | ORDER_GENERATED: 20, |
| | | } as const; |
| | | |
| | | export const AWARD_STATUS_MAP: Record<number, string> = { |
| | | 0: 'å¾
审æ¹', |
| | | 10: '已宿 ', |
| | | 20: 'å·²çæè®¢å', |
| | | }; |
| | | |
| | | // ========== å·¥å
·å½æ° ========== |
| | | |
| | | /** æ ¹æ®å¼è·åæ ç¾ */ |
| | | export function getLabel(map: Record<number, string>, value?: number): string { |
| | | return value !== undefined ? map[value] || String(value) : ''; |
| | | } |
| | | |
| | | /** å° options æ°ç»è½¬ä¸º labels æ å° */ |
| | | export function optionsToLabels(options: EnumOption[]): Record<number, string> { |
| | | const labels: Record<number, string> = {}; |
| | | for (const opt of options) { |
| | | if (typeof opt.value === 'number') { |
| | | labels[opt.value] = opt.label; |
| | | } |
| | | } |
| | | return labels; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { EchartsUIType } from '@vben/plugins/echarts'; |
| | | import type { SrmStatisticsApi } from '#/api/srm/statistics'; |
| | | |
| | | import { onMounted, ref, watch } from 'vue'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; |
| | | |
| | | import { getTenderStatistics } from '#/api/srm/statistics'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | |
| | | defineOptions({ name: 'SrmStatistics' }); |
| | | |
| | | const year = ref(new Date().getFullYear().toString()); |
| | | const stats = ref<SrmStatisticsApi.TenderStatisticsVO>({}); |
| | | const supplierCount = ref(0); |
| | | |
| | | async function loadStats() { |
| | | stats.value = await getTenderStatistics(year.value); |
| | | } |
| | | async function loadSupplierCount() { |
| | | const list = await getSupplierSimpleList(); |
| | | supplierCount.value = list?.length ?? 0; |
| | | } |
| | | |
| | | // å¾è¡¨ |
| | | const pieChartRef = ref<EchartsUIType>(); |
| | | const barChartRef = ref<EchartsUIType>(); |
| | | const { renderEcharts: renderPie } = useEcharts(pieChartRef); |
| | | const { renderEcharts: renderBar } = useEcharts(barChartRef); |
| | | |
| | | async function renderCharts() { |
| | | // 饼å¾ï¼ä¾åºåç¶æåå¸ |
| | | await renderPie({ |
| | | title: { text: 'ä¾åºåç¶æåå¸', left: 'center' }, |
| | | tooltip: { trigger: 'item' }, |
| | | legend: { bottom: 0 }, |
| | | series: [{ |
| | | type: 'pie', |
| | | radius: ['40%', '70%'], |
| | | data: [ |
| | | { value: supplierCount.value, name: 'å·²å¯ç¨' }, |
| | | { value: 0, name: 'å·²ç¦ç¨' }, |
| | | ], |
| | | }], |
| | | }); |
| | | |
| | | // æ±ç¶å¾ï¼ææ ç»è®¡æ¦è§ |
| | | await renderBar({ |
| | | title: { text: `${year.value} å¹´ææ ç»è®¡æ¦è§`, left: 'center' }, |
| | | tooltip: { trigger: 'axis' }, |
| | | xAxis: { type: 'category', data: ['ææ æ»æ°', 'åä¸ä¾åºå', '宿 éé¢(ä¸)', 'è约éé¢(ä¸)'] }, |
| | | yAxis: { type: 'value' }, |
| | | series: [{ |
| | | type: 'bar', |
| | | data: [ |
| | | stats.value.totalTenderCount ?? 0, |
| | | stats.value.supplierCount ?? 0, |
| | | +(((stats.value.totalAwardAmount ?? 0) / 10000).toFixed(2)), |
| | | +(((stats.value.savedAmount ?? 0) / 10000).toFixed(2)), |
| | | ], |
| | | itemStyle: { |
| | | color: (params: any) => ['#3b82f6', '#ec4899', '#f97316', '#8b5cf6'][params.dataIndex], |
| | | }, |
| | | }], |
| | | }); |
| | | } |
| | | |
| | | watch([stats, supplierCount], () => renderCharts()); |
| | | onMounted(() => { loadStats(); loadSupplierCount(); }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <div class="p-4"> |
| | | <div class="mb-6 flex items-center gap-4"> |
| | | <span class="text-lg font-medium">SRM ç»è®¡çæ¿</span> |
| | | <a-input v-model:value="year" placeholder="年份" style="width: 120px" @change="loadStats" /> |
| | | </div> |
| | | |
| | | <!-- 6 个ç»è®¡å¡ç --> |
| | | <div class="mb-6 grid grid-cols-3 gap-4"> |
| | | <div class="rounded-lg bg-blue-50 p-6 text-center shadow-sm"> |
| | | <div class="text-3xl font-bold text-blue-600">{{ stats.totalTenderCount ?? 0 }}</div> |
| | | <div class="mt-2 text-gray-500">ææ æ»æ°</div> |
| | | </div> |
| | | <div class="rounded-lg bg-green-50 p-6 text-center shadow-sm"> |
| | | <div class="text-3xl font-bold text-green-600">{{ stats.totalTenderAmount?.toLocaleString() ?? 0 }}</div> |
| | | <div class="mt-2 text-gray-500">ææ æ»éé¢(å
)</div> |
| | | </div> |
| | | <div class="rounded-lg bg-orange-50 p-6 text-center shadow-sm"> |
| | | <div class="text-3xl font-bold text-orange-600">{{ stats.totalAwardAmount?.toLocaleString() ?? 0 }}</div> |
| | | <div class="mt-2 text-gray-500">宿 æ»éé¢(å
)</div> |
| | | </div> |
| | | <div class="rounded-lg bg-purple-50 p-6 text-center shadow-sm"> |
| | | <div class="text-3xl font-bold text-purple-600">{{ stats.savedAmount?.toLocaleString() ?? 0 }}</div> |
| | | <div class="mt-2 text-gray-500">è约éé¢(å
)</div> |
| | | </div> |
| | | <div class="rounded-lg bg-teal-50 p-6 text-center shadow-sm"> |
| | | <div class="text-3xl font-bold text-teal-600">{{ stats.winRate ?? 0 }}%</div> |
| | | <div class="mt-2 text-gray-500">䏿 ç</div> |
| | | </div> |
| | | <div class="rounded-lg bg-rose-50 p-6 text-center shadow-sm"> |
| | | <div class="text-3xl font-bold text-rose-600">{{ stats.supplierCount ?? 0 }}</div> |
| | | <div class="mt-2 text-gray-500">å䏿æ ä¾åºå</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="mb-6 grid grid-cols-2 gap-4"> |
| | | <div class="rounded-lg bg-white p-4 shadow-sm"> |
| | | <EchartsUI ref="pieChartRef" class="h-[350px] w-full" /> |
| | | </div> |
| | | <div class="rounded-lg bg-white p-4 shadow-sm"> |
| | | <EchartsUI ref="barChartRef" class="h-[350px] w-full" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºé¨ä¿¡æ¯ --> |
| | | <div class="rounded-lg bg-white p-4 shadow-sm"> |
| | | <h3 class="mb-3 text-base font-medium">ä¾åºåæ»æ°: {{ supplierCount }} å®¶</h3> |
| | | <a-empty v-if="supplierCount === 0" description="ææ ä¾åºåæ°æ®" /> |
| | | </div> |
| | | </div> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | export { default as SrmSupplierSelect } from './select.vue'; |
| | | export { default as SrmSupplierSelectDialog } from './select-dialog.vue'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | |
| | | import { nextTick, ref } from 'vue'; |
| | | |
| | | import { CommonStatusEnum } from '@vben/constants'; |
| | | |
| | | import { message, Modal } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getSupplierPage } from '#/api/srm/supplier'; |
| | | |
| | | defineOptions({ name: 'SrmSupplierSelectDialog' }); |
| | | |
| | | const emit = defineEmits<{ |
| | | selected: [rows: SrmSupplierApi.SupplierVO[]]; |
| | | }>(); |
| | | |
| | | const open = ref(false); |
| | | const multiple = ref(true); |
| | | const approvalFilter = ref(true); |
| | | const selectedRows = ref<SrmSupplierApi.SupplierVO[]>([]); |
| | | const preSelectedIds = ref<number[]>([]); |
| | | |
| | | function getTableRows() { |
| | | return gridApi.grid.getTableData().fullData as SrmSupplierApi.SupplierVO[]; |
| | | } |
| | | |
| | | function getMultipleSelectedRows() { |
| | | const selectedMap = new Map<number, SrmSupplierApi.SupplierVO>(); |
| | | const records = [ |
| | | ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []), |
| | | ...(gridApi.grid.getCheckboxRecords?.() ?? []), |
| | | ] as SrmSupplierApi.SupplierVO[]; |
| | | records.forEach((row) => { |
| | | const rowId = row.id; |
| | | if (rowId) { |
| | | selectedMap.set(rowId, row); |
| | | } |
| | | }); |
| | | return [...selectedMap.values()]; |
| | | } |
| | | |
| | | function handleCheckboxSelectChange() { |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | |
| | | function handleRadioChange(row: SrmSupplierApi.SupplierVO) { |
| | | selectedRows.value = [row]; |
| | | } |
| | | |
| | | async function toggleMultipleRow(row: SrmSupplierApi.SupplierVO) { |
| | | const selected = gridApi.grid.isCheckedByCheckboxRow(row); |
| | | await gridApi.grid.setCheckboxRow(row, !selected); |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | |
| | | async function handleCellDblclick({ row }: { row: SrmSupplierApi.SupplierVO }) { |
| | | if (multiple.value) { |
| | | await toggleMultipleRow(row); |
| | | return; |
| | | } |
| | | selectedRows.value = [row]; |
| | | await gridApi.grid.setRadioRow(row); |
| | | handleConfirm(); |
| | | } |
| | | |
| | | async function applyPreSelection() { |
| | | if (preSelectedIds.value.length === 0) { |
| | | return; |
| | | } |
| | | const rows = getTableRows(); |
| | | for (const row of rows) { |
| | | if ( |
| | | row.id === undefined || |
| | | row.id === null || |
| | | !preSelectedIds.value.includes(row.id) |
| | | ) { |
| | | continue; |
| | | } |
| | | if (multiple.value) { |
| | | await gridApi.grid.setCheckboxRow(row, true); |
| | | } else { |
| | | await gridApi.grid.setRadioRow(row); |
| | | selectedRows.value = [row]; |
| | | return; |
| | | } |
| | | } |
| | | if (multiple.value) { |
| | | selectedRows.value = getMultipleSelectedRows(); |
| | | } |
| | | } |
| | | |
| | | function useSelectGridFormSchema() { |
| | | return [ |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ä¾åºååç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºååç§°', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä¾åºåç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¾åºåç¼ç ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | function useSelectGridColumns(multiple = false): VxeTableGridOptions<SrmSupplierApi.SupplierVO>['columns'] { |
| | | return [ |
| | | { |
| | | type: multiple ? 'checkbox' : 'radio', |
| | | width: 50, |
| | | }, |
| | | { |
| | | field: 'code', |
| | | title: 'ç¼ç ', |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | field: 'name', |
| | | title: 'ä¾åºååç§°', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | field: 'contactName', |
| | | title: 'è系人', |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | field: 'contactMobile', |
| | | title: 'èç³»çµè¯', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'contactEmail', |
| | | title: 'é®ç®±', |
| | | minWidth: 150, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useSelectGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useSelectGridColumns(true), |
| | | height: 520, |
| | | keepSource: true, |
| | | checkboxConfig: { |
| | | highlight: true, |
| | | range: true, |
| | | reserve: true, |
| | | }, |
| | | radioConfig: { |
| | | highlight: true, |
| | | trigger: 'row', |
| | | }, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | const params: Record<string, unknown> = { |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | status: CommonStatusEnum.ENABLE, |
| | | }; |
| | | if (approvalFilter.value) { |
| | | params.approved = true; |
| | | } |
| | | return await getSupplierPage(params); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<SrmSupplierApi.SupplierVO>, |
| | | gridEvents: { |
| | | cellDblclick: handleCellDblclick, |
| | | checkboxAll: handleCheckboxSelectChange, |
| | | checkboxChange: handleCheckboxSelectChange, |
| | | radioChange: ({ row }: { row: SrmSupplierApi.SupplierVO }) => { |
| | | handleRadioChange(row); |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | async function resetQueryState() { |
| | | selectedRows.value = []; |
| | | await gridApi.grid.clearCheckboxRow(); |
| | | await gridApi.grid.clearCheckboxReserve(); |
| | | await gridApi.grid.clearRadioRow(); |
| | | await gridApi.formApi.resetForm(); |
| | | } |
| | | |
| | | async function openModal( |
| | | selectedIds?: number[], |
| | | options?: { multiple?: boolean; approvalFilter?: boolean }, |
| | | ) { |
| | | open.value = true; |
| | | multiple.value = options?.multiple ?? true; |
| | | approvalFilter.value = options?.approvalFilter ?? true; |
| | | preSelectedIds.value = selectedIds || []; |
| | | await nextTick(); |
| | | gridApi.setGridOptions({ |
| | | columns: useSelectGridColumns(multiple.value), |
| | | }); |
| | | await resetQueryState(); |
| | | await gridApi.query(); |
| | | await nextTick(); |
| | | await applyPreSelection(); |
| | | } |
| | | |
| | | async function closeModal() { |
| | | open.value = false; |
| | | await resetQueryState(); |
| | | } |
| | | |
| | | function handleConfirm() { |
| | | const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value; |
| | | if (rows.length === 0) { |
| | | message.warning(multiple.value ? '请è³å°éæ©ä¸æ¡æ°æ®' : 'è¯·éæ©ä¸æ¡æ°æ®'); |
| | | return; |
| | | } |
| | | emit('selected', multiple.value ? rows : [rows[0]!]); |
| | | open.value = false; |
| | | } |
| | | |
| | | defineExpose({ open: openModal }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal |
| | | v-model:open="open" |
| | | title="SRM ä¾åºåéæ©" |
| | | width="70%" |
| | | :destroy-on-close="true" |
| | | @ok="handleConfirm" |
| | | @cancel="closeModal" |
| | | > |
| | | <Grid table-title="ä¾åºåå表" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | |
| | | import { computed, ref, useAttrs } from 'vue'; |
| | | |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { Input, Tooltip } from 'ant-design-vue'; |
| | | |
| | | import SrmSupplierSelectDialog from './select-dialog.vue'; |
| | | |
| | | defineOptions({ name: 'SrmSupplierSelect', inheritAttrs: false }); |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | allowClear?: boolean; |
| | | approvalFilter?: boolean; |
| | | disabled?: boolean; |
| | | modelValue?: number; |
| | | placeholder?: string; |
| | | supplierName?: string; |
| | | }>(), |
| | | { |
| | | allowClear: true, |
| | | approvalFilter: true, |
| | | disabled: false, |
| | | modelValue: undefined, |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | supplierName: undefined, |
| | | }, |
| | | ); |
| | | const emit = defineEmits<{ |
| | | change: [item: SrmSupplierApi.SupplierVO | undefined]; |
| | | 'update:modelValue': [value: number | undefined]; |
| | | }>(); |
| | | const attrs = useAttrs(); |
| | | const dialogRef = ref<InstanceType<typeof SrmSupplierSelectDialog>>(); |
| | | const hovering = ref(false); |
| | | const selectedItem = ref<SrmSupplierApi.SupplierVO>(); |
| | | |
| | | const displayLabel = computed(() => props.supplierName || selectedItem.value?.name || ''); |
| | | const showClear = computed( |
| | | () => |
| | | props.allowClear && |
| | | !props.disabled && |
| | | hovering.value && |
| | | props.modelValue !== null, |
| | | ); |
| | | |
| | | function clearSelected() { |
| | | selectedItem.value = undefined; |
| | | emit('update:modelValue', undefined); |
| | | emit('change', undefined); |
| | | } |
| | | |
| | | function handleClick(event: MouseEvent) { |
| | | if (props.disabled) { |
| | | return; |
| | | } |
| | | const target = event.target as HTMLElement; |
| | | if (showClear.value && target.closest('.ant-input-suffix')) { |
| | | event.stopPropagation(); |
| | | clearSelected(); |
| | | return; |
| | | } |
| | | const selectedIds = ( |
| | | props.modelValue === null ? [] : [props.modelValue] |
| | | ) as number[]; |
| | | dialogRef.value?.open(selectedIds, { multiple: false, approvalFilter: props.approvalFilter }); |
| | | } |
| | | |
| | | function handleSelected(rows: SrmSupplierApi.SupplierVO[]) { |
| | | const item = rows[0]; |
| | | if (!item) { |
| | | return; |
| | | } |
| | | selectedItem.value = item; |
| | | emit('update:modelValue', item.id); |
| | | emit('change', item); |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <div |
| | | v-bind="attrs" |
| | | class="w-full" |
| | | :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'" |
| | | @click="handleClick" |
| | | @mouseenter="hovering = true" |
| | | @mouseleave="hovering = false" |
| | | > |
| | | <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false"> |
| | | <template #title> |
| | | <div v-if="selectedItem" class="leading-6"> |
| | | <div>ç¼ç ï¼{{ selectedItem.code || '-' }}</div> |
| | | <div>åç§°ï¼{{ selectedItem.name || '-' }}</div> |
| | | <div>è系人ï¼{{ selectedItem.contactName || '-' }}</div> |
| | | <div>çµè¯ï¼{{ selectedItem.contactMobile || '-' }}</div> |
| | | </div> |
| | | </template> |
| | | <Input |
| | | :disabled="disabled" |
| | | :placeholder="placeholder" |
| | | :value="displayLabel" |
| | | readonly |
| | | > |
| | | <template #suffix> |
| | | <IconifyIcon |
| | | class="size-4" |
| | | :icon="showClear ? 'lucide:circle-x' : 'lucide:search'" |
| | | /> |
| | | </template> |
| | | </Input> |
| | | </Tooltip> |
| | | </div> |
| | | <SrmSupplierSelectDialog ref="dialogRef" @selected="handleSelected" /> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | |
| | | import { |
| | | SUPPLIER_STATUS_OPTIONS, |
| | | SUPPLIER_STATUS_CELLTAG, |
| | | SUPPLIER_TYPE_OPTIONS, |
| | | SUPPLIER_TYPE_CELLTAG, |
| | | } from '../enums'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ä¾åºåç¼ç ', |
| | | component: 'Input', |
| | | rules: 'required', |
| | | componentProps: { placeholder: '请è¾å
¥ä¾åºåç¼ç ' }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ä¾åºååç§°', |
| | | component: 'Input', |
| | | rules: 'required', |
| | | componentProps: { placeholder: '请è¾å
¥ä¾åºååç§°' }, |
| | | }, |
| | | { |
| | | fieldName: 'type', |
| | | label: 'ä¾åºåç±»å', |
| | | component: 'Select', |
| | | rules: 'required', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ä¾åºåç±»å', |
| | | options: SUPPLIER_TYPE_OPTIONS, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | options: SUPPLIER_STATUS_OPTIONS, |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | }, |
| | | rules: z.number().default(1), |
| | | }, |
| | | { |
| | | fieldName: 'contactName', |
| | | label: 'è系人', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥è系人' }, |
| | | }, |
| | | { |
| | | fieldName: 'contactMobile', |
| | | label: 'èç³»çµè¯', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥èç³»çµè¯' }, |
| | | }, |
| | | { |
| | | fieldName: 'contactEmail', |
| | | label: 'é®ç®±', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥é®ç®±' }, |
| | | }, |
| | | { |
| | | fieldName: 'address', |
| | | label: 'å°å', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥å°å' }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'bankName', |
| | | label: '弿·é¶è¡', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥å¼æ·é¶è¡' }, |
| | | }, |
| | | { |
| | | fieldName: 'bankAccount', |
| | | label: 'é¶è¡è´¦å·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥é¶è¡è´¦å·' }, |
| | | }, |
| | | { |
| | | fieldName: 'taxNo', |
| | | label: 'ç¨å·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ç¨å·' }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥å¤æ³¨', rows: 3 }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** æç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ç¼ç ', allowClear: true }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ä¾åºååç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ä¾åºååç§°', allowClear: true }, |
| | | }, |
| | | { |
| | | fieldName: 'type', |
| | | label: 'ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç±»å', |
| | | allowClear: true, |
| | | options: SUPPLIER_TYPE_OPTIONS, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | | options: SUPPLIER_STATUS_OPTIONS, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** è¡¨æ ¼å */ |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'code', title: 'ç¼ç ', minWidth: 120 }, |
| | | { field: 'name', title: 'ä¾åºååç§°', minWidth: 150 }, |
| | | { |
| | | field: 'type', |
| | | title: 'ç±»å', |
| | | minWidth: 100, |
| | | cellRender: { name: 'CellTag', props: SUPPLIER_TYPE_CELLTAG }, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | minWidth: 80, |
| | | cellRender: { name: 'CellTag', props: SUPPLIER_STATUS_CELLTAG }, |
| | | }, |
| | | { field: 'contactName', title: 'è系人', minWidth: 100 }, |
| | | { field: 'contactMobile', title: 'èç³»çµè¯', minWidth: 120 }, |
| | | { field: 'contactEmail', title: 'é®ç®±', minWidth: 150 }, |
| | | { field: 'createTime', title: 'å建æ¶é´', minWidth: 160 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 130, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteSupplier, |
| | | getSupplierPage, |
| | | } from '#/api/srm/supplier'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import SupplierForm from './modules/form.vue'; |
| | | |
| | | defineOptions({ name: 'SrmSupplier' }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | function handleEdit(row: SrmSupplierApi.SupplierVO) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | async function handleDelete(row: SrmSupplierApi.SupplierVO) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteSupplier(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.name])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: SupplierForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getSupplierPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<SrmSupplierApi.SupplierVO>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="ä¾åºåå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ä¾åºå']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['srm:supplier:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['srm:supplier:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['srm:supplier:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createSupplier, |
| | | getSupplier, |
| | | updateSupplier, |
| | | } from '#/api/srm/supplier'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<SrmSupplierApi.SupplierVO>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['ä¾åºå']) |
| | | : $t('ui.actionTitle.create', ['ä¾åºå']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as SrmSupplierApi.SupplierVO; |
| | | try { |
| | | await (formData.value?.id ? updateSupplier(data) : createSupplier(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<SrmSupplierApi.SupplierVO>(); |
| | | if (!data || !data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getSupplier(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | defineExpose({ modalApi }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-1/2"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | |
| | | import { markRaw } from 'vue'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | |
| | | import { SrmSupplierSelect } from '#/views/srm/supplier/components'; |
| | | |
| | | import { APPLY_STATUS_CELLTAG, APPLY_STATUS_OPTIONS } from '../enums'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'supplierId', |
| | | label: 'éæ©ä¾åºå', |
| | | component: markRaw(SrmSupplierSelect), |
| | | componentProps: { |
| | | approvalFilter: false, |
| | | placeholder: 'è¯·éæ©SRMä¾åºåï¼æç´æ¥å¡«åä¾åºååç§°ï¼', |
| | | onChange: async (supplier?: SrmSupplierApi.SupplierVO) => { |
| | | if (!supplier || !formApi) return; |
| | | await formApi.setValues({ |
| | | supplierName: supplier.name || '', |
| | | contactName: supplier.contactName || '', |
| | | contactMobile: supplier.contactMobile || '', |
| | | contactEmail: supplier.contactEmail || '', |
| | | taxNo: supplier.taxNo || '', |
| | | address: supplier.address || '', |
| | | }); |
| | | }, |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['supplierName'], |
| | | componentProps: (values) => ({ |
| | | approvalFilter: false, |
| | | placeholder: 'è¯·éæ©SRMä¾åºåï¼æç´æ¥å¡«åä¾åºååç§°ï¼', |
| | | supplierName: values.supplierName as string, |
| | | }), |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'supplierName', |
| | | label: 'ä¾åºååç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ä¾åºååç§°' }, |
| | | }, |
| | | { |
| | | fieldName: 'contactName', |
| | | label: 'è系人', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥è系人' }, |
| | | }, |
| | | { |
| | | fieldName: 'contactMobile', |
| | | label: 'èç³»çµè¯', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥èç³»çµè¯' }, |
| | | }, |
| | | { |
| | | fieldName: 'contactEmail', |
| | | label: 'é®ç®±', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥é®ç®±' }, |
| | | }, |
| | | { |
| | | fieldName: 'address', |
| | | label: 'å°å', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥å°å' }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'bankName', |
| | | label: '弿·é¶è¡', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥å¼æ·é¶è¡' }, |
| | | }, |
| | | { |
| | | fieldName: 'bankAccount', |
| | | label: 'é¶è¡è´¦å·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥é¶è¡è´¦å·' }, |
| | | }, |
| | | { |
| | | fieldName: 'taxNo', |
| | | label: 'ç¨å·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ç¨å·' }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥å¤æ³¨', rows: 3 }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** æç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'applyNo', |
| | | label: 'ç³è¯·ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ç³è¯·ç¼å·', allowClear: true }, |
| | | }, |
| | | { |
| | | fieldName: 'supplierName', |
| | | label: 'ä¾åºååç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ä¾åºååç§°', allowClear: true }, |
| | | }, |
| | | { |
| | | fieldName: 'applyStatus', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | | options: APPLY_STATUS_OPTIONS, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** è¡¨æ ¼å */ |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'applyNo', title: 'ç³è¯·ç¼å·', minWidth: 140 }, |
| | | { field: 'supplierName', title: 'ä¾åºååç§°', minWidth: 150 }, |
| | | { |
| | | field: 'applyStatus', |
| | | title: 'ç¶æ', |
| | | minWidth: 100, |
| | | cellRender: { name: 'CellTag', props: APPLY_STATUS_CELLTAG }, |
| | | }, |
| | | { field: 'contactName', title: 'è系人', minWidth: 100 }, |
| | | { field: 'contactMobile', title: 'èç³»çµè¯', minWidth: 120 }, |
| | | { field: 'createTime', title: 'å建æ¶é´', minWidth: 160 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmSupplierApplyApi } from '#/api/srm/supplierApply'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteApply, |
| | | getApplyPage, |
| | | submitApply, |
| | | reviewPurchase, |
| | | reviewQuality, |
| | | reviewFinance, |
| | | } from '#/api/srm/supplierApply'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import ApplyForm from './modules/form.vue'; |
| | | import { APPLY_STATUS } from '../enums'; |
| | | |
| | | defineOptions({ name: 'SrmSupplierApply' }); |
| | | |
| | | // ---------- reviewModal ---------- |
| | | const reviewFormData = ref({ |
| | | applyId: 0, |
| | | reviewType: '' as 'purchase' | 'quality' | 'finance', |
| | | passed: true, |
| | | comment: '', |
| | | }); |
| | | const reviewTitle = computed(() => { |
| | | const labels: Record<string, string> = { |
| | | purchase: 'éè´å®¡æ ¸', |
| | | quality: 'è´¨éå®¡æ ¸', |
| | | finance: 'è´¢å¡å®¡æ ¸', |
| | | }; |
| | | return labels[reviewFormData.value.reviewType] || 'å®¡æ ¸'; |
| | | }); |
| | | |
| | | const [ReviewForm, reviewFormApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 100, |
| | | }, |
| | | wrapperClass: 'grid-cols-1', |
| | | layout: 'horizontal', |
| | | schema: [ |
| | | { |
| | | fieldName: 'passed', |
| | | label: 'å®¡æ ¸ç»æ', |
| | | component: 'RadioGroup', |
| | | rules: 'required', |
| | | componentProps: { |
| | | options: [ |
| | | { label: 'éè¿', value: true }, |
| | | { label: 'æç»', value: false }, |
| | | ], |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'comment', |
| | | label: 'å®¡æ ¸æè§', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥å®¡æ ¸æè§', rows: 3 }, |
| | | }, |
| | | ], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [ReviewModal, reviewModalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await reviewFormApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | reviewModalApi.lock(); |
| | | const data = (await reviewFormApi.getValues()) as { passed: boolean; comment?: string }; |
| | | try { |
| | | const payload: SrmSupplierApplyApi.ReviewVO = { |
| | | id: reviewFormData.value.applyId, |
| | | passed: data.passed, |
| | | comment: data.comment, |
| | | }; |
| | | if (reviewFormData.value.reviewType === 'purchase') { |
| | | await reviewPurchase(payload); |
| | | } else if (reviewFormData.value.reviewType === 'quality') { |
| | | await reviewQuality(payload); |
| | | } else if (reviewFormData.value.reviewType === 'finance') { |
| | | await reviewFinance(payload); |
| | | } |
| | | await reviewModalApi.close(); |
| | | handleRefresh(); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | reviewModalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | function openReviewModal(row: SrmSupplierApplyApi.ApplyVO, reviewType: 'purchase' | 'quality' | 'finance') { |
| | | reviewFormData.value = { |
| | | applyId: row.id!, |
| | | reviewType, |
| | | passed: true, |
| | | comment: '', |
| | | }; |
| | | reviewFormApi.resetForm(); |
| | | reviewModalApi.open(); |
| | | } |
| | | |
| | | // ---------- grid ---------- |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | function handleEdit(row: SrmSupplierApplyApi.ApplyVO) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | async function handleDelete(row: SrmSupplierApplyApi.ApplyVO) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.supplierName]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteApply(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.supplierName])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | async function handleSubmit(row: SrmSupplierApplyApi.ApplyVO) { |
| | | const hideLoading = message.loading({ content: 'æäº¤ä¸...', duration: 0 }); |
| | | try { |
| | | await submitApply(row.id!); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: ApplyForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getApplyPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<SrmSupplierApplyApi.ApplyVO>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ReviewModal :title="reviewTitle" class="w-1/3"> |
| | | <ReviewForm class="mx-4" /> |
| | | </ReviewModal> |
| | | <Grid table-title="åå
¥ç³è¯·å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['åå
¥ç³è¯·']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['srm:supplier-apply:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['srm:supplier-apply:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'æäº¤', |
| | | type: 'link', |
| | | icon: ACTION_ICON.CHECK, |
| | | auth: ['srm:supplier-apply:update'], |
| | | ifShow: row.applyStatus === APPLY_STATUS.DRAFT, |
| | | onClick: handleSubmit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'éè´å®¡æ ¸', |
| | | type: 'link', |
| | | icon: ACTION_ICON.CHECK, |
| | | auth: ['srm:supplier-apply:update'], |
| | | ifShow: row.applyStatus === APPLY_STATUS.PENDING_PURCHASE, |
| | | onClick: openReviewModal.bind(null, row, 'purchase'), |
| | | }, |
| | | { |
| | | label: 'è´¨éå®¡æ ¸', |
| | | type: 'link', |
| | | icon: ACTION_ICON.CHECK, |
| | | auth: ['srm:supplier-apply:update'], |
| | | ifShow: row.applyStatus === APPLY_STATUS.PENDING_QUALITY, |
| | | onClick: openReviewModal.bind(null, row, 'quality'), |
| | | }, |
| | | { |
| | | label: 'è´¢å¡å®¡æ ¸', |
| | | type: 'link', |
| | | icon: ACTION_ICON.CHECK, |
| | | auth: ['srm:supplier-apply:update'], |
| | | ifShow: row.applyStatus === APPLY_STATUS.PENDING_FINANCE, |
| | | onClick: openReviewModal.bind(null, row, 'finance'), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['srm:supplier-apply:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.supplierName]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { SrmSupplierApplyApi } from '#/api/srm/supplierApply'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createApply, |
| | | getApply, |
| | | updateApply, |
| | | } from '#/api/srm/supplierApply'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<SrmSupplierApplyApi.ApplyVO>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['åå
¥ç³è¯·']) |
| | | : $t('ui.actionTitle.create', ['åå
¥ç³è¯·']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | // 注å
¥ formApi ä»¥æ¯æéæ© SRM ä¾åºååèªå¨å¡«å
表ååæ®µ |
| | | formApi.setState({ schema: useFormSchema(formApi) }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as SrmSupplierApplyApi.ApplyVO; |
| | | try { |
| | | await (formData.value?.id ? updateApply(data) : createApply(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<SrmSupplierApplyApi.ApplyVO>(); |
| | | if (!data || !data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getApply(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | defineExpose({ modalApi }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-1/2"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | |
| | | import { CERTIFICATE_TYPE_CELLTAG, CERTIFICATE_TYPE_OPTIONS } from '../enums'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'supplierId', |
| | | label: 'ä¾åºå', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getSupplierSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'certificateName', |
| | | label: 'èµè´¨è¯ä¹¦åç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥èµè´¨è¯ä¹¦åç§°' }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'certificateNo', |
| | | label: 'è¯ä¹¦ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥è¯ä¹¦ç¼å·' }, |
| | | }, |
| | | { |
| | | fieldName: 'certificateType', |
| | | label: 'è¯ä¹¦ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è¯ä¹¦ç±»å', |
| | | options: CERTIFICATE_TYPE_OPTIONS, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'issuingAuthority', |
| | | label: 'é¢åæºæ', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥é¢åæºæ' }, |
| | | }, |
| | | { |
| | | fieldName: 'issueDate', |
| | | label: 'é¢åæ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©é¢åæ¥æ', |
| | | allowClear: true, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'expireDate', |
| | | label: 'å°ææ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©å°ææ¥æ', |
| | | allowClear: true, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥å¤æ³¨', rows: 3 }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** æç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'certificateName', |
| | | label: 'èµè´¨è¯ä¹¦åç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥èµè´¨è¯ä¹¦åç§°', allowClear: true }, |
| | | }, |
| | | { |
| | | fieldName: 'certificateType', |
| | | label: 'è¯ä¹¦ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è¯ä¹¦ç±»å', |
| | | allowClear: true, |
| | | options: CERTIFICATE_TYPE_OPTIONS, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'supplierId', |
| | | label: 'ä¾åºå', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getSupplierSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** è¡¨æ ¼å */ |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'supplierName', title: 'ä¾åºå', minWidth: 120 }, |
| | | { field: 'certificateName', title: 'èµè´¨è¯ä¹¦åç§°', minWidth: 150 }, |
| | | { field: 'certificateNo', title: 'è¯ä¹¦ç¼å·', minWidth: 120 }, |
| | | { |
| | | field: 'certificateType', |
| | | title: 'è¯ä¹¦ç±»å', |
| | | minWidth: 120, |
| | | cellRender: { name: 'CellTag', props: CERTIFICATE_TYPE_CELLTAG }, |
| | | }, |
| | | { field: 'issuingAuthority', title: 'é¢åæºæ', minWidth: 120 }, |
| | | { field: 'issueDate', title: 'é¢åæ¥æ', minWidth: 120 }, |
| | | { field: 'expireDate', title: 'å°ææ¥æ', minWidth: 120 }, |
| | | { field: 'createTime', title: 'å建æ¶é´', minWidth: 160 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 130, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmSupplierCertificateApi } from '#/api/srm/supplierCertificate'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteCertificate, |
| | | getCertificatePage, |
| | | getExpiringCertificates, |
| | | } from '#/api/srm/supplierCertificate'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import CertificateForm from './modules/form.vue'; |
| | | |
| | | defineOptions({ name: 'SrmSupplierCertificate' }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | function handleEdit(row: SrmSupplierCertificateApi.CertificateVO) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | async function handleDelete(row: SrmSupplierCertificateApi.CertificateVO) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.certificateName]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteCertificate(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.certificateName])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | async function handleExpiringCertificates() { |
| | | try { |
| | | const data = await getExpiringCertificates(); |
| | | if (data && data.length > 0) { |
| | | message.warning(`æ ${data.length} 个èµè´¨è¯ä¹¦å³å°å°æ`); |
| | | } else { |
| | | message.success('没æå³å°å°æçèµè´¨è¯ä¹¦'); |
| | | } |
| | | } catch { |
| | | message.error('è·åå³å°å°æçèµè´¨è¯ä¹¦å¤±è´¥'); |
| | | } |
| | | } |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: CertificateForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getCertificatePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<SrmSupplierCertificateApi.CertificateVO>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="èµè´¨è¯ä¹¦å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['èµè´¨è¯ä¹¦']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['srm:supplier-certificate:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: 'å°ææé', |
| | | type: 'warning', |
| | | icon: 'ant-design:bell-outlined', |
| | | auth: ['srm:supplier-certificate:query'], |
| | | onClick: handleExpiringCertificates, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['srm:supplier-certificate:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['srm:supplier-certificate:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.certificateName]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { SrmSupplierCertificateApi } from '#/api/srm/supplierCertificate'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createCertificate, |
| | | getCertificate, |
| | | updateCertificate, |
| | | } from '#/api/srm/supplierCertificate'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<SrmSupplierCertificateApi.CertificateVO>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['èµè´¨è¯ä¹¦']) |
| | | : $t('ui.actionTitle.create', ['èµè´¨è¯ä¹¦']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as SrmSupplierCertificateApi.CertificateVO; |
| | | try { |
| | | await (formData.value?.id ? updateCertificate(data) : createCertificate(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<SrmSupplierCertificateApi.CertificateVO>(); |
| | | if (!data || !data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getCertificate(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | defineExpose({ modalApi }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-1/2"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | |
| | | import { COMPOSITE_SCORE_CELLTAG, EVALUATION_GRADE_CELLTAG } from '../enums'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'supplierId', |
| | | label: 'ä¾åºå', |
| | | component: 'ApiSelect', |
| | | rules: 'required', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getSupplierSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'evaluationPeriod', |
| | | label: 'è¯ä»·å¨æ', |
| | | component: 'Input', |
| | | rules: 'required', |
| | | componentProps: { placeholder: 'å¦ 2024-Q1' }, |
| | | }, |
| | | { |
| | | fieldName: 'qualityScore', |
| | | label: 'è´¨éè¯å', |
| | | component: 'InputNumber', |
| | | rules: 'required', |
| | | componentProps: { min: 0, max: 100, placeholder: '请è¾å
¥è´¨éè¯å' }, |
| | | }, |
| | | { |
| | | fieldName: 'deliveryScore', |
| | | label: '交ä»è¯å', |
| | | component: 'InputNumber', |
| | | rules: 'required', |
| | | componentProps: { min: 0, max: 100, placeholder: '请è¾å
¥äº¤ä»è¯å' }, |
| | | }, |
| | | { |
| | | fieldName: 'priceScore', |
| | | label: 'ä»·æ ¼è¯å', |
| | | component: 'InputNumber', |
| | | rules: 'required', |
| | | componentProps: { min: 0, max: 100, placeholder: '请è¾å
¥ä»·æ ¼è¯å' }, |
| | | }, |
| | | { |
| | | fieldName: 'serviceScore', |
| | | label: 'æå¡è¯å', |
| | | component: 'InputNumber', |
| | | rules: 'required', |
| | | componentProps: { min: 0, max: 100, placeholder: '请è¾å
¥æå¡è¯å' }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: 'è¯ä»·è¯´æ', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥è¯ä»·è¯´æ', rows: 3 }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** æç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'supplierId', |
| | | label: 'ä¾åºå', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ä¾åºå', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | api: getSupplierSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'evaluationPeriod', |
| | | label: 'è¯ä»·å¨æ', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥è¯ä»·å¨æ', allowClear: true }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** è¡¨æ ¼å */ |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'supplierName', title: 'ä¾åºå', minWidth: 150 }, |
| | | { field: 'evaluationPeriod', title: 'è¯ä»·å¨æ', minWidth: 120 }, |
| | | { field: 'qualityScore', title: 'è´¨éè¯å', minWidth: 100 }, |
| | | { field: 'deliveryScore', title: '交ä»è¯å', minWidth: 100 }, |
| | | { field: 'priceScore', title: 'ä»·æ ¼è¯å', minWidth: 100 }, |
| | | { field: 'serviceScore', title: 'æå¡è¯å', minWidth: 100 }, |
| | | { |
| | | field: 'compositeScore', |
| | | title: '综åè¯å', |
| | | minWidth: 120, |
| | | cellRender: { name: 'CellTag', props: COMPOSITE_SCORE_CELLTAG }, |
| | | }, |
| | | { |
| | | field: 'grade', |
| | | title: 'ç级', |
| | | minWidth: 80, |
| | | cellRender: { name: 'CellTag', props: EVALUATION_GRADE_CELLTAG }, |
| | | }, |
| | | { field: 'createTime', title: 'å建æ¶é´', minWidth: 160 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 130, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmSupplierEvaluationApi } from '#/api/srm/supplierEvaluation'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteEvaluation, |
| | | getEvaluationPage, |
| | | } from '#/api/srm/supplierEvaluation'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import EvaluationForm from './modules/form.vue'; |
| | | |
| | | defineOptions({ name: 'SrmSupplierEvaluation' }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | function handleEdit(row: SrmSupplierEvaluationApi.EvaluationVO) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | async function handleDelete(row: SrmSupplierEvaluationApi.EvaluationVO) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.evaluationPeriod]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteEvaluation(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.evaluationPeriod])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: EvaluationForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getEvaluationPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<SrmSupplierEvaluationApi.EvaluationVO>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="绩æè¯ä»·å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['绩æè¯ä»·']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['srm:supplier-evaluation:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['srm:supplier-evaluation:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['srm:supplier-evaluation:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.evaluationPeriod]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { SrmSupplierEvaluationApi } from '#/api/srm/supplierEvaluation'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createEvaluation, |
| | | getEvaluation, |
| | | updateEvaluation, |
| | | } from '#/api/srm/supplierEvaluation'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<SrmSupplierEvaluationApi.EvaluationVO>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['绩æè¯ä»·']) |
| | | : $t('ui.actionTitle.create', ['绩æè¯ä»·']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as SrmSupplierEvaluationApi.EvaluationVO; |
| | | try { |
| | | await (formData.value?.id ? updateEvaluation(data) : createEvaluation(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<SrmSupplierEvaluationApi.EvaluationVO>(); |
| | | if (!data || !data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getEvaluation(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | defineExpose({ modalApi }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-1/2"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { |
| | | TENDER_STATUS_CELLTAG, |
| | | TENDER_STATUS_OPTIONS, |
| | | TENDER_TYPE_CELLTAG, |
| | | TENDER_TYPE_OPTIONS, |
| | | } from '../enums'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'tenderNo', |
| | | label: 'ææ ç¼å·', |
| | | component: 'Input', |
| | | rules: 'required', |
| | | componentProps: { placeholder: '请è¾å
¥ææ ç¼å·' }, |
| | | }, |
| | | { |
| | | fieldName: 'tenderName', |
| | | label: 'ææ é¡¹ç®åç§°', |
| | | component: 'Input', |
| | | rules: 'required', |
| | | componentProps: { placeholder: '请è¾å
¥ææ 项ç®åç§°' }, |
| | | }, |
| | | { |
| | | fieldName: 'tenderType', |
| | | label: 'ææ ç±»å', |
| | | component: 'Select', |
| | | rules: 'required', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ææ ç±»å', |
| | | options: TENDER_TYPE_OPTIONS, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'purchaseOrg', |
| | | label: 'éè´ç»ç»', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥éè´ç»ç»' }, |
| | | }, |
| | | { |
| | | fieldName: 'projectLeaderName', |
| | | label: '项ç®è´è´£äºº', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥é¡¹ç®è´è´£äºº' }, |
| | | }, |
| | | { |
| | | fieldName: 'budgetAmount', |
| | | label: 'é¢ç®éé¢', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | placeholder: '请è¾å
¥é¢ç®éé¢', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'startTime', |
| | | label: 'å¼å§æ¶é´', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | placeholder: 'è¯·éæ©å¼å§æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'endTime', |
| | | label: 'æªæ¢æ¶é´', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | placeholder: 'è¯·éæ©æªæ¢æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'bidOpenTime', |
| | | label: '弿 æ¶é´', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | placeholder: 'è¯·éæ©å¼æ æ¶é´', |
| | | showTime: true, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'tenderDesc', |
| | | label: 'ææ è¯´æ', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥ææ 说æ', rows: 3 }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'qualificationRequirements', |
| | | label: 'èµè´¨è¦æ±', |
| | | component: 'Textarea', |
| | | componentProps: { placeholder: '请è¾å
¥èµè´¨è¦æ±', rows: 3 }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** æç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'tenderNo', |
| | | label: 'ææ ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ææ ç¼å·', allowClear: true }, |
| | | }, |
| | | { |
| | | fieldName: 'tenderName', |
| | | label: 'ææ é¡¹ç®åç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ææ 项ç®åç§°', allowClear: true }, |
| | | }, |
| | | { |
| | | fieldName: 'tenderType', |
| | | label: 'ææ ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ææ ç±»å', |
| | | allowClear: true, |
| | | options: TENDER_TYPE_OPTIONS, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'tenderStatus', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | | options: TENDER_STATUS_OPTIONS, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** è¡¨æ ¼å */ |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { field: 'tenderNo', title: 'ææ ç¼å·', minWidth: 140 }, |
| | | { field: 'tenderName', title: 'ææ é¡¹ç®åç§°', minWidth: 180 }, |
| | | { |
| | | field: 'tenderType', |
| | | title: 'ææ ç±»å', |
| | | minWidth: 120, |
| | | cellRender: { name: 'CellTag', props: TENDER_TYPE_CELLTAG }, |
| | | }, |
| | | { |
| | | field: 'tenderStatus', |
| | | title: 'ç¶æ', |
| | | minWidth: 100, |
| | | cellRender: { name: 'CellTag', props: TENDER_STATUS_CELLTAG }, |
| | | }, |
| | | { field: 'budgetAmount', title: 'é¢ç®éé¢', minWidth: 120 }, |
| | | { field: 'purchaseOrg', title: 'éè´ç»ç»', minWidth: 100 }, |
| | | { field: 'startTime', title: 'å¼å§æ¶é´', minWidth: 160 }, |
| | | { field: 'endTime', title: 'æªæ¢æ¶é´', minWidth: 160 }, |
| | | { field: 'purchaseOrderNo', title: 'éè´è®¢åå·', minWidth: 140 }, |
| | | { |
| | | title: 'æä½', |
| | | width: 250, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { SrmTenderApi } from '#/api/srm/tender'; |
| | | |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | import { useRoute, useRouter } from 'vue-router'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { |
| | | getTenderProject, |
| | | getMaterialList, createMaterial, updateMaterial, deleteMaterial, |
| | | getBidListByProject, createBid, withdrawBid, |
| | | getQuoteList, createQuote, updateQuote, |
| | | createBidOpen, getBidOpen, |
| | | getEvaluationList, createBidEvaluation, calculateRanking, |
| | | createAward, getAward, approveAward, generatePurchaseOrder, |
| | | } from '#/api/srm/tender'; |
| | | import { getSupplierSimpleList } from '#/api/srm/supplier'; |
| | | import { getItemSimpleList } from '#/api/mdm/item'; |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | import { |
| | | TENDER_STATUS, |
| | | TENDER_STATUS_CELLTAG, |
| | | TENDER_STATUS_COLORS, |
| | | TENDER_TYPE_CELLTAG, |
| | | BID_STATUS, |
| | | BID_STATUS_MAP, |
| | | AWARD_STATUS, |
| | | AWARD_STATUS_MAP, |
| | | getLabel, |
| | | } from '../enums'; |
| | | |
| | | defineOptions({ name: 'SrmTenderDetail' }); |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const projectId = Number(route.query.id); |
| | | |
| | | const project = ref<SrmTenderApi.TenderProjectVO>(); |
| | | const activeTab = ref('info'); |
| | | |
| | | // ========== 䏿éé¡¹æ°æ® ========== |
| | | const mdmItemList = ref<MdmItemApi.Item[]>([]); |
| | | const supplierList = ref<{ id: number; name: string }[]>([]); |
| | | |
| | | onMounted(async () => { |
| | | const [items, suppliers] = await Promise.all([ |
| | | getItemSimpleList(), |
| | | getSupplierSimpleList(), |
| | | ]); |
| | | mdmItemList.value = items; |
| | | supplierList.value = suppliers; |
| | | }); |
| | | |
| | | const filterOption = (input: string, option: any) => |
| | | option.label?.toLowerCase().indexOf(input.toLowerCase()) >= 0; |
| | | |
| | | // ========== åºæ¬ä¿¡æ¯ ========== |
| | | const materialList = ref<SrmTenderApi.TenderMaterialVO[]>([]); |
| | | const materialLoading = ref(false); |
| | | const materialFormVisible = ref(false); |
| | | const materialForm = ref<SrmTenderApi.TenderMaterialVO>({ tenderProjectId: projectId }); |
| | | |
| | | async function loadProject() { |
| | | project.value = await getTenderProject(projectId); |
| | | } |
| | | async function loadMaterials() { |
| | | materialLoading.value = true; |
| | | try { materialList.value = await getMaterialList(projectId); } finally { materialLoading.value = false; } |
| | | } |
| | | async function handleSaveMaterial() { |
| | | if (materialForm.value.id) { |
| | | await updateMaterial(materialForm.value); |
| | | } else { |
| | | await createMaterial(materialForm.value); |
| | | } |
| | | materialFormVisible.value = false; |
| | | loadMaterials(); |
| | | } |
| | | function onMaterialSelect(itemId: number) { |
| | | const item = mdmItemList.value.find((i) => i.id === itemId); |
| | | if (item) { |
| | | materialForm.value.productCode = item.code; |
| | | materialForm.value.productName = item.name; |
| | | materialForm.value.productSpec = item.specification; |
| | | materialForm.value.unit = item.unitMeasureName; |
| | | } |
| | | } |
| | | |
| | | function handleAddMaterial() { |
| | | materialForm.value = { tenderProjectId: projectId }; |
| | | materialFormVisible.value = true; |
| | | } |
| | | function handleEditMaterial(row: SrmTenderApi.TenderMaterialVO) { |
| | | materialForm.value = { ...row }; |
| | | materialFormVisible.value = true; |
| | | } |
| | | async function handleDeleteMaterial(id: number) { |
| | | await deleteMaterial(id); |
| | | loadMaterials(); |
| | | } |
| | | |
| | | // ========== ææ ç®¡ç ========== |
| | | const bidList = ref<SrmTenderApi.TenderBidVO[]>([]); |
| | | const bidLoading = ref(false); |
| | | const bidFormVisible = ref(false); |
| | | const bidForm = ref<SrmTenderApi.TenderBidVO>({ tenderProjectId: projectId, supplierId: 0, bidNo: '' }); |
| | | |
| | | async function loadBids() { |
| | | bidLoading.value = true; |
| | | try { bidList.value = await getBidListByProject(projectId); } finally { bidLoading.value = false; } |
| | | } |
| | | async function handleCreateBid() { |
| | | await createBid(bidForm.value); |
| | | bidFormVisible.value = false; |
| | | loadBids(); |
| | | } |
| | | async function handleWithdrawBid(id: number) { |
| | | await withdrawBid(id); |
| | | loadBids(); |
| | | } |
| | | |
| | | // ========== æ¥ä»·ç®¡ç ========== |
| | | const quoteList = ref<SrmTenderApi.QuoteVO[]>([]); |
| | | const quoteLoading = ref(false); |
| | | const quoteFormVisible = ref(false); |
| | | const quoteForm = ref<SrmTenderApi.QuoteVO>({ bidId: 0, tenderMaterialId: 0 }); |
| | | const selectedBidId = ref<number>(); |
| | | |
| | | const availableMaterials = computed(() => { |
| | | const quotedIds = new Set( |
| | | quoteList.value |
| | | .filter((q) => q.tenderMaterialId !== undefined) |
| | | .map((q) => q.tenderMaterialId), |
| | | ); |
| | | // ç¼è¾æ¶å
许ä¿çå½åç©æ |
| | | if (quoteForm.value.id && quoteForm.value.tenderMaterialId) { |
| | | quotedIds.delete(quoteForm.value.tenderMaterialId); |
| | | } |
| | | return materialList.value.filter((m) => !quotedIds.has(m.id)); |
| | | }); |
| | | |
| | | async function loadQuotes(bidId?: number) { |
| | | if (!bidId) return; |
| | | selectedBidId.value = bidId; |
| | | quoteLoading.value = true; |
| | | try { quoteList.value = await getQuoteList(bidId); } finally { quoteLoading.value = false; } |
| | | } |
| | | async function handleSaveQuote() { |
| | | if (quoteForm.value.id) { |
| | | await updateQuote(quoteForm.value); |
| | | } else { |
| | | await createQuote(quoteForm.value); |
| | | } |
| | | quoteFormVisible.value = false; |
| | | loadQuotes(selectedBidId.value); |
| | | } |
| | | function handleAddQuote() { |
| | | quoteForm.value = { bidId: selectedBidId.value!, tenderMaterialId: 0 }; |
| | | quoteFormVisible.value = true; |
| | | } |
| | | function handleEditQuote(row: SrmTenderApi.QuoteVO) { |
| | | quoteForm.value = { ...row }; |
| | | quoteFormVisible.value = true; |
| | | } |
| | | |
| | | // ========== 弿 ========== |
| | | const bidOpen = ref<any>(null); |
| | | |
| | | async function loadBidOpen() { |
| | | try { |
| | | bidOpen.value = null; |
| | | } catch { /* ææ å¼æ è®°å½ */ } |
| | | } |
| | | async function handleCreateBidOpen() { |
| | | await createBidOpen(projectId); |
| | | message.success('弿 æå'); |
| | | loadBidOpen(); |
| | | loadProject(); |
| | | } |
| | | |
| | | // ========== è¯æ 管ç ========== |
| | | const evaluationList = ref<SrmTenderApi.BidEvaluationVO[]>([]); |
| | | const evalLoading = ref(false); |
| | | const evalFormVisible = ref(false); |
| | | const evalForm = ref<SrmTenderApi.BidEvaluationVO>({ tenderProjectId: projectId, supplierId: 0, bidId: 0 }); |
| | | |
| | | async function loadEvaluations() { |
| | | evalLoading.value = true; |
| | | try { evaluationList.value = await getEvaluationList(projectId); } finally { evalLoading.value = false; } |
| | | } |
| | | async function handleCreateEvaluation() { |
| | | await createBidEvaluation(evalForm.value); |
| | | evalFormVisible.value = false; |
| | | loadEvaluations(); |
| | | loadProject(); |
| | | } |
| | | async function handleCalculateRanking() { |
| | | await calculateRanking(projectId); |
| | | message.success('æå计ç®å®æ'); |
| | | loadEvaluations(); |
| | | } |
| | | |
| | | // ========== 宿 ========== |
| | | const award = ref<SrmTenderApi.BidAwardVO>(); |
| | | const awardFormVisible = ref(false); |
| | | const awardForm = ref<SrmTenderApi.BidAwardVO>({ tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' }); |
| | | |
| | | async function loadAward() { |
| | | try { award.value = await getAward(projectId); } catch { award.value = undefined; } |
| | | } |
| | | async function handleCreateAward() { |
| | | await createAward(awardForm.value); |
| | | awardFormVisible.value = false; |
| | | loadAward(); |
| | | loadProject(); |
| | | } |
| | | async function handleApproveAward() { |
| | | if (!award.value?.id) return; |
| | | await approveAward(award.value.id); |
| | | message.success('宿 审æ¹éè¿'); |
| | | loadAward(); |
| | | loadProject(); |
| | | } |
| | | async function handleGeneratePurchaseOrder() { |
| | | if (!award.value?.id) return; |
| | | await generatePurchaseOrder(award.value.id); |
| | | message.success('éè´è®¢åå·²çæ'); |
| | | loadAward(); |
| | | loadProject(); |
| | | } |
| | | |
| | | // ========== åå§å ========== |
| | | loadProject(); |
| | | loadMaterials(); |
| | | loadBids(); |
| | | loadEvaluations(); |
| | | loadAward(); |
| | | |
| | | // ========== ç¶æå¤æ ========== |
| | | const statusLabel = computed(() => getLabel(TENDER_STATUS_CELLTAG.labels, project.value?.tenderStatus)); |
| | | const statusColor = computed(() => TENDER_STATUS_COLORS[project.value?.tenderStatus!] || 'default'); |
| | | const typeLabel = computed(() => getLabel(TENDER_TYPE_CELLTAG.labels, project.value?.tenderType)); |
| | | |
| | | const stepItems = [ |
| | | { 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); |
| | | 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'; |
| | | return 'process'; |
| | | }); |
| | | |
| | | const materialTotalAmount = computed(() => |
| | | materialList.value.reduce((sum, m) => sum + (m.estimatedPrice ?? 0) * (m.quantity ?? 0), 0), |
| | | ); |
| | | const bidCount = computed(() => bidList.value.length); |
| | | const bidTotalAmount = computed(() => |
| | | bidList.value.reduce((sum, b) => sum + (b.bidTotalAmount ?? 0), 0), |
| | | ); |
| | | const evalCount = computed(() => evaluationList.value.length); |
| | | const rankedCount = computed(() => evaluationList.value.filter((e) => e.rank != null).length); |
| | | |
| | | const selectedBid = computed(() => |
| | | bidList.value.find((b) => b.id === selectedBidId.value), |
| | | ); |
| | | |
| | | const materialColumns = [ |
| | | { title: 'ç©æç¼ç ', dataIndex: 'productCode', width: 120 }, |
| | | { title: 'ç©æåç§°', dataIndex: 'productName', width: 150 }, |
| | | { title: 'è§æ ¼åå·', dataIndex: 'productSpec', width: 120 }, |
| | | { title: 'åä½', dataIndex: 'unit', width: 80 }, |
| | | { title: 'æ°é', dataIndex: 'quantity', width: 80 }, |
| | | { title: 'ææ¯è¦æ±', dataIndex: 'techRequirement', width: 150 }, |
| | | { title: 'é¢ä¼°åä»·', dataIndex: 'estimatedPrice', width: 100 }, |
| | | { title: 'æä½', key: 'actions', width: 150 }, |
| | | ]; |
| | | |
| | | const bidColumns = [ |
| | | { title: 'ææ ç¼å·', dataIndex: 'bidNo', width: 140 }, |
| | | { title: 'ä¾åºå', dataIndex: 'supplierName', width: 200 }, |
| | | { 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 }, |
| | | ]; |
| | | |
| | | const quoteColumns = [ |
| | | { title: 'ç©æ', dataIndex: 'materialName', width: 120 }, |
| | | { title: 'æ¥ä»·ä»·æ ¼', dataIndex: 'quotePrice', width: 100 }, |
| | | { title: 'ç¨ç(%)', dataIndex: 'taxRate', width: 80 }, |
| | | { title: '交æ(天)', dataIndex: 'deliveryCycle', width: 80 }, |
| | | { title: '仿¬¾æ¡ä»¶', dataIndex: 'paymentTerms', width: 120 }, |
| | | { title: 'è´¨ä¿æ(æ)', dataIndex: 'warrantyPeriod', width: 80 }, |
| | | { title: '夿³¨', dataIndex: 'remark', width: 150 }, |
| | | { title: 'æä½', key: 'actions', width: 120 }, |
| | | ]; |
| | | |
| | | const evalColumns = [ |
| | | { title: 'ææ ID', dataIndex: 'bidId', width: 80 }, |
| | | { title: 'ä¾åºå', dataIndex: 'supplierName', width: 150 }, |
| | | { title: 'ä»·æ ¼è¯å', dataIndex: 'priceScore', width: 100 }, |
| | | { title: 'ææ¯è¯å', dataIndex: 'techScore', width: 100 }, |
| | | { title: '交ä»è¯å', dataIndex: 'deliveryScore', width: 100 }, |
| | | { title: 'æå¡è¯å', dataIndex: 'serviceScore', width: 100 }, |
| | | { title: '综åè¯å', dataIndex: 'compositeScore', width: 100 }, |
| | | { title: 'æå', dataIndex: 'rank', width: 80 }, |
| | | { title: 'è¯å§', dataIndex: 'evaluatorName', width: 100 }, |
| | | { title: 'è¯è¯', dataIndex: 'evaluationOpinion', width: 150 }, |
| | | { title: 'æä½', key: 'actions', width: 120 }, |
| | | ]; |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <!-- å è½½ä¸ --> |
| | | <a-skeleton v-if="!project" active class="p-4" /> |
| | | <template v-else> |
| | | <!-- 顶鍿 颿 --> |
| | | <div class="flex items-center gap-2 px-4 pt-3 pb-2"> |
| | | <a-button type="text" size="small" @click="router.back()"> |
| | | <template #icon><IconifyIcon icon="ant-design:arrow-left-outlined" /></template> |
| | | </a-button> |
| | | <span class="text-lg font-bold truncate max-w-md">{{ project.tenderName }}</span> |
| | | <span class="text-gray-400 text-xs">{{ project.tenderNo }}</span> |
| | | <a-tag :color="statusColor">{{ statusLabel }}</a-tag> |
| | | <a-tag color="blue">{{ typeLabel }}</a-tag> |
| | | <div class="flex-1" /> |
| | | <a-button size="small" @click="loadProject(); loadMaterials(); loadBids(); loadEvaluations(); loadAward();"> |
| | | <template #icon><IconifyIcon icon="ant-design:reload-outlined" /></template> |
| | | å·æ° |
| | | </a-button> |
| | | </div> |
| | | |
| | | <!-- 工使µè¿åº¦ + å
³é®ä¿¡æ¯ --> |
| | | <div class="px-4 mb-3"> |
| | | <a-card size="small"> |
| | | <!-- æµç¨æ¥éª¤ --> |
| | | <a-steps :current="currentStep" :status="stepStatus" size="small" class="mb-3"> |
| | | <a-step v-for="item in stepItems" :key="item.status" :title="item.title" /> |
| | | </a-steps> |
| | | |
| | | <!-- å
³é®å段 --> |
| | | <div class="grid grid-cols-6 gap-3 text-xs"> |
| | | <div> |
| | | <div class="text-gray-400 mb-0.5">é¢ç®éé¢</div> |
| | | <div class="font-semibold text-blue-500">{{ project.budgetAmount?.toLocaleString() ?? '-' }} å
</div> |
| | | </div> |
| | | <div> |
| | | <div class="text-gray-400 mb-0.5">è´è´£äºº</div> |
| | | <div class="font-semibold">{{ project.projectLeaderName || '-' }}</div> |
| | | </div> |
| | | <div> |
| | | <div class="text-gray-400 mb-0.5">éè´ç»ç»</div> |
| | | <div class="font-semibold">{{ project.purchaseOrg || '-' }}</div> |
| | | </div> |
| | | <div> |
| | | <div class="text-gray-400 mb-0.5">弿 æ¶é´</div> |
| | | <div class="font-semibold">{{ project.bidOpenTime || 'å¾
å®' }}</div> |
| | | </div> |
| | | <div> |
| | | <div class="text-gray-400 mb-0.5">å¼å§æ¶é´</div> |
| | | <div class="font-semibold">{{ project.startTime || '-' }}</div> |
| | | </div> |
| | | <div> |
| | | <div class="text-gray-400 mb-0.5">æªæ¢æ¶é´</div> |
| | | <div class="font-semibold">{{ project.endTime || '-' }}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ææ è¯´æ / èµè´¨è¦æ± --> |
| | | <template v-if="project.tenderDesc || project.qualificationRequirements"> |
| | | <a-divider class="!my-2" /> |
| | | <a-collapse :bordered="false" ghost expand-icon-position="end"> |
| | | <a-collapse-panel v-if="project.tenderDesc" key="desc" header="ææ è¯´æ"> |
| | | <p class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.tenderDesc }}</p> |
| | | </a-collapse-panel> |
| | | <a-collapse-panel v-if="project.qualificationRequirements" key="req" header="èµè´¨è¦æ±"> |
| | | <p class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.qualificationRequirements }}</p> |
| | | </a-collapse-panel> |
| | | </a-collapse> |
| | | </template> |
| | | </a-card> |
| | | </div> |
| | | |
| | | <!-- Tab é¡µç¾ --> |
| | | <div class="px-4 pb-4"> |
| | | <a-card size="small"> |
| | | <a-tabs v-model:activeKey="activeTab" size="small"> |
| | | <!-- Tab1: ç©ææ¸
å --> |
| | | <a-tab-pane key="info"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:unordered-list-outlined" />ç©ææ¸
å</a-space> |
| | | </template> |
| | | <div class="flex items-center justify-between mb-3"> |
| | | <a-space> |
| | | <span class="text-xs text-gray-400">å
± <b class="text-gray-600">{{ materialList.length }}</b> 项ï¼é¢ä¼°æ»éé¢ <b class="text-blue-500">{{ materialTotalAmount.toLocaleString() }}</b> å
</span> |
| | | </a-space> |
| | | <a-button type="primary" size="small" @click="handleAddMaterial"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | æ·»å ç©æ |
| | | </a-button> |
| | | </div> |
| | | <a-table :columns="materialColumns" :data-source="materialList" :loading="materialLoading" :pagination="false" row-key="id" size="small"> |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.dataIndex === 'estimatedPrice'"> |
| | | {{ record.estimatedPrice ? record.estimatedPrice.toLocaleString() : '-' }} |
| | | </template> |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-space> |
| | | <a-button size="small" type="link" @click="handleEditMaterial(record)"><IconifyIcon icon="ant-design:edit-outlined" />ç¼è¾</a-button> |
| | | <a-popconfirm title="确认å é¤è¯¥ç©æï¼" @confirm="handleDeleteMaterial(record.id)"> |
| | | <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:delete-outlined" />å é¤</a-button> |
| | | </a-popconfirm> |
| | | </a-space> |
| | | </template> |
| | | </template> |
| | | </a-table> |
| | | <a-empty v-if="!materialLoading && materialList.length === 0" description="ææ ç©æ" class="mt-8" /> |
| | | </a-tab-pane> |
| | | |
| | | <!-- Tab2: ææ ç®¡ç --> |
| | | <a-tab-pane key="bid"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:team-outlined" />ææ ç®¡ç</a-space> |
| | | </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 type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: 0, bidNo: '' }; bidFormVisible = true"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | æ°å¢ææ |
| | | </a-button> |
| | | </div> |
| | | <a-table :columns="bidColumns" :data-source="bidList" :loading="bidLoading" :pagination="false" row-key="id" size="small"> |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.dataIndex === 'bidTotalAmount'"> |
| | | <span class="font-medium">{{ record.bidTotalAmount?.toLocaleString() }}</span> |
| | | </template> |
| | | <template v-if="column.dataIndex === 'bidStatus'"> |
| | | <a-tag :color="record.bidStatus === BID_STATUS.WIN ? 'green' : record.bidStatus === BID_STATUS.LOSE ? 'red' : 'default'"> |
| | | {{ BID_STATUS_MAP[record.bidStatus] }} |
| | | </a-tag> |
| | | </template> |
| | | <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="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> |
| | | </template> |
| | | </template> |
| | | </a-table> |
| | | <a-empty v-if="!bidLoading && bidList.length === 0" description="ææ ææ è®°å½" class="mt-8" /> |
| | | </a-tab-pane> |
| | | |
| | | <!-- Tab3: æ¥ä»·ç®¡ç --> |
| | | <a-tab-pane key="quote"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:dollar-outlined" />æ¥ä»·ç®¡ç</a-space> |
| | | </template> |
| | | <template v-if="selectedBidId && selectedBid"> |
| | | <div class="flex items-center gap-3 mb-3 px-3 py-2 bg-gray-50 rounded text-xs"> |
| | | <span class="text-gray-400">ææ ï¼<b class="text-gray-700">{{ selectedBid.bidNo }}</b></span> |
| | | <a-divider type="vertical" /> |
| | | <span class="text-gray-400">ä¾åºåï¼<b class="text-gray-700">{{ selectedBid.supplierName }}</b></span> |
| | | <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="selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | æ°å¢æ¥ä»· |
| | | </a-button> |
| | | </div> |
| | | <a-table :columns="quoteColumns" :data-source="quoteList" :loading="quoteLoading" :pagination="false" row-key="id" size="small"> |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.dataIndex === 'quotePrice'"> |
| | | <span class="font-medium text-blue-600">{{ record.quotePrice?.toLocaleString() }}</span> |
| | | </template> |
| | | <template v-if="column.dataIndex === 'taxRate'"> |
| | | {{ record.taxRate != null ? `${record.taxRate}%` : '-' }} |
| | | </template> |
| | | <template v-if="column.dataIndex === 'deliveryCycle'"> |
| | | {{ record.deliveryCycle != null ? `${record.deliveryCycle} 天` : '-' }} |
| | | </template> |
| | | <template v-if="column.dataIndex === 'warrantyPeriod'"> |
| | | {{ record.warrantyPeriod != null ? `${record.warrantyPeriod} 个æ` : '-' }} |
| | | </template> |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-button v-if="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-empty v-if="!quoteLoading && quoteList.length === 0" description="ææ æ¥ä»·æç»" class="mt-8" /> |
| | | </template> |
| | | <a-empty v-else description="请å
å¨ãææ ç®¡çãä¸éæ©ä¸ä¸ªææ æ¥çæ¥ä»·"> |
| | | <a-button type="primary" @click="activeTab = 'bid'">å徿æ 管ç</a-button> |
| | | </a-empty> |
| | | </a-tab-pane> |
| | | |
| | | <!-- Tab4: 弿 --> |
| | | <a-tab-pane key="bidOpen"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:unlock-outlined" />弿 </a-space> |
| | | </template> |
| | | <template v-if="bidOpen"> |
| | | <a-result status="success" title="已弿 "> |
| | | <template #sub-title> |
| | | <a-descriptions :column="2" size="small" class="mt-4"> |
| | | <a-descriptions-item label="弿 ç¼å·">{{ bidOpen.id }}</a-descriptions-item> |
| | | <a-descriptions-item label="弿 æ¶é´">{{ bidOpen.openTime }}</a-descriptions-item> |
| | | </a-descriptions> |
| | | </template> |
| | | </a-result> |
| | | </template> |
| | | <template v-else> |
| | | <div class="text-center py-8"> |
| | | <a-result status="info" title="å°æªå¼æ " sub-title="æ§è¡å¼æ å项ç®è¿å
¥ææ é¶æ®µï¼ä¾åºå坿£å¼ææ "> |
| | | <template #extra> |
| | | <a-button type="primary" @click="handleCreateBidOpen"> |
| | | <template #icon><IconifyIcon icon="ant-design:thunderbolt-outlined" /></template> |
| | | æ§è¡å¼æ |
| | | </a-button> |
| | | </template> |
| | | </a-result> |
| | | </div> |
| | | </template> |
| | | </a-tab-pane> |
| | | |
| | | <!-- Tab5: è¯æ --> |
| | | <a-tab-pane key="evaluation"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:trophy-outlined" />è¯æ </a-space> |
| | | </template> |
| | | <div class="flex items-center justify-between mb-3"> |
| | | <span class="text-xs text-gray-400">å
± <b class="text-gray-600">{{ evalCount }}</b> æ¡ï¼å·²æå <b class="text-green-500">{{ rankedCount }}</b> æ¡</span> |
| | | <a-space> |
| | | <a-button size="small" @click="handleCalculateRanking"> |
| | | <template #icon><IconifyIcon icon="ant-design:bar-chart-outlined" /></template> |
| | | è®¡ç®æå |
| | | </a-button> |
| | | <a-button type="primary" size="small" @click="evalForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0 }; evalFormVisible = true"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | æ°å¢è¯æ |
| | | </a-button> |
| | | </a-space> |
| | | </div> |
| | | <a-table :columns="evalColumns" :data-source="evaluationList" :loading="evalLoading" :pagination="false" row-key="id" size="small"> |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="['priceScore', 'techScore', 'deliveryScore', 'serviceScore'].includes(column.dataIndex)"> |
| | | <div class="flex items-center gap-2"> |
| | | <a-progress :percent="record[column.dataIndex] || 0" :show-info="false" size="small" |
| | | :stroke-color="record[column.dataIndex] >= 80 ? '#52c41a' : record[column.dataIndex] >= 60 ? '#faad14' : '#ff4d4f'" |
| | | class="flex-1" style="min-width: 50px;" /> |
| | | <span class="text-xs w-8 text-right">{{ record[column.dataIndex] ?? '-' }}</span> |
| | | </div> |
| | | </template> |
| | | <template v-if="column.dataIndex === 'compositeScore'"> |
| | | <span class="font-bold" :class="record.compositeScore >= 80 ? 'text-green-500' : record.compositeScore >= 60 ? 'text-orange-500' : 'text-red-500'"> |
| | | {{ record.compositeScore ?? '-' }} |
| | | </span> |
| | | </template> |
| | | <template v-if="column.dataIndex === 'rank'"> |
| | | <a-tag v-if="record.rank === 1" color="gold"><IconifyIcon icon="ant-design:trophy-outlined" /> 第1å</a-tag> |
| | | <a-tag v-else-if="record.rank" color="default">第{{ record.rank }}å</a-tag> |
| | | <span v-else class="text-gray-300">-</span> |
| | | </template> |
| | | <template v-if="column.key === 'actions'"> |
| | | <a-button size="small" type="link" @click="evalForm = { ...record }; evalFormVisible = true"><IconifyIcon icon="ant-design:edit-outlined" />ç¼è¾</a-button> |
| | | </template> |
| | | </template> |
| | | </a-table> |
| | | <a-empty v-if="!evalLoading && evaluationList.length === 0" description="ææ è¯æ è®°å½" class="mt-8" /> |
| | | </a-tab-pane> |
| | | |
| | | <!-- Tab6: 宿 --> |
| | | <a-tab-pane key="award"> |
| | | <template #tab> |
| | | <a-space :size="4"><IconifyIcon icon="ant-design:check-circle-outlined" />宿 </a-space> |
| | | </template> |
| | | <template v-if="award"> |
| | | <a-row :gutter="16"> |
| | | <a-col :span="16"> |
| | | <a-descriptions :column="2" bordered size="small"> |
| | | <a-descriptions-item label="宿 ç¼å·"> |
| | | <span class="font-mono">{{ award.awardNo }}</span> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="䏿 ä¾åºå"> |
| | | <a-tag color="blue">{{ award.supplierName || award.supplierId }}</a-tag> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="宿 éé¢"> |
| | | <span class="font-bold text-blue-600">{{ award.awardAmount?.toLocaleString() }}</span> å
|
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="宿 æ¶é´">{{ award.awardTime || '-' }}</a-descriptions-item> |
| | | <a-descriptions-item label="ç¶æ"> |
| | | <a-tag :color="award.awardStatus === AWARD_STATUS.AWARDED ? 'green' : 'orange'">{{ AWARD_STATUS_MAP[award.awardStatus] }}</a-tag> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="éè´è®¢å">{{ award.purchaseOrderId || 'å¾
çæ' }}</a-descriptions-item> |
| | | <a-descriptions-item label="夿³¨" :span="2">{{ award.remark || '-' }}</a-descriptions-item> |
| | | </a-descriptions> |
| | | </a-col> |
| | | <a-col :span="8" class="flex items-center justify-center"> |
| | | <template v-if="award.awardStatus === AWARD_STATUS.ORDER_GENERATED"> |
| | | <a-result status="success" title="订åå·²çæ" class="p-0"> |
| | | <template #sub-title> |
| | | <span class="text-sm">éè´è®¢å ID: {{ award.purchaseOrderId }}</span> |
| | | </template> |
| | | </a-result> |
| | | </template> |
| | | <template v-else-if="award.awardStatus === AWARD_STATUS.AWARDED"> |
| | | <div class="text-center"> |
| | | <a-result status="success" title="宿 宿" class="p-0 mb-3" /> |
| | | <a-button type="primary" block @click="handleGeneratePurchaseOrder"> |
| | | <template #icon><IconifyIcon icon="ant-design:shopping-cart-outlined" /></template> |
| | | çæéè´è®¢å |
| | | </a-button> |
| | | </div> |
| | | </template> |
| | | <template v-else> |
| | | <div class="text-center"> |
| | | <div class="text-gray-500 text-sm mb-3">宿 ä¿¡æ¯å·²å建ï¼è¯·å®¡æ¹</div> |
| | | <a-button type="primary" block @click="handleApproveAward" style="background: #52c41a; border-color: #52c41a;"> |
| | | <template #icon><IconifyIcon icon="ant-design:check-outlined" /></template> |
| | | 审æ¹éè¿ |
| | | </a-button> |
| | | </div> |
| | | </template> |
| | | </a-col> |
| | | </a-row> |
| | | </template> |
| | | <template v-else> |
| | | <div class="text-center py-8"> |
| | | <a-result status="info" title="å°æªå®æ " sub-title="åºäºè¯æ æå鿩䏿 ä¾åºåå¹¶åå»ºå®æ "> |
| | | <template #extra> |
| | | <a-button type="primary" @click="awardForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' }; awardFormVisible = true"> |
| | | <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template> |
| | | åå»ºå®æ |
| | | </a-button> |
| | | </template> |
| | | </a-result> |
| | | </div> |
| | | </template> |
| | | </a-tab-pane> |
| | | </a-tabs> |
| | | </a-card> |
| | | </div> |
| | | |
| | | <!-- ========== å¼¹çª ========== --> |
| | | |
| | | <!-- ç©æè¡¨å --> |
| | | <a-modal v-model:open="materialFormVisible" :title="materialForm.id ? 'ç¼è¾ç©æ' : 'æ·»å ç©æ'" :width="560" @ok="handleSaveMaterial"> |
| | | <a-form layout="vertical" class="mt-4"> |
| | | <a-form-item label="MDMç©æ" required> |
| | | <a-select v-model:value="materialForm.productId" show-search placeholder="æç´¢å¹¶éæ©MDMç©æ" :filter-option="filterOption" option-label-prop="label" @change="onMaterialSelect"> |
| | | <a-select-option v-for="item in mdmItemList" :key="item.id" :value="item.id" :label="`${item.code} ${item.name}`"> |
| | | <div class="flex flex-col"> |
| | | <span class="font-medium">{{ item.code }}</span> |
| | | <span class="text-xs text-gray-400">{{ item.name }}{{ item.specification ? ` · ${item.specification}` : '' }}</span> |
| | | </div> |
| | | </a-select-option> |
| | | </a-select> |
| | | </a-form-item> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="12"> |
| | | <a-form-item label="ç©æåç§°" required><a-input v-model:value="materialForm.productName" placeholder="èªå¨å¸¦å
¥" /></a-form-item> |
| | | </a-col> |
| | | <a-col :span="12"> |
| | | <a-form-item label="è§æ ¼åå·"><a-input v-model:value="materialForm.productSpec" placeholder="èªå¨å¸¦å
¥" /></a-form-item> |
| | | </a-col> |
| | | </a-row> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="8"> |
| | | <a-form-item label="åä½"><a-input v-model:value="materialForm.unit" placeholder="èªå¨å¸¦å
¥" /></a-form-item> |
| | | </a-col> |
| | | <a-col :span="8"> |
| | | <a-form-item label="æ°é" required><a-input-number v-model:value="materialForm.quantity" :min="0" class="!w-full" placeholder="æ°é" /></a-form-item> |
| | | </a-col> |
| | | <a-col :span="8"> |
| | | <a-form-item label="é¢ä¼°åä»·"><a-input-number v-model:value="materialForm.estimatedPrice" :min="0" class="!w-full" placeholder="åä»·" /></a-form-item> |
| | | </a-col> |
| | | </a-row> |
| | | <a-form-item label="ææ¯è¦æ±"> |
| | | <a-textarea v-model:value="materialForm.techRequirement" placeholder="å¦ï¼ç¬¦åGB/Tæ å" :rows="2" /> |
| | | </a-form-item> |
| | | </a-form> |
| | | </a-modal> |
| | | |
| | | <!-- ææ è¡¨å --> |
| | | <a-modal v-model:open="bidFormVisible" title="æ°å¢ææ " :width="460" @ok="handleCreateBid"> |
| | | <a-form layout="vertical" class="mt-4"> |
| | | <a-form-item label="ææ ç¼å·" required><a-input v-model:value="bidForm.bidNo" placeholder="å¦ BID2024010001" /></a-form-item> |
| | | <a-form-item label="ä¾åºå" required> |
| | | <a-select v-model:value="bidForm.supplierId" show-search placeholder="æç´¢å¹¶éæ©ä¾åºå" :filter-option="filterOption"> |
| | | <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option> |
| | | </a-select> |
| | | </a-form-item> |
| | | <a-form-item label="ææ æ»éé¢"> |
| | | <a-input-number v-model:value="bidForm.bidTotalAmount" :min="0" class="!w-full" placeholder="ææ æ»éé¢"><template #addonAfter>å
</template></a-input-number> |
| | | </a-form-item> |
| | | </a-form> |
| | | </a-modal> |
| | | |
| | | <!-- æ¥ä»·è¡¨å --> |
| | | <a-modal v-model:open="quoteFormVisible" :title="quoteForm.id ? 'ç¼è¾æ¥ä»·' : 'æ°å¢æ¥ä»·'" :width="560" @ok="handleSaveQuote"> |
| | | <a-form layout="vertical" class="mt-4"> |
| | | <a-form-item label="ææ ç©æ" required> |
| | | <a-select v-model:value="quoteForm.tenderMaterialId" show-search placeholder="æç´¢å¹¶éæ©ææ ç©æ" :filter-option="filterOption"> |
| | | <a-select-option v-for="m in availableMaterials" :key="m.id" :value="m.id" :label="m.productName">{{ m.productCode }} - {{ m.productName }}</a-select-option> |
| | | </a-select> |
| | | </a-form-item> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="8"> |
| | | <a-form-item label="æ¥ä»·ä»·æ ¼" required><a-input-number v-model:value="quoteForm.quotePrice" :min="0" class="!w-full" placeholder="æ¥ä»·"><template #addonAfter>å
</template></a-input-number></a-form-item> |
| | | </a-col> |
| | | <a-col :span="8"> |
| | | <a-form-item label="ç¨ç(%)"><a-input-number v-model:value="quoteForm.taxRate" :min="0" :max="100" class="!w-full" placeholder="å¦ 13" /></a-form-item> |
| | | </a-col> |
| | | <a-col :span="8"> |
| | | <a-form-item label="交æ(天)"><a-input-number v-model:value="quoteForm.deliveryCycle" :min="0" class="!w-full" placeholder="天æ°" /></a-form-item> |
| | | </a-col> |
| | | </a-row> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="12"> |
| | | <a-form-item label="仿¬¾æ¡ä»¶"><a-input v-model:value="quoteForm.paymentTerms" placeholder="å¦ï¼è´§å°30天仿¬¾" /></a-form-item> |
| | | </a-col> |
| | | <a-col :span="12"> |
| | | <a-form-item label="è´¨ä¿æ(æ)"><a-input-number v-model:value="quoteForm.warrantyPeriod" :min="0" class="!w-full" placeholder="ææ°" /></a-form-item> |
| | | </a-col> |
| | | </a-row> |
| | | <a-form-item label="夿³¨"><a-textarea v-model:value="quoteForm.remark" placeholder="å¦å«è¿è´¹" :rows="2" /></a-form-item> |
| | | </a-form> |
| | | </a-modal> |
| | | |
| | | <!-- è¯æ 表å --> |
| | | <a-modal v-model:open="evalFormVisible" :title="evalForm.id ? 'ç¼è¾è¯æ ' : 'æ°å¢è¯æ '" :width="600" @ok="handleCreateEvaluation"> |
| | | <a-form layout="vertical" class="mt-4"> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="12"> |
| | | <a-form-item label="ææ " required> |
| | | <a-select v-model:value="evalForm.bidId" show-search placeholder="éæ©ææ " :filter-option="filterOption" |
| | | @change="(val: number) => { const b = bidList.find(i => i.id === val); if (b) { evalForm.supplierId = b.supplierId; evalForm.supplierName = b.supplierName; } }"> |
| | | <a-select-option v-for="b in bidList" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option> |
| | | </a-select> |
| | | </a-form-item> |
| | | </a-col> |
| | | <a-col :span="12"> |
| | | <a-form-item label="ä¾åºå"> |
| | | <a-select v-model:value="evalForm.supplierId" show-search placeholder="éæ©ä¾åºå" :filter-option="filterOption"> |
| | | <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option> |
| | | </a-select> |
| | | </a-form-item> |
| | | </a-col> |
| | | </a-row> |
| | | <a-divider class="!my-2">è¯å项ï¼ç¾åå¶ï¼</a-divider> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="12"><a-form-item label="ä»·æ ¼è¯å"><a-input-number v-model:value="evalForm.priceScore" :min="0" :max="100" class="!w-full" placeholder="0-100" /></a-form-item></a-col> |
| | | <a-col :span="12"><a-form-item label="ææ¯è¯å"><a-input-number v-model:value="evalForm.techScore" :min="0" :max="100" class="!w-full" placeholder="0-100" /></a-form-item></a-col> |
| | | <a-col :span="12"><a-form-item label="交ä»è¯å"><a-input-number v-model:value="evalForm.deliveryScore" :min="0" :max="100" class="!w-full" placeholder="0-100" /></a-form-item></a-col> |
| | | <a-col :span="12"><a-form-item label="æå¡è¯å"><a-input-number v-model:value="evalForm.serviceScore" :min="0" :max="100" class="!w-full" placeholder="0-100" /></a-form-item></a-col> |
| | | </a-row> |
| | | <a-divider class="!my-2" /> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="12"><a-form-item label="è¯å§"><a-input v-model:value="evalForm.evaluatorName" placeholder="è¯å§å§å" /></a-form-item></a-col> |
| | | </a-row> |
| | | <a-form-item label="è¯æ æè§"><a-textarea v-model:value="evalForm.evaluationOpinion" placeholder="ä¾åºåçææ¯æ¹æ¡ã交ä»è½åç综åè¯ä»·" :rows="3" /></a-form-item> |
| | | </a-form> |
| | | </a-modal> |
| | | |
| | | <!-- 宿 表å --> |
| | | <a-modal v-model:open="awardFormVisible" title="åå»ºå®æ " :width="500" @ok="handleCreateAward"> |
| | | <a-form layout="vertical" class="mt-4"> |
| | | <a-form-item label="宿 ç¼å·" required><a-input v-model:value="awardForm.awardNo" placeholder="å¦ AWARD2024010001" /></a-form-item> |
| | | <a-row :gutter="12"> |
| | | <a-col :span="12"> |
| | | <a-form-item label="ä¾åºå" required> |
| | | <a-select v-model:value="awardForm.supplierId" show-search placeholder="éæ©ä¾åºå" :filter-option="filterOption"> |
| | | <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option> |
| | | </a-select> |
| | | </a-form-item> |
| | | </a-col> |
| | | <a-col :span="12"> |
| | | <a-form-item label="ææ " required> |
| | | <a-select v-model:value="awardForm.bidId" show-search placeholder="éæ©ææ " :filter-option="filterOption"> |
| | | <a-select-option v-for="b in bidList" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option> |
| | | </a-select> |
| | | </a-form-item> |
| | | </a-col> |
| | | </a-row> |
| | | <a-form-item label="宿 éé¢" required> |
| | | <a-input-number v-model:value="awardForm.awardAmount" :min="0" class="!w-full" placeholder="宿 éé¢"><template #addonAfter>å
</template></a-input-number> |
| | | </a-form-item> |
| | | <a-form-item label="夿³¨"><a-textarea v-model:value="awardForm.remark" placeholder="å¦ï¼ç»¼åè¯æ 第ä¸å" :rows="2" /></a-form-item> |
| | | </a-form> |
| | | </a-modal> |
| | | </template> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmTenderApi } from '#/api/srm/tender'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | closeTender, |
| | | deleteTenderProject, |
| | | generatePurchaseOrder, |
| | | getTenderProjectPage, |
| | | publishTender, |
| | | startBidding, |
| | | startEvaluation, |
| | | } from '#/api/srm/tender'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import TenderForm from './modules/form.vue'; |
| | | import { TENDER_STATUS } from '../enums'; |
| | | |
| | | defineOptions({ name: 'SrmTender' }); |
| | | |
| | | const router = useRouter(); |
| | | |
| | | function handleDetail(row: SrmTenderApi.TenderProjectVO) { |
| | | router.push({ path: '/srm/tender/detail', query: { id: row.id } }); |
| | | } |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | function handleEdit(row: SrmTenderApi.TenderProjectVO) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | async function handleDelete(row: SrmTenderApi.TenderProjectVO) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.tenderName]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteTenderProject(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.tenderName])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | async function handlePublish(row: SrmTenderApi.TenderProjectVO) { |
| | | await publishTender(row.id!); |
| | | message.success('å叿å'); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | async function handleStartBidding(row: SrmTenderApi.TenderProjectVO) { |
| | | await startBidding(row.id!); |
| | | message.success('å·²å¼å§ææ '); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | async function handleStartEvaluation(row: SrmTenderApi.TenderProjectVO) { |
| | | await startEvaluation(row.id!); |
| | | message.success('å·²å¼å§è¯æ '); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | async function handleClose(row: SrmTenderApi.TenderProjectVO) { |
| | | await closeTender(row.id!); |
| | | message.success('å·²å
³é'); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | async function handleGeneratePurchaseOrder(row: SrmTenderApi.TenderProjectVO) { |
| | | if (!row.awardId) return; |
| | | await generatePurchaseOrder(row.awardId); |
| | | message.success('éè´è®¢åå·²çæ'); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: TenderForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getTenderProjectPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true }, |
| | | } as VxeTableGridOptions<SrmTenderApi.TenderProjectVO>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="ææ é¡¹ç®å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ææ é¡¹ç®']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['srm:tender-project:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: '详æ
', |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'åå¸', |
| | | type: 'link', |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.DRAFT, |
| | | popConfirm: { |
| | | title: '确认è¦åå¸è¯¥é¡¹ç®åï¼åå¸åæ æ³ä¿®æ¹ã', |
| | | confirm: handlePublish.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.DRAFT, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['srm:tender-project:delete'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.DRAFT, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.tenderName]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'å¼å§ææ ', |
| | | type: 'link', |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED, |
| | | popConfirm: { |
| | | title: '确认è¦å¼å§ææ åï¼', |
| | | confirm: handleStartBidding.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'å
³é', |
| | | type: 'link', |
| | | danger: true, |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING, |
| | | popConfirm: { |
| | | title: '确认è¦å
³é该项ç®åï¼', |
| | | confirm: handleClose.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'å¼å§è¯æ ', |
| | | type: 'link', |
| | | auth: ['srm:tender-project:update'], |
| | | ifShow: row.tenderStatus === TENDER_STATUS.BIDDING, |
| | | popConfirm: { |
| | | title: '确认è¦å¼å§è¯æ åï¼', |
| | | confirm: handleStartEvaluation.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'çæéè´è®¢å', |
| | | type: 'link', |
| | | auth: ['srm:bid-award:update'], |
| | | ifShow: !!row.awardId && !row.purchaseOrderNo, |
| | | popConfirm: { |
| | | title: '确认è¦çæéè´è®¢ååï¼', |
| | | confirm: handleGeneratePurchaseOrder.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { SrmTenderApi } from '#/api/srm/tender'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createTenderProject, |
| | | getTenderProject, |
| | | updateTenderProject, |
| | | } from '#/api/srm/tender'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<SrmTenderApi.TenderProjectVO>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['ææ é¡¹ç®']) |
| | | : $t('ui.actionTitle.create', ['ææ é¡¹ç®']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { class: 'w-full' }, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as SrmTenderApi.TenderProjectVO; |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateTenderProject(data) |
| | | : createTenderProject(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<SrmTenderApi.TenderProjectVO>(); |
| | | if (!data || !data.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getTenderProject(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | defineExpose({ modalApi }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-1/2"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©è¿ææ¶é´', |
| | | }, |
| | | rules: 'required', |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©å°è´§æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©å
¥åºæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©çäº§æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æææ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åºåºæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©å
¥åºæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åææ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©å
¥åºæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©çäº§æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æææ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©è£
ç®±æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©æææ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©éæ±æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©å
¥åºæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | disabled: isHeaderReadonly(formType), |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åºåºæ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©éææ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©éè´§æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©éè´§æ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åè´§æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©å¼å§æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['type'], |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç»ææ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['type'], |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©å¼å§æ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['type'], |
| | |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç»ææ¶é´', |
| | | showTime: true, |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['type'], |
| | |
| | | class: 'w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åæ®æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | fieldName: 'orderTime', |
| | | label: 'åæ®æ¥æ', |
| | |
| | | class: 'w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åæ®æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | fieldName: 'orderTime', |
| | | label: 'åæ®æ¥æ', |
| | |
| | | class: 'w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åæ®æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | fieldName: 'orderTime', |
| | | label: 'åæ®æ¥æ', |
| | |
| | | class: 'w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©åæ®æ¥æ', |
| | | valueFormat: 'x', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | fieldName: 'orderTime', |
| | | label: 'åæ®æ¥æ', |