| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace FinFixedAssetApi { |
| | | /** åºå®èµäº§ä¿¡æ¯ */ |
| | | export interface FixedAsset { |
| | | id?: number; // èµäº§ç¼å· |
| | | assetCode?: string; // èµäº§ç¼ç |
| | | assetName?: string; // èµäº§åç§° |
| | | category?: string; // èµäº§ç±»å« |
| | | specification?: string; // è§æ ¼åå· |
| | | purchaseDate?: string; // è´ç½®æ¥æ yyyy-MM-dd |
| | | originalValue?: number; // èµäº§åå¼ |
| | | usefulLife?: number; // 使ç¨å¹´éï¼å¹´ï¼ |
| | | residualRate?: number; // æ®å¼çï¼%ï¼ |
| | | monthlyDepreciation?: number; // æææ§é¢ï¼å端计ç®ï¼åªè¯»ï¼ |
| | | accumulatedDepreciation?: number; // ç´¯è®¡ææ§ |
| | | netValue?: number; // åå¼ |
| | | location?: string; // åæ¾å°ç¹ |
| | | departmentId?: number; // 使ç¨é¨é¨ç¼å·ï¼system_dept.idï¼è¡¨åæäº¤ç¨ï¼ |
| | | departmentName?: string; // 使ç¨é¨é¨åç§°ï¼å端解æï¼åªè¯»ï¼ |
| | | keeperId?: number; // ä¿ç®¡äººç¼å·ï¼system_users.idï¼è¡¨åæäº¤ç¨ï¼ |
| | | keeperName?: string; // ä¿ç®¡äººæµç§°ï¼å端解æï¼åªè¯»ï¼ |
| | | status?: number; // ç¶æï¼1å¨ç¨ 2é²ç½® 3ç»´ä¿®ä¸ 4å·²æ¥åº |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | } |
| | | |
| | | /** è®¡æææ§ç»æ */ |
| | | export interface DepreciateResult { |
| | | processedCount: number; // æ¬æ¬¡è®¡æçèµäº§æ°é |
| | | totalMonthlyDepreciation: number; // æ¬æ¬¡è®¡æææ§å计 |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢åºå®èµäº§å页 */ |
| | | export function getFixedAssetPage(params: PageParam) { |
| | | return requestClient.get<PageResult<FinFixedAssetApi.FixedAsset>>( |
| | | '/finance/fixed-asset/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢åºå®èµäº§å表ï¼ä¸å页ï¼ç¨äºç»è®¡ï¼ */ |
| | | export function getFixedAssetList() { |
| | | return requestClient.get<FinFixedAssetApi.FixedAsset[]>( |
| | | '/finance/fixed-asset/list', |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢åºå®èµäº§è¯¦æ
*/ |
| | | export function getFixedAsset(id: number) { |
| | | return requestClient.get<FinFixedAssetApi.FixedAsset>( |
| | | `/finance/fixed-asset/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢åºå®èµäº§ */ |
| | | export function createFixedAsset(data: FinFixedAssetApi.FixedAsset) { |
| | | return requestClient.post('/finance/fixed-asset/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹åºå®èµäº§ */ |
| | | export function updateFixedAsset(data: FinFixedAssetApi.FixedAsset) { |
| | | return requestClient.put('/finance/fixed-asset/update', data); |
| | | } |
| | | |
| | | /** å é¤åºå®èµäº§ */ |
| | | export function deleteFixedAsset(id: number) { |
| | | return requestClient.delete(`/finance/fixed-asset/delete?id=${id}`); |
| | | } |
| | | |
| | | /** æ¹éå é¤åºå®èµäº§ */ |
| | | export function deleteFixedAssetList(ids: number[]) { |
| | | return requestClient.delete( |
| | | `/finance/fixed-asset/delete-list?ids=${ids.join(',')}`, |
| | | ); |
| | | } |
| | | |
| | | /** è®¡æææ§ï¼ids 为空表示å
¨é¨è®¡æï¼ */ |
| | | export function depreciateFixedAssetList(ids: number[]) { |
| | | return requestClient.post<FinFixedAssetApi.DepreciateResult>( |
| | | `/finance/fixed-asset/depreciate?ids=${ids.join(',')}`, |
| | | ); |
| | | } |
| | | |
| | | /** 导åºåºå®èµäº§ Excel */ |
| | | export function exportFixedAsset(params: any) { |
| | | return requestClient.download('/finance/fixed-asset/export-excel', { |
| | | params, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace FinIntangibleAssetApi { |
| | | /** æ å½¢èµäº§ä¿¡æ¯ */ |
| | | export interface IntangibleAsset { |
| | | id?: number; // èµäº§ç¼å· |
| | | assetCode?: string; // èµäº§ç¼ç |
| | | assetName?: string; // èµäº§åç§° |
| | | category?: string; // èµäº§ç±»å« |
| | | certificateNo?: string; // è¯ä¹¦ç¼å· |
| | | acquisitionDate?: string; // å徿¥æ yyyy-MM-dd |
| | | originalValue?: number; // èµäº§åå¼ |
| | | amortizationPeriod?: number; // æéå¹´éï¼å¹´ï¼ |
| | | residualRate?: number; // æ®å¼çï¼%ï¼ |
| | | monthlyAmortization?: number; // ææéé¢ï¼å端计ç®ï¼åªè¯»ï¼ |
| | | accumulatedAmortization?: number; // 累计æé |
| | | netValue?: number; // åå¼ |
| | | validityDate?: string; // æææè³ yyyy-MM-dd |
| | | status?: number; // ç¶æï¼1å¨ç¨ 2已失æ 3å·²æéå® |
| | | description?: string; // èµäº§æè¿° |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | } |
| | | |
| | | /** 计ææéç»æ */ |
| | | export interface AmortizeResult { |
| | | processedCount: number; // æ¬æ¬¡è®¡æçèµäº§æ°é |
| | | totalMonthlyAmortization: number; // æ¬æ¬¡è®¡ææéå计 |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢æ å½¢èµäº§å页 */ |
| | | export function getIntangibleAssetPage(params: PageParam) { |
| | | return requestClient.get<PageResult<FinIntangibleAssetApi.IntangibleAsset>>( |
| | | '/finance/intangible-asset/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢æ å½¢èµäº§å表ï¼ä¸å页ï¼ç¨äºç»è®¡ï¼ */ |
| | | export function getIntangibleAssetList() { |
| | | return requestClient.get<FinIntangibleAssetApi.IntangibleAsset[]>( |
| | | '/finance/intangible-asset/list', |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢æ å½¢èµäº§è¯¦æ
*/ |
| | | export function getIntangibleAsset(id: number) { |
| | | return requestClient.get<FinIntangibleAssetApi.IntangibleAsset>( |
| | | `/finance/intangible-asset/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢æ å½¢èµäº§ */ |
| | | export function createIntangibleAsset( |
| | | data: FinIntangibleAssetApi.IntangibleAsset, |
| | | ) { |
| | | return requestClient.post('/finance/intangible-asset/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹æ å½¢èµäº§ */ |
| | | export function updateIntangibleAsset( |
| | | data: FinIntangibleAssetApi.IntangibleAsset, |
| | | ) { |
| | | return requestClient.put('/finance/intangible-asset/update', data); |
| | | } |
| | | |
| | | /** å 餿 å½¢èµäº§ */ |
| | | export function deleteIntangibleAsset(id: number) { |
| | | return requestClient.delete(`/finance/intangible-asset/delete?id=${id}`); |
| | | } |
| | | |
| | | /** æ¹éå 餿 å½¢èµäº§ */ |
| | | export function deleteIntangibleAssetList(ids: number[]) { |
| | | return requestClient.delete( |
| | | `/finance/intangible-asset/delete-list?ids=${ids.join(',')}`, |
| | | ); |
| | | } |
| | | |
| | | /** 计ææéï¼ids 为空表示å
¨é¨è®¡æï¼ */ |
| | | export function amortizeIntangibleAssetList(ids: number[]) { |
| | | return requestClient.post<FinIntangibleAssetApi.AmortizeResult>( |
| | | `/finance/intangible-asset/amortize?ids=${ids.join(',')}`, |
| | | ); |
| | | } |
| | | |
| | | /** å¯¼åºæ å½¢èµäº§ Excel */ |
| | | export function exportIntangibleAsset(params: any) { |
| | | return requestClient.download('/finance/intangible-asset/export-excel', { |
| | | params, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace FinLedgerApi { |
| | | /** æ»è´¦ / æç»è´¦æ¥è¯¢åæ° */ |
| | | export interface LedgerParams { |
| | | subjectCode: string; // ç§ç®ç¼ç |
| | | startMonth?: string; // èµ·å§æä»½ yyyy-MM |
| | | endMonth?: string; // ç»ææä»½ yyyy-MM |
| | | includeChildren?: boolean; // æ¯å¦å
å«ä¸çº§ç§ç® |
| | | } |
| | | |
| | | /** æ»è´¦ / æç»è´¦è¡ */ |
| | | export interface LedgerRow { |
| | | rowType: string; // è¡ç±»åï¼openingæå entryæç» monthly_totalæå计 yearly_totalå¹´å计 |
| | | period?: string; // æé´ yyyy-MM |
| | | date?: string; // æ¥æ |
| | | voucherNo?: string; // åè¯åå· |
| | | subjectCode?: string; // ç§ç®ç¼ç |
| | | subjectName?: string; // ç§ç®åç§° |
| | | summary?: string; // æè¦ |
| | | debit?: number; // åæ¹éé¢ |
| | | credit?: number; // è´·æ¹éé¢ |
| | | direction?: number; // ä½é¢æ¹åï¼1å 2è´· |
| | | balance?: number; // ä½é¢ |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢æ»è´¦ */ |
| | | export function getGeneralLedger(params: FinLedgerApi.LedgerParams) { |
| | | return requestClient.get<FinLedgerApi.LedgerRow[]>('/finance/ledger/general', { |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** æ¥è¯¢æç»è´¦ */ |
| | | export function getDetailLedger(params: FinLedgerApi.LedgerParams) { |
| | | return requestClient.get<FinLedgerApi.LedgerRow[]>('/finance/ledger/detail', { |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** å¯¼åºæ»è´¦ Excel */ |
| | | export function exportGeneralLedger(params: FinLedgerApi.LedgerParams) { |
| | | return requestClient.download('/finance/ledger/general/export-excel', { |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** å¯¼åºæç»è´¦ Excel */ |
| | | export function exportDetailLedger(params: FinLedgerApi.LedgerParams) { |
| | | return requestClient.download('/finance/ledger/detail/export-excel', { |
| | | params, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace FinStatisticsApi { |
| | | /** ä¼è®¡æ ¸ç®çæ¿æ¥è¯¢åæ° */ |
| | | export interface StatisticsParams { |
| | | startMonth?: string; // èµ·å§æä»½ yyyy-MMï¼ç空é»è®¤æ¬å¹´ 1 æ |
| | | endMonth?: string; // ç»ææä»½ yyyy-MMï¼ç空é»è®¤å½åæ |
| | | } |
| | | |
| | | /** ä¼è®¡æ ¸ç®çæ¿æ»è§ */ |
| | | export interface StatisticsOverview { |
| | | startDate: string; // å®é
ç»è®¡èµ·å§æ¥æ |
| | | endDate: string; // å®é
ç»è®¡ç»ææ¥æ |
| | | receiptPrice: number; // æ¶æ¬¾éé¢ï¼ERP å·²å®¡æ ¸æ¶æ¬¾åï¼ |
| | | paymentPrice: number; // 仿¬¾éé¢ï¼ERP å·²å®¡æ ¸ä»æ¬¾åï¼ |
| | | netProfit: number; // å婿¶¦ = æ¶æ¬¾ - 仿¬¾ |
| | | saleOrderPrice: number; // éå®è®¢åéé¢ï¼å·²å®¡æ ¸ï¼ |
| | | purchaseOrderPrice: number; // éè´è®¢åéé¢ï¼å·²å®¡æ ¸ï¼ |
| | | |
| | | fixedAssetOriginalValue: number; // åºå®èµäº§åå¼å计 |
| | | fixedAssetAccumulatedDepreciation: number; // åºå®èµäº§ç´¯è®¡ææ§å计 |
| | | fixedAssetNetValue: number; // åºå®èµäº§åå¼å计 |
| | | intangibleAssetOriginalValue: number; // æ å½¢èµäº§åå¼å计 |
| | | intangibleAssetAccumulatedAmortization: number; // æ å½¢èµäº§ç´¯è®¡æéå计 |
| | | intangibleAssetNetValue: number; // æ å½¢èµäº§åå¼å计 |
| | | totalAssetNetValue: number; // èµäº§åå¼å计 |
| | | |
| | | subjectCount: number; // ä¼è®¡ç§ç®æ°é |
| | | voucherCount: number; // åè¯æ»æ° |
| | | voucherUnpostedCount: number; // æªè¿è´¦åè¯æ°é |
| | | voucherPostedCount: number; // å·²è¿è´¦åè¯æ°é |
| | | voucherCancelledCount: number; // å·²ä½åºåè¯æ°é |
| | | } |
| | | |
| | | /** æåº¦è¶å¿ */ |
| | | export interface StatisticsTrend { |
| | | month: string; // æä»½ yyyy-MM |
| | | receiptPrice: number; // æ¶æ¬¾éé¢ |
| | | paymentPrice: number; // 仿¬¾éé¢ |
| | | netProfit: number; // å婿¶¦ |
| | | saleOrderPrice: number; // éå®è®¢åéé¢ |
| | | purchaseOrderPrice: number; // éè´è®¢åéé¢ |
| | | } |
| | | } |
| | | |
| | | /** è·åä¼è®¡æ ¸ç®çæ¿æ»è§ */ |
| | | export function getStatisticsOverview(params?: FinStatisticsApi.StatisticsParams) { |
| | | return requestClient.get<FinStatisticsApi.StatisticsOverview>( |
| | | '/finance/statistics/overview', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** è·åæåº¦æ¶æ¯è¶å¿ï¼æé´å
æ æ°æ®çæä»½è¡¥ 0ï¼ */ |
| | | export function getStatisticsTrend(params?: FinStatisticsApi.StatisticsParams) { |
| | | return requestClient.get<FinStatisticsApi.StatisticsTrend[]>( |
| | | '/finance/statistics/trend', |
| | | { params }, |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace FinAccountSubjectApi { |
| | | /** ä¼è®¡ç§ç®ä¿¡æ¯ */ |
| | | export interface AccountSubject { |
| | | id?: number; // ç§ç®ç¼å· |
| | | parentId?: number; // ä¸çº§ç§ç®ç¼å·ï¼ç©ºè¡¨ç¤ºä¸çº§ç§ç® |
| | | subjectCode: string; // ç§ç®ç¼ç |
| | | subjectName: string; // ç§ç®åç§° |
| | | subjectType: number; // ç§ç®ç±»åï¼1èµäº§ç±» 2è´åºç±» 3ææè
æçç±» 4ææ¬ç±» 5æçç±» |
| | | balanceDirection: number; // ä½é¢æ¹åï¼1å 2è´· |
| | | status: number; // ç¶æï¼0å¯ç¨ 1ç¦ç¨ |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | children?: AccountSubject[]; // åç§ç®ï¼æ å½¢è¿åï¼ |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¼è®¡ç§ç®å页 */ |
| | | export function getAccountSubjectPage(params: PageParam) { |
| | | return requestClient.get<PageResult<FinAccountSubjectApi.AccountSubject>>( |
| | | '/finance/account-subject/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¼è®¡ç§ç®å½ï¼æ å½¢ï¼å表 */ |
| | | export function getAccountSubjectList(params?: any) { |
| | | return requestClient.get<FinAccountSubjectApi.AccountSubject[]>( |
| | | '/finance/account-subject/list', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¼è®¡ç§ç®ç²¾ç®å表ï¼ä¸å页ãå¹³éºï¼ */ |
| | | export function getAccountSubjectSimpleList() { |
| | | return requestClient.get<FinAccountSubjectApi.AccountSubject[]>( |
| | | '/finance/account-subject/simple-list', |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¼è®¡ç§ç®è¯¦æ
*/ |
| | | export function getAccountSubject(id: number) { |
| | | return requestClient.get<FinAccountSubjectApi.AccountSubject>( |
| | | `/finance/account-subject/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢ä¼è®¡ç§ç® */ |
| | | export function createAccountSubject( |
| | | data: FinAccountSubjectApi.AccountSubject, |
| | | ) { |
| | | return requestClient.post('/finance/account-subject/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹ä¼è®¡ç§ç® */ |
| | | export function updateAccountSubject( |
| | | data: FinAccountSubjectApi.AccountSubject, |
| | | ) { |
| | | return requestClient.put('/finance/account-subject/update', data); |
| | | } |
| | | |
| | | /** å é¤ä¼è®¡ç§ç®ï¼æ¯ææ¹éï¼å«ä¸çº§ç§ç®ï¼ */ |
| | | export function deleteAccountSubject(ids: number[]) { |
| | | return requestClient.delete( |
| | | `/finance/account-subject/delete?ids=${ids.join(',')}`, |
| | | ); |
| | | } |
| | | |
| | | /** 导åºä¼è®¡ç§ç® Excel */ |
| | | export function exportAccountSubject(params: any) { |
| | | return requestClient.download('/finance/account-subject/export-excel', { |
| | | params, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import type { SystemStorageApi } from '#/api/system/storage'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace FinVoucherApi { |
| | | /** åè¯åå½ */ |
| | | export interface VoucherEntry { |
| | | id?: number; // åå½ç¼å· |
| | | rowNo?: number; // è¡å· |
| | | subjectCode?: string; // ç§ç®ç¼ç |
| | | subjectName?: string; // ç§ç®åç§° |
| | | summary?: string; // æè¦ |
| | | debit?: number; // åæ¹éé¢ |
| | | credit?: number; // è´·æ¹éé¢ |
| | | auxiliaryType?: number; // è¾
婿 ¸ç®ç±»å |
| | | auxiliaryId?: string; // è¾
婿 ¸ç®å¯¹è±¡ç¼å· |
| | | auxiliaryName?: string; // è¾
婿 ¸ç®å¯¹è±¡åç§° |
| | | } |
| | | |
| | | /** è®°è´¦åè¯ */ |
| | | export interface Voucher { |
| | | id?: number; // åè¯ç¼å· |
| | | voucherNo?: string; // åè¯åå·ï¼ç空æ¶å端èªå¨çæ |
| | | voucherDate: string; // åè¯æ¥æ |
| | | summary?: string; // æè¦ |
| | | debit?: number; // åæ¹å计 |
| | | credit?: number; // è´·æ¹å计 |
| | | status?: number; // åè¯ç¶æï¼0æªè¿è´¦ 10å·²è¿è´¦ 20å·²ä½åº |
| | | creator?: string; // å¶å人ï¼ç¨æ·ç¼å·ï¼ä»
ç¨äºæç´¢æ¡ä»¶ï¼ |
| | | creatorName?: string; // å¶å人æµç§°ï¼å表ã导åºå±ç¤ºç¨ï¼ |
| | | attachmentCount?: number; // éä»¶å¼ æ° |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | entries?: VoucherEntry[]; // åè¯åå½ |
| | | attachmentList?: SystemStorageApi.StorageBlob[]; // éä»¶å表 |
| | | hasAttachment?: boolean; // æ¯å¦æéä»¶ |
| | | blobIds?: number[]; // éä»¶ blobId åè¡¨ï¼æäº¤ç¨ï¼ |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢è®°è´¦åè¯å页 */ |
| | | export function getVoucherPage(params: PageParam) { |
| | | return requestClient.get<PageResult<FinVoucherApi.Voucher>>( |
| | | '/finance/voucher/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢è®°è´¦åè¯å页ï¼å«åå½ï¼ç¨äºåè¯å表å±å¼ä¸æå°ï¼ */ |
| | | export function getVoucherPageWithEntry(params: PageParam) { |
| | | return requestClient.get<PageResult<FinVoucherApi.Voucher>>( |
| | | '/finance/voucher/page-with-entry', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢è®°è´¦åè¯è¯¦æ
ï¼å«åå½ä¸éä»¶ï¼ */ |
| | | export function getVoucher(id: number) { |
| | | return requestClient.get<FinVoucherApi.Voucher>( |
| | | `/finance/voucher/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢è®°è´¦åè¯ */ |
| | | export function createVoucher(data: FinVoucherApi.Voucher) { |
| | | return requestClient.post('/finance/voucher/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹è®°è´¦åè¯ */ |
| | | export function updateVoucher(data: FinVoucherApi.Voucher) { |
| | | return requestClient.put('/finance/voucher/update', data); |
| | | } |
| | | |
| | | /** å é¤è®°è´¦åè¯ */ |
| | | export function deleteVoucher(id: number) { |
| | | return requestClient.delete(`/finance/voucher/delete?id=${id}`); |
| | | } |
| | | |
| | | /** æ¹éå é¤è®°è´¦åè¯ */ |
| | | export function deleteVoucherList(ids: number[]) { |
| | | return requestClient.delete( |
| | | `/finance/voucher/delete-list?ids=${ids.join(',')}`, |
| | | ); |
| | | } |
| | | |
| | | /** åè¯è¿è´¦ */ |
| | | export function postVoucher(id: number) { |
| | | return requestClient.put('/finance/voucher/post', null, { params: { id } }); |
| | | } |
| | | |
| | | /** åè¯ä½åº */ |
| | | export function cancelVoucher(id: number) { |
| | | return requestClient.put('/finance/voucher/cancel', null, { params: { id } }); |
| | | } |
| | | |
| | | /** 导åºè®°è´¦åè¯ Excel */ |
| | | export function exportVoucher(params: any) { |
| | | return requestClient.download('/finance/voucher/export-excel', { params }); |
| | | } |
| | |
| | | ERP_STOCK_RECORD_BIZ_TYPE: 'erp_stock_record_biz_type', // åºåæç»çä¸å¡ç±»å |
| | | } as const; |
| | | |
| | | /** ========== FINANCE - è´¢å¡ç®¡ç模å ========== */ |
| | | const FINANCE_DICT = { |
| | | FINANCE_SUBJECT_TYPE: 'finance_subject_type', // ä¼è®¡ç§ç®ç±»å |
| | | FINANCE_BALANCE_DIRECTION: 'finance_balance_direction', // ç§ç®ä½é¢æ¹å |
| | | FINANCE_AUXILIARY_TYPE: 'finance_auxiliary_type', // è¾
婿 ¸ç®ç±»å |
| | | FINANCE_VOUCHER_STATUS: 'finance_voucher_status', // è®°è´¦åè¯ç¶æ |
| | | FINANCE_FIXED_ASSET_STATUS: 'finance_fixed_asset_status', // åºå®èµäº§ç¶æ |
| | | FINANCE_INTANGIBLE_ASSET_STATUS: 'finance_intangible_asset_status', // æ å½¢èµäº§ç¶æ |
| | | } as const; |
| | | |
| | | /** ========== AI - 人工æºè½æ¨¡å ========== */ |
| | | const AI_DICT = { |
| | | AI_GENERATE_MODE: 'ai_generate_mode', // AI çææ¨¡å¼ |
| | |
| | | ...BPM_DICT, |
| | | ...CRM_DICT, |
| | | ...ERP_DICT, |
| | | ...FINANCE_DICT, |
| | | ...HRM_DICT, |
| | | ...INFRA_DICT, |
| | | ...IM_DICT, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinFixedAssetApi } from '#/api/erp/finance/fixed-asset'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | import { handleTree } from '@vben/utils'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getSimpleDeptList } from '#/api/system/dept'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | import dayjs from 'dayjs'; |
| | | |
| | | /** |
| | | * 使ç¨é¨é¨ä¸æï¼ç³»ç»é¨é¨æ ï¼value = é¨é¨ç¼å·ï¼ |
| | | * ä¸ CRM ç»è®¡é¡µçãå½å±é¨é¨ãåæ³ä¿æä¸è´ |
| | | */ |
| | | function departmentSelectProps() { |
| | | return { |
| | | allowClear: true, |
| | | class: 'w-full', |
| | | api: async () => handleTree(await getSimpleDeptList()), |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | childrenField: 'children', |
| | | placeholder: 'è¯·éæ©ä½¿ç¨é¨é¨', |
| | | treeDefaultExpandAll: true, |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * ä¿ç®¡äººä¸æï¼ç³»ç»ç¨æ·å表ï¼value = ç¨æ·ç¼å·ï¼label = æµç§°ï¼ |
| | | * ä¸ WMS åæ®ééç¨æ·çåæ³ä¿æä¸è´ |
| | | */ |
| | | function keeperSelectProps() { |
| | | return { |
| | | allowClear: true, |
| | | class: 'w-full', |
| | | api: getSimpleUserList, |
| | | labelField: 'nickname', |
| | | valueField: 'id', |
| | | placeholder: 'è¯·éæ©ä¿ç®¡äºº', |
| | | showSearch: true, |
| | | }; |
| | | } |
| | | |
| | | /** èµäº§ç¶æï¼1å¨ç¨ 2é²ç½® 3ç»´ä¿®ä¸ 4å·²æ¥åº */ |
| | | export const FIXED_ASSET_STATUS_IN_USE = 1; |
| | | export const FIXED_ASSET_STATUS_IDLE = 2; |
| | | export const FIXED_ASSET_STATUS_REPAIRING = 3; |
| | | export const FIXED_ASSET_STATUS_SCRAPPED = 4; |
| | | |
| | | /** å¯è®¡æææ§çç¶æï¼å·²æ¥åºçèµäº§ä¸å计æï¼ */ |
| | | export const DEPRECIABLE_FIXED_ASSET_STATUS = [ |
| | | FIXED_ASSET_STATUS_IN_USE, |
| | | FIXED_ASSET_STATUS_IDLE, |
| | | FIXED_ASSET_STATUS_REPAIRING, |
| | | ]; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'assetCode', |
| | | label: 'èµäº§ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§ç¼ç ï¼å¦ GD2026100001', |
| | | allowClear: true, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'assetName', |
| | | label: 'èµäº§åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§åç§°', |
| | | allowClear: true, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'category', |
| | | label: 'èµäº§ç±»å«', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§ç±»å«ï¼å¦ ç产设å¤', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'specification', |
| | | label: 'è§æ ¼åå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è§æ ¼åå·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'purchaseDate', |
| | | label: 'è´ç½®æ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è´ç½®æ¥æ', |
| | | showTime: false, |
| | | valueFormat: 'YYYY-MM-DD', |
| | | format: 'YYYY-MM-DD', |
| | | class: 'w-full', |
| | | }, |
| | | rules: 'required', |
| | | defaultValue: dayjs().format('YYYY-MM-DD'), |
| | | }, |
| | | { |
| | | fieldName: 'originalValue', |
| | | label: 'èµäº§åå¼', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | placeholder: '请è¾å
¥èµäº§åå¼', |
| | | min: 0, |
| | | precision: 2, |
| | | }, |
| | | rules: z.number().min(0).default(0), |
| | | }, |
| | | { |
| | | fieldName: 'usefulLife', |
| | | label: '使ç¨å¹´éï¼å¹´ï¼', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | placeholder: '请è¾å
¥ä½¿ç¨å¹´é', |
| | | min: 1, |
| | | precision: 0, |
| | | }, |
| | | defaultValue: 10, |
| | | rules: z.number().min(1).default(10), |
| | | }, |
| | | { |
| | | fieldName: 'residualRate', |
| | | label: 'æ®å¼çï¼%ï¼', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | placeholder: '请è¾å
¥æ®å¼ç', |
| | | min: 0, |
| | | max: 100, |
| | | precision: 2, |
| | | }, |
| | | defaultValue: 5, |
| | | rules: z.number().min(0).max(100).default(5), |
| | | help: 'æææ§é¢ =ï¼åå¼ Ãï¼1 - æ®å¼çï¼ï¼Ã·ï¼ä½¿ç¨å¹´é à 12ï¼', |
| | | }, |
| | | { |
| | | fieldName: 'location', |
| | | label: 'åæ¾å°ç¹', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥åæ¾å°ç¹', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'departmentId', |
| | | label: '使ç¨é¨é¨', |
| | | component: 'ApiTreeSelect', |
| | | componentProps: departmentSelectProps(), |
| | | }, |
| | | { |
| | | fieldName: 'keeperId', |
| | | label: 'ä¿ç®¡äºº', |
| | | component: 'ApiSelect', |
| | | componentProps: keeperSelectProps(), |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'èµäº§ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | options: getDictOptions( |
| | | DICT_TYPE.FINANCE_FIXED_ASSET_STATUS, |
| | | 'number', |
| | | ), |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | }, |
| | | defaultValue: FIXED_ASSET_STATUS_IN_USE, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | formItemClass: 'md:col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'assetCode', |
| | | label: 'èµäº§ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§ç¼ç ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'assetName', |
| | | label: 'èµäº§åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§åç§°', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'category', |
| | | label: 'èµäº§ç±»å«', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§ç±»å«', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'purchaseDate', |
| | | label: 'è´ç½®æ¥æ', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'departmentId', |
| | | label: '使ç¨é¨é¨', |
| | | component: 'ApiTreeSelect', |
| | | componentProps: departmentSelectProps(), |
| | | }, |
| | | { |
| | | fieldName: 'keeperId', |
| | | label: 'ä¿ç®¡äºº', |
| | | component: 'ApiSelect', |
| | | componentProps: keeperSelectProps(), |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'èµäº§ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getDictOptions( |
| | | DICT_TYPE.FINANCE_FIXED_ASSET_STATUS, |
| | | 'number', |
| | | ), |
| | | placeholder: 'è¯·éæ©èµäº§ç¶æ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<FinFixedAssetApi.FixedAsset>['columns'] { |
| | | return [ |
| | | { type: 'checkbox', width: 40 }, |
| | | { |
| | | field: 'assetCode', |
| | | title: 'èµäº§ç¼ç ', |
| | | minWidth: 150, |
| | | fixed: 'left', |
| | | }, |
| | | { |
| | | field: 'assetName', |
| | | title: 'èµäº§åç§°', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'category', |
| | | title: 'èµäº§ç±»å«', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'specification', |
| | | title: 'è§æ ¼åå·', |
| | | minWidth: 140, |
| | | showOverflow: 'tooltip', |
| | | }, |
| | | { |
| | | field: 'purchaseDate', |
| | | title: 'è´ç½®æ¥æ', |
| | | minWidth: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'originalValue', |
| | | title: 'èµäº§åå¼', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'usefulLife', |
| | | title: '使ç¨å¹´é', |
| | | minWidth: 100, |
| | | align: 'right', |
| | | }, |
| | | { |
| | | field: 'residualRate', |
| | | title: 'æ®å¼ç(%)', |
| | | minWidth: 110, |
| | | align: 'right', |
| | | }, |
| | | { |
| | | field: 'monthlyDepreciation', |
| | | title: 'æææ§é¢', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'accumulatedDepreciation', |
| | | title: 'ç´¯è®¡ææ§', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'netValue', |
| | | title: 'åå¼', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | // å端 departmentId åçæ¯é¨é¨ç¼å·ï¼å表å±ç¤ºç¨å端解æå¥½ç departmentName |
| | | field: 'departmentName', |
| | | title: '使ç¨é¨é¨', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'keeperName', |
| | | title: 'ä¿ç®¡äºº', |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'èµäº§ç¶æ', |
| | | minWidth: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.FINANCE_FIXED_ASSET_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | minWidth: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinFixedAssetApi } from '#/api/erp/finance/fixed-asset'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteFixedAsset, |
| | | deleteFixedAssetList, |
| | | depreciateFixedAssetList, |
| | | exportFixedAsset, |
| | | getFixedAssetPage, |
| | | } from '#/api/erp/finance/fixed-asset'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | /** åºå®èµäº§å表 */ |
| | | defineOptions({ name: 'FinFixedAsset' }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportFixedAsset(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'åºå®èµäº§.xls', source: data }); |
| | | } |
| | | |
| | | /** æ°å¢åºå®èµäº§ */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** ç¼è¾åºå®èµäº§ */ |
| | | function handleEdit(row: FinFixedAssetApi.FixedAsset) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å é¤åºå®èµäº§ */ |
| | | async function handleDelete(ids: number[]) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting'), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await (ids.length === 1 |
| | | ? deleteFixedAsset(ids[0]!) |
| | | : deleteFixedAssetList(ids)); |
| | | checkedIds.value = []; |
| | | message.success($t('ui.actionMessage.deleteSuccess')); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** è®¡æææ§ */ |
| | | async function handleDepreciate(ids: number[]) { |
| | | const hideLoading = message.loading({ |
| | | content: 'è®¡æææ§ä¸...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | const result = await depreciateFixedAssetList(ids); |
| | | message.success( |
| | | `æ¬æ¬¡è®¡æ ${result.processedCount} 项èµäº§ï¼ææ§å计 ${result.totalMonthlyDepreciation} å
`, |
| | | ); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const checkedIds = ref<number[]>([]); |
| | | function handleRowCheckboxChange({ |
| | | records, |
| | | }: { |
| | | records: FinFixedAssetApi.FixedAsset[]; |
| | | }) { |
| | | checkedIds.value = records.map((item) => item.id!); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getFixedAssetPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<FinFixedAssetApi.FixedAsset>, |
| | | gridEvents: { |
| | | checkboxAll: handleRowCheckboxChange, |
| | | checkboxChange: handleRowCheckboxChange, |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="åºå®èµäº§å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['åºå®èµäº§']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['finance:fixed-asset:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: 'è®¡æææ§', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.AUDIT, |
| | | auth: ['finance:fixed-asset:depreciate'], |
| | | popConfirm: { |
| | | title: isEmpty(checkedIds) |
| | | ? 'æªå¾éèµäº§ï¼å°å¯¹å
¨é¨æªæ¥åºèµäº§è®¡æä¸ä¸ªæææ§ï¼ç¡®è®¤ç»§ç»åï¼' |
| | | : `确认对已å¾éç ${checkedIds.length} 项èµäº§è®¡æä¸ä¸ªæææ§åï¼`, |
| | | confirm: handleDepreciate.bind(null, checkedIds), |
| | | }, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['finance:fixed-asset:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: 'æ¹éå é¤', |
| | | type: 'primary', |
| | | danger: true, |
| | | disabled: isEmpty(checkedIds), |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:fixed-asset:delete'], |
| | | popConfirm: { |
| | | title: 'æ¯å¦å 餿é䏿°æ®ï¼', |
| | | confirm: handleDelete.bind(null, checkedIds), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['finance:fixed-asset:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'è®¡æææ§', |
| | | type: 'link', |
| | | icon: ACTION_ICON.AUDIT, |
| | | auth: ['finance:fixed-asset:depreciate'], |
| | | ifShow: () => row.status !== 4, |
| | | popConfirm: { |
| | | title: `确认对èµäº§ã${row.assetName}ã计æä¸ä¸ªæææ§åï¼`, |
| | | confirm: handleDepreciate.bind(null, [row.id!]), |
| | | }, |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:fixed-asset:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.assetName]), |
| | | confirm: handleDelete.bind(null, [row.id!]), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FinFixedAssetApi } from '#/api/erp/finance/fixed-asset'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { $t } from '@vben/locales'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createFixedAsset, |
| | | getFixedAsset, |
| | | updateFixedAsset, |
| | | } from '#/api/erp/finance/fixed-asset'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<FinFixedAssetApi.FixedAsset>(); |
| | | 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 FinFixedAssetApi.FixedAsset; |
| | | if (formData.value?.id) { |
| | | data.id = formData.value.id; |
| | | } |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateFixedAsset(data) |
| | | : createFixedAsset(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; |
| | | } |
| | | formData.value = undefined; |
| | | await formApi.resetForm(); |
| | | const data = modalApi.getData<FinFixedAssetApi.FixedAsset>(); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getFixedAsset(data.id); |
| | | await formApi.setValues(formData.value, false); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-1/2" :title="getTitle"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinIntangibleAssetApi } from '#/api/erp/finance/intangible-asset'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | import dayjs from 'dayjs'; |
| | | |
| | | /** èµäº§ç¶æï¼1å¨ç¨ 2已失æ 3å·²æéå® */ |
| | | export const INTANGIBLE_STATUS_IN_USE = 1; |
| | | export const INTANGIBLE_STATUS_INVALID = 2; |
| | | export const INTANGIBLE_STATUS_AMORTIZED = 3; |
| | | |
| | | /** å¯è®¡ææéçç¶æï¼ä»
ãå¨ç¨ãçèµäº§æéï¼å·²å¤±æ/å·²æå®ä¸å计æï¼ */ |
| | | export const AMORTIZABLE_INTANGIBLE_STATUS = [INTANGIBLE_STATUS_IN_USE]; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'assetCode', |
| | | label: 'èµäº§ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§ç¼ç ï¼å¦ WX2026100001', |
| | | allowClear: true, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'assetName', |
| | | label: 'èµäº§åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§åç§°', |
| | | allowClear: true, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'category', |
| | | label: 'èµäº§ç±»å«', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§ç±»å«ï¼å¦ 软件è使', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'certificateNo', |
| | | label: 'è¯ä¹¦ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è¯ä¹¦ç¼å·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'acquisitionDate', |
| | | label: 'å徿¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©å徿¥æ', |
| | | showTime: false, |
| | | valueFormat: 'YYYY-MM-DD', |
| | | format: 'YYYY-MM-DD', |
| | | class: 'w-full', |
| | | }, |
| | | rules: 'required', |
| | | defaultValue: dayjs().format('YYYY-MM-DD'), |
| | | }, |
| | | { |
| | | fieldName: 'validityDate', |
| | | label: 'æææè³', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ææææªæ¢æ¥æ', |
| | | showTime: false, |
| | | valueFormat: 'YYYY-MM-DD', |
| | | format: 'YYYY-MM-DD', |
| | | class: 'w-full', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'originalValue', |
| | | label: 'èµäº§åå¼', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | placeholder: '请è¾å
¥èµäº§åå¼', |
| | | min: 0, |
| | | precision: 2, |
| | | }, |
| | | rules: z.number().min(0).default(0), |
| | | }, |
| | | { |
| | | fieldName: 'amortizationPeriod', |
| | | label: 'æéå¹´éï¼å¹´ï¼', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | placeholder: '请è¾å
¥æéå¹´é', |
| | | min: 1, |
| | | precision: 0, |
| | | }, |
| | | defaultValue: 10, |
| | | rules: z.number().min(1).default(10), |
| | | }, |
| | | { |
| | | fieldName: 'residualRate', |
| | | label: 'æ®å¼çï¼%ï¼', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | placeholder: '请è¾å
¥æ®å¼ç', |
| | | min: 0, |
| | | max: 100, |
| | | precision: 2, |
| | | }, |
| | | defaultValue: 0, |
| | | rules: z.number().min(0).max(100).default(0), |
| | | help: 'ææéé¢ =ï¼åå¼ Ãï¼1 - æ®å¼çï¼ï¼Ã·ï¼æéå¹´é à 12ï¼', |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'èµäº§ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | options: getDictOptions( |
| | | DICT_TYPE.FINANCE_INTANGIBLE_ASSET_STATUS, |
| | | 'number', |
| | | ), |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | }, |
| | | defaultValue: INTANGIBLE_STATUS_IN_USE, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'description', |
| | | label: 'èµäº§æè¿°', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§æè¿°', |
| | | rows: 2, |
| | | }, |
| | | formItemClass: 'md:col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | formItemClass: 'md:col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'assetCode', |
| | | label: 'èµäº§ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§ç¼ç ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'assetName', |
| | | label: 'èµäº§åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§åç§°', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'category', |
| | | label: 'èµäº§ç±»å«', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥èµäº§ç±»å«', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'certificateNo', |
| | | label: 'è¯ä¹¦ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è¯ä¹¦ç¼å·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'acquisitionDate', |
| | | label: 'å徿¥æ', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'èµäº§ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getDictOptions( |
| | | DICT_TYPE.FINANCE_INTANGIBLE_ASSET_STATUS, |
| | | 'number', |
| | | ), |
| | | placeholder: 'è¯·éæ©èµäº§ç¶æ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<FinIntangibleAssetApi.IntangibleAsset>['columns'] { |
| | | return [ |
| | | { type: 'checkbox', width: 40 }, |
| | | { |
| | | field: 'assetCode', |
| | | title: 'èµäº§ç¼ç ', |
| | | minWidth: 150, |
| | | fixed: 'left', |
| | | }, |
| | | { |
| | | field: 'assetName', |
| | | title: 'èµäº§åç§°', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'category', |
| | | title: 'èµäº§ç±»å«', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'certificateNo', |
| | | title: 'è¯ä¹¦ç¼å·', |
| | | minWidth: 140, |
| | | showOverflow: 'tooltip', |
| | | }, |
| | | { |
| | | field: 'acquisitionDate', |
| | | title: 'å徿¥æ', |
| | | minWidth: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'validityDate', |
| | | title: 'æææè³', |
| | | minWidth: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'originalValue', |
| | | title: 'èµäº§åå¼', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'amortizationPeriod', |
| | | title: 'æéå¹´é', |
| | | minWidth: 100, |
| | | align: 'right', |
| | | }, |
| | | { |
| | | field: 'residualRate', |
| | | title: 'æ®å¼ç(%)', |
| | | minWidth: 110, |
| | | align: 'right', |
| | | }, |
| | | { |
| | | field: 'monthlyAmortization', |
| | | title: 'ææéé¢', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'accumulatedAmortization', |
| | | title: '累计æé', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'netValue', |
| | | title: 'åå¼', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'èµäº§ç¶æ', |
| | | minWidth: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.FINANCE_INTANGIBLE_ASSET_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | minWidth: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinIntangibleAssetApi } from '#/api/erp/finance/intangible-asset'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | amortizeIntangibleAssetList, |
| | | deleteIntangibleAsset, |
| | | deleteIntangibleAssetList, |
| | | exportIntangibleAsset, |
| | | getIntangibleAssetPage, |
| | | } from '#/api/erp/finance/intangible-asset'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { INTANGIBLE_STATUS_IN_USE, useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | /** æ å½¢èµäº§å表 */ |
| | | defineOptions({ name: 'FinIntangibleAsset' }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportIntangibleAsset(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'æ å½¢èµäº§.xls', source: data }); |
| | | } |
| | | |
| | | /** æ°å¢æ å½¢èµäº§ */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** ç¼è¾æ å½¢èµäº§ */ |
| | | function handleEdit(row: FinIntangibleAssetApi.IntangibleAsset) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å 餿 å½¢èµäº§ */ |
| | | async function handleDelete(ids: number[]) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting'), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await (ids.length === 1 |
| | | ? deleteIntangibleAsset(ids[0]!) |
| | | : deleteIntangibleAssetList(ids)); |
| | | checkedIds.value = []; |
| | | message.success($t('ui.actionMessage.deleteSuccess')); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** 计ææé */ |
| | | async function handleAmortize(ids: number[]) { |
| | | const hideLoading = message.loading({ |
| | | content: '计ææéä¸...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | const result = await amortizeIntangibleAssetList(ids); |
| | | message.success( |
| | | `æ¬æ¬¡è®¡æ ${result.processedCount} 项èµäº§ï¼æéå计 ${result.totalMonthlyAmortization} å
`, |
| | | ); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const checkedIds = ref<number[]>([]); |
| | | function handleRowCheckboxChange({ |
| | | records, |
| | | }: { |
| | | records: FinIntangibleAssetApi.IntangibleAsset[]; |
| | | }) { |
| | | checkedIds.value = records.map((item) => item.id!); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getIntangibleAssetPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<FinIntangibleAssetApi.IntangibleAsset>, |
| | | gridEvents: { |
| | | checkboxAll: handleRowCheckboxChange, |
| | | checkboxChange: handleRowCheckboxChange, |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="æ å½¢èµäº§å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['æ å½¢èµäº§']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['finance:intangible-asset:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: '计ææé', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.AUDIT, |
| | | auth: ['finance:intangible-asset:amortize'], |
| | | popConfirm: { |
| | | title: isEmpty(checkedIds) |
| | | ? 'æªå¾éèµäº§ï¼å°å¯¹å
¨é¨ãå¨ç¨ã䏿ªæå®çæ å½¢èµäº§è®¡æä¸ä¸ªææéï¼ç¡®è®¤ç»§ç»åï¼' |
| | | : `确认对已å¾éç ${checkedIds.length} 项èµäº§è®¡æä¸ä¸ªææéåï¼`, |
| | | confirm: handleAmortize.bind(null, checkedIds), |
| | | }, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['finance:intangible-asset:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: 'æ¹éå é¤', |
| | | type: 'primary', |
| | | danger: true, |
| | | disabled: isEmpty(checkedIds), |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:intangible-asset:delete'], |
| | | popConfirm: { |
| | | title: 'æ¯å¦å 餿é䏿°æ®ï¼', |
| | | confirm: handleDelete.bind(null, checkedIds), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['finance:intangible-asset:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: '计ææé', |
| | | type: 'link', |
| | | icon: ACTION_ICON.AUDIT, |
| | | auth: ['finance:intangible-asset:amortize'], |
| | | ifShow: () => row.status === INTANGIBLE_STATUS_IN_USE, |
| | | popConfirm: { |
| | | title: `确认对èµäº§ã${row.assetName}ã计æä¸ä¸ªææéåï¼`, |
| | | confirm: handleAmortize.bind(null, [row.id!]), |
| | | }, |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:intangible-asset:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.assetName]), |
| | | confirm: handleDelete.bind(null, [row.id!]), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FinIntangibleAssetApi } from '#/api/erp/finance/intangible-asset'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { $t } from '@vben/locales'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createIntangibleAsset, |
| | | getIntangibleAsset, |
| | | updateIntangibleAsset, |
| | | } from '#/api/erp/finance/intangible-asset'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<FinIntangibleAssetApi.IntangibleAsset>(); |
| | | 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 FinIntangibleAssetApi.IntangibleAsset; |
| | | if (formData.value?.id) { |
| | | data.id = formData.value.id; |
| | | } |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateIntangibleAsset(data) |
| | | : createIntangibleAsset(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; |
| | | } |
| | | formData.value = undefined; |
| | | await formApi.resetForm(); |
| | | const data = modalApi.getData<FinIntangibleAssetApi.IntangibleAsset>(); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getIntangibleAsset(data.id); |
| | | await formApi.setValues(formData.value, false); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-1/2" :title="getTitle"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinLedgerApi } from '#/api/erp/finance/ledger'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getAccountSubjectList } from '#/api/erp/finance/subject'; |
| | | |
| | | import dayjs from 'dayjs'; |
| | | |
| | | /** è¡ç±»å -> å±ç¤ºææ¡ */ |
| | | export const LEDGER_ROW_TYPE_LABEL: Record<string, string> = { |
| | | opening: 'æåä½é¢', |
| | | entry: 'æç»', |
| | | monthly_total: 'æ¬æå计', |
| | | yearly_total: 'æ¬å¹´ç´¯è®¡', |
| | | }; |
| | | |
| | | /** è¡ç±»å -> Tag é¢è² */ |
| | | export const LEDGER_ROW_TYPE_COLOR: Record<string, string> = { |
| | | opening: 'blue', |
| | | entry: 'default', |
| | | monthly_total: 'orange', |
| | | yearly_total: 'purple', |
| | | }; |
| | | |
| | | /** |
| | | * 账页æ¥è¯¢è¡¨å |
| | | * |
| | | * 说æï¼è´¦é¡µåªç»è®¡ãå·²è¿è´¦ãçåè¯ï¼æªè¿è´¦ãå·²ä½åºçåè¯ä¸è®¡å
¥ã |
| | | */ |
| | | export function useLedgerFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'subjectCode', |
| | | label: 'ä¼è®¡ç§ç®', |
| | | component: 'ApiTreeSelect', |
| | | componentProps: { |
| | | api: getAccountSubjectList, |
| | | labelField: 'subjectName', |
| | | valueField: 'subjectCode', |
| | | childrenField: 'children', |
| | | placeholder: 'è¯·éæ©ä¼è®¡ç§ç®', |
| | | treeDefaultExpandAll: true, |
| | | showSearch: true, |
| | | treeNodeFilterProp: 'subjectName', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'startMonth', |
| | | label: 'èµ·å§æä»½', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | picker: 'month', |
| | | allowClear: true, |
| | | valueFormat: 'YYYY-MM', |
| | | format: 'YYYY-MM', |
| | | style: { width: '100%' }, |
| | | placeholder: 'éæ©èµ·å§æä»½', |
| | | }, |
| | | defaultValue: dayjs().startOf('year').format('YYYY-MM'), |
| | | }, |
| | | { |
| | | fieldName: 'endMonth', |
| | | label: 'ç»ææä»½', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | picker: 'month', |
| | | allowClear: true, |
| | | valueFormat: 'YYYY-MM', |
| | | format: 'YYYY-MM', |
| | | style: { width: '100%' }, |
| | | placeholder: 'éæ©ç»ææä»½', |
| | | }, |
| | | defaultValue: dayjs().format('YYYY-MM'), |
| | | }, |
| | | { |
| | | fieldName: 'includeChildren', |
| | | label: 'å
å«ä¸çº§ç§ç®', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | defaultValue: false, |
| | | rules: z.boolean().default(false).optional(), |
| | | help: 'å¼å¯åï¼æéç§ç®åå
¶å
¨é¨ä¸çº§ç§ç®çåçé¢ä¸ä½é¢åå¹¶ç»è®¡', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** æ»è´¦åï¼æå + éæå计 + æ¬å¹´ç´¯è®¡ */ |
| | | export function useGeneralColumns(): VxeTableGridOptions<FinLedgerApi.LedgerRow>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'rowType', |
| | | title: 'è¡ç±»å', |
| | | width: 110, |
| | | fixed: 'left', |
| | | slots: { default: 'rowType' }, |
| | | }, |
| | | { |
| | | field: 'period', |
| | | title: 'æé´', |
| | | width: 110, |
| | | }, |
| | | { |
| | | field: 'subjectCode', |
| | | title: 'ç§ç®ç¼ç ', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'subjectName', |
| | | title: 'ç§ç®åç§°', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'debit', |
| | | title: 'åæ¹åçé¢', |
| | | minWidth: 140, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'credit', |
| | | title: 'è´·æ¹åçé¢', |
| | | minWidth: 140, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'direction', |
| | | title: 'æ¹å', |
| | | width: 80, |
| | | align: 'center', |
| | | slots: { default: 'direction' }, |
| | | }, |
| | | { |
| | | field: 'balance', |
| | | title: 'ææ«ä½é¢', |
| | | minWidth: 140, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** æç»è´¦åï¼æå + éç¬åå½ + ç»è½¬å计 */ |
| | | export function useDetailColumns(): VxeTableGridOptions<FinLedgerApi.LedgerRow>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'rowType', |
| | | title: 'è¡ç±»å', |
| | | width: 110, |
| | | fixed: 'left', |
| | | slots: { default: 'rowType' }, |
| | | }, |
| | | { |
| | | field: 'date', |
| | | title: 'æ¥æ', |
| | | width: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'voucherNo', |
| | | title: 'åè¯åå·', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'subjectCode', |
| | | title: 'ç§ç®ç¼ç ', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'summary', |
| | | title: 'æè¦', |
| | | minWidth: 200, |
| | | showOverflow: 'tooltip', |
| | | }, |
| | | { |
| | | field: 'debit', |
| | | title: 'åæ¹éé¢', |
| | | minWidth: 140, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'credit', |
| | | title: 'è´·æ¹éé¢', |
| | | minWidth: 140, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'direction', |
| | | title: 'æ¹å', |
| | | width: 80, |
| | | align: 'center', |
| | | slots: { default: 'direction' }, |
| | | }, |
| | | { |
| | | field: 'balance', |
| | | title: 'ä½é¢', |
| | | minWidth: 140, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ä½é¢æ¹ååå
¸ç±»åï¼ä¾é¡µé¢æ¸²ææ¹åæ ç¾ä½¿ç¨ */ |
| | | export const LEDGER_DIRECTION_DICT = DICT_TYPE.FINANCE_BALANCE_DIRECTION; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinLedgerApi } from '#/api/erp/finance/ledger'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { exportDetailLedger, getDetailLedger } from '#/api/erp/finance/ledger'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { |
| | | LEDGER_DIRECTION_DICT, |
| | | LEDGER_ROW_TYPE_COLOR, |
| | | LEDGER_ROW_TYPE_LABEL, |
| | | useDetailColumns, |
| | | useLedgerFormSchema, |
| | | } from '../data'; |
| | | |
| | | /** æç»è´¦ */ |
| | | defineOptions({ name: 'FinDetailLedger' }); |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportDetailLedger({ |
| | | ...((await gridApi.formApi.getValues()) as FinLedgerApi.LedgerParams), |
| | | }); |
| | | downloadFileFromBlobPart({ fileName: 'æç»è´¦.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useLedgerFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useDetailColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async (_params, formValues) => { |
| | | // ç§ç®ä¸ºå¿
å¡«ï¼æªéç§ç®æ¶ç´æ¥è¿å空表ï¼é¿å
æ æä¹çèæ¥è¯¢ |
| | | if (!formValues.subjectCode) { |
| | | return []; |
| | | } |
| | | return await getDetailLedger(formValues as FinLedgerApi.LedgerParams); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<FinLedgerApi.LedgerRow>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <Grid table-title="æç»è´¦ï¼ä»
ç»è®¡å·²è¿è´¦åè¯ï¼æåè¯éç¬ç»è½¬ä½é¢ï¼"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['finance:ledger:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | |
| | | <template #rowType="{ row }"> |
| | | <Tag :color="LEDGER_ROW_TYPE_COLOR[row.rowType] ?? 'default'"> |
| | | {{ LEDGER_ROW_TYPE_LABEL[row.rowType] ?? row.rowType }} |
| | | </Tag> |
| | | </template> |
| | | |
| | | <template #direction="{ row }"> |
| | | <DictTag |
| | | v-if="row.direction" |
| | | :type="LEDGER_DIRECTION_DICT" |
| | | :value="row.direction" |
| | | /> |
| | | <span v-else>--</span> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinLedgerApi } from '#/api/erp/finance/ledger'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | exportGeneralLedger, |
| | | getGeneralLedger, |
| | | } from '#/api/erp/finance/ledger'; |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { |
| | | LEDGER_DIRECTION_DICT, |
| | | LEDGER_ROW_TYPE_COLOR, |
| | | LEDGER_ROW_TYPE_LABEL, |
| | | useGeneralColumns, |
| | | useLedgerFormSchema, |
| | | } from '../data'; |
| | | |
| | | /** æ»è´¦ */ |
| | | defineOptions({ name: 'FinGeneralLedger' }); |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportGeneralLedger({ |
| | | ...((await gridApi.formApi.getValues()) as FinLedgerApi.LedgerParams), |
| | | }); |
| | | downloadFileFromBlobPart({ fileName: 'æ»è´¦.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useLedgerFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGeneralColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async (_params, formValues) => { |
| | | // ç§ç®ä¸ºå¿
å¡«ï¼æªéç§ç®æ¶ç´æ¥è¿å空表ï¼é¿å
æ æä¹çèæ¥è¯¢ |
| | | if (!formValues.subjectCode) { |
| | | return []; |
| | | } |
| | | return await getGeneralLedger(formValues as FinLedgerApi.LedgerParams); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<FinLedgerApi.LedgerRow>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <Grid table-title="æ»è´¦ï¼ä»
ç»è®¡å·²è¿è´¦åè¯ï¼"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['finance:ledger:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | |
| | | <template #rowType="{ row }"> |
| | | <Tag :color="LEDGER_ROW_TYPE_COLOR[row.rowType] ?? 'default'"> |
| | | {{ LEDGER_ROW_TYPE_LABEL[row.rowType] ?? row.rowType }} |
| | | </Tag> |
| | | </template> |
| | | |
| | | <template #direction="{ row }"> |
| | | <DictTag |
| | | v-if="row.direction" |
| | | :type="LEDGER_DIRECTION_DICT" |
| | | :value="row.direction" |
| | | /> |
| | | <span v-else>--</span> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| | |
| | | v-model:value="row.receiptPrice" |
| | | :precision="2" |
| | | :disabled="disabled" |
| | | :formatter="erpPriceInputFormatter" |
| | | placeholder="请è¾å
¥æ¬æ¬¡æ¶æ¬¾" |
| | | @change="handleRowChange(row)" |
| | | /> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FinStatisticsApi } from '#/api/erp/finance/statistics'; |
| | | |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | import { erpNumberFormatter } from '@vben/utils'; |
| | | |
| | | import { Button, Card, DatePicker, Col, Row, Spin, Tag } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | getStatisticsOverview, |
| | | getStatisticsTrend, |
| | | } from '#/api/erp/finance/statistics'; |
| | | |
| | | import TrendChart from './modules/trend-chart.vue'; |
| | | |
| | | /** è´¢å¡æ¥è¡¨ / ä¼è®¡æ ¸ç®çæ¿ */ |
| | | defineOptions({ name: 'FinStatistics' }); |
| | | |
| | | const loading = ref(false); |
| | | const overview = ref<FinStatisticsApi.StatisticsOverview>(); |
| | | const trendList = ref<FinStatisticsApi.StatisticsTrend[]>([]); |
| | | |
| | | /** æ¥è¯¢æ¡ä»¶ï¼é»è®¤æ¬å¹´ 1 æè³å½åæ */ |
| | | const startMonth = ref<string>(); |
| | | const endMonth = ref<string>(); |
| | | |
| | | /** 顶鍿¶æ¯æ¦è§å¡ç */ |
| | | const incomeCards = computed(() => [ |
| | | { |
| | | title: 'æ¶æ¬¾éé¢', |
| | | value: overview.value?.receiptPrice ?? 0, |
| | | color: '#18a058', |
| | | tip: 'ERP å·²å®¡æ ¸æ¶æ¬¾åå计', |
| | | }, |
| | | { |
| | | title: '仿¬¾éé¢', |
| | | value: overview.value?.paymentPrice ?? 0, |
| | | color: '#d03050', |
| | | tip: 'ERP å·²å®¡æ ¸ä»æ¬¾åå计', |
| | | }, |
| | | { |
| | | title: 'å婿¶¦', |
| | | value: overview.value?.netProfit ?? 0, |
| | | color: '#2f7df6', |
| | | tip: 'æ¶æ¬¾éé¢ - 仿¬¾éé¢', |
| | | }, |
| | | { |
| | | title: 'éå®è®¢åéé¢', |
| | | value: overview.value?.saleOrderPrice ?? 0, |
| | | color: '#7c3aed', |
| | | tip: 'ERP å·²å®¡æ ¸éå®è®¢åå计', |
| | | }, |
| | | { |
| | | title: 'éè´è®¢åéé¢', |
| | | value: overview.value?.purchaseOrderPrice ?? 0, |
| | | color: '#f59e0b', |
| | | tip: 'ERP å·²å®¡æ ¸éè´è®¢åå计', |
| | | }, |
| | | ]); |
| | | |
| | | /** èµäº§æ¦è§å¡ç */ |
| | | const assetCards = computed(() => [ |
| | | { |
| | | title: 'åºå®èµäº§åå¼', |
| | | value: overview.value?.fixedAssetNetValue ?? 0, |
| | | sub: `åå¼ ${formatAmount(overview.value?.fixedAssetOriginalValue)} / ç´¯è®¡ææ§ ${formatAmount(overview.value?.fixedAssetAccumulatedDepreciation)}`, |
| | | color: '#2f7df6', |
| | | }, |
| | | { |
| | | title: 'æ å½¢èµäº§åå¼', |
| | | value: overview.value?.intangibleAssetNetValue ?? 0, |
| | | sub: `åå¼ ${formatAmount(overview.value?.intangibleAssetOriginalValue)} / 累计æé ${formatAmount(overview.value?.intangibleAssetAccumulatedAmortization)}`, |
| | | color: '#18a058', |
| | | }, |
| | | { |
| | | title: 'èµäº§åå¼å计', |
| | | value: overview.value?.totalAssetNetValue ?? 0, |
| | | sub: 'åºå®èµäº§åå¼ + æ å½¢èµäº§åå¼', |
| | | color: '#7c3aed', |
| | | }, |
| | | ]); |
| | | |
| | | /** åè¯æ¦è§ */ |
| | | const voucherCards = computed(() => [ |
| | | { |
| | | title: 'ä¼è®¡ç§ç®', |
| | | value: overview.value?.subjectCount ?? 0, |
| | | color: '#2f7df6', |
| | | }, |
| | | { |
| | | title: 'åè¯æ»æ°', |
| | | value: overview.value?.voucherCount ?? 0, |
| | | color: '#7c3aed', |
| | | }, |
| | | { |
| | | title: 'æªè¿è´¦', |
| | | value: overview.value?.voucherUnpostedCount ?? 0, |
| | | color: '#f59e0b', |
| | | }, |
| | | { |
| | | title: 'å·²è¿è´¦', |
| | | value: overview.value?.voucherPostedCount ?? 0, |
| | | color: '#18a058', |
| | | }, |
| | | { |
| | | title: 'å·²ä½åº', |
| | | value: overview.value?.voucherCancelledCount ?? 0, |
| | | color: '#d03050', |
| | | }, |
| | | ]); |
| | | |
| | | /** ç»è®¡æé´å±ç¤ºææ¡ */ |
| | | const periodText = computed(() => { |
| | | if (!overview.value) { |
| | | return ''; |
| | | } |
| | | return `${overview.value.startDate} ~ ${overview.value.endDate}`; |
| | | }); |
| | | |
| | | function formatAmount(value?: number) { |
| | | return erpNumberFormatter(value ?? 0, 2); |
| | | } |
| | | |
| | | /** å è½½çæ¿æ°æ® */ |
| | | async function loadData() { |
| | | loading.value = true; |
| | | try { |
| | | const params: FinStatisticsApi.StatisticsParams = { |
| | | startMonth: startMonth.value, |
| | | endMonth: endMonth.value, |
| | | }; |
| | | const [overviewData, trendData] = await Promise.all([ |
| | | getStatisticsOverview(params), |
| | | getStatisticsTrend(params), |
| | | ]); |
| | | overview.value = overviewData; |
| | | trendList.value = trendData ?? []; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | /** é置为é»è®¤æé´ï¼æ¬å¹´ 1 æè³å½åæï¼ */ |
| | | function handleReset() { |
| | | startMonth.value = undefined; |
| | | endMonth.value = undefined; |
| | | loadData(); |
| | | } |
| | | |
| | | onMounted(loadData); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page> |
| | | <Spin :spinning="loading"> |
| | | <div class="flex flex-col gap-4"> |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <Card :body-style="{ padding: '12px 16px' }" class="shadow-sm"> |
| | | <div class="flex flex-wrap items-center gap-3"> |
| | | <span class="font-medium">ç»è®¡æé´ï¼</span> |
| | | <DatePicker |
| | | v-model:value="startMonth" |
| | | picker="month" |
| | | format="YYYY-MM" |
| | | value-format="YYYY-MM" |
| | | placeholder="èµ·å§æä»½" |
| | | style="width: 160px" |
| | | /> |
| | | <span>~</span> |
| | | <DatePicker |
| | | v-model:value="endMonth" |
| | | picker="month" |
| | | format="YYYY-MM" |
| | | value-format="YYYY-MM" |
| | | placeholder="ç»ææä»½" |
| | | style="width: 160px" |
| | | /> |
| | | <Button type="primary" @click="loadData">æ¥è¯¢</Button> |
| | | <Button @click="handleReset">éç½®</Button> |
| | | <Tag color="blue" class="ml-auto"> |
| | | æ°æ®æ¥æºï¼ERP å·²å®¡æ ¸åæ® + æ¬æ¨¡åå·²è¿è´¦åè¯ / èµäº§ |
| | | </Tag> |
| | | </div> |
| | | </Card> |
| | | |
| | | <!-- æ¶æ¯æ¦è§ --> |
| | | <div> |
| | | <div class="mb-2 text-muted-foreground"> |
| | | æ¶æ¯ä¸è´éæ¦è§ |
| | | <span v-if="periodText">ï¼{{ periodText }}ï¼</span> |
| | | </div> |
| | | <div |
| | | class="grid grid-cols-5 gap-4 max-xl:grid-cols-3 max-sm:grid-cols-1" |
| | | > |
| | | <Card |
| | | v-for="item in incomeCards" |
| | | :key="item.title" |
| | | :body-style="{ padding: '12px 16px 16px' }" |
| | | class="h-full shadow-sm" |
| | | :style="{ borderTop: `3px solid ${item.color}` }" |
| | | > |
| | | <div class="mb-2 text-muted-foreground">{{ item.title }}</div> |
| | | <div |
| | | class="text-2xl font-bold" |
| | | :style="{ color: item.color }" |
| | | > |
| | | {{ formatAmount(item.value) }} |
| | | </div> |
| | | <div class="mt-2 text-xs text-muted-foreground"> |
| | | {{ item.tip }} |
| | | </div> |
| | | </Card> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- èµäº§ä¸åè¯æ¦è§ --> |
| | | <Row :gutter="16"> |
| | | <Col :lg="12" :md="24" :sm="24" :xs="24"> |
| | | <div class="mb-2 text-muted-foreground">èµäº§æ¦è§ï¼æ¶ç¹æ°ï¼</div> |
| | | <div class="grid grid-cols-3 gap-4 max-sm:grid-cols-1"> |
| | | <Card |
| | | v-for="item in assetCards" |
| | | :key="item.title" |
| | | :body-style="{ padding: '12px 16px 16px' }" |
| | | class="h-full shadow-sm" |
| | | :style="{ borderTop: `3px solid ${item.color}` }" |
| | | > |
| | | <div class="mb-2 text-muted-foreground">{{ item.title }}</div> |
| | | <div |
| | | class="text-2xl font-bold" |
| | | :style="{ color: item.color }" |
| | | > |
| | | {{ formatAmount(item.value) }} |
| | | </div> |
| | | <div class="mt-2 text-xs text-muted-foreground"> |
| | | {{ item.sub }} |
| | | </div> |
| | | </Card> |
| | | </div> |
| | | </Col> |
| | | <Col :lg="12" :md="24" :sm="24" :xs="24"> |
| | | <div class="mb-2 text-muted-foreground">åè¯æ¦è§</div> |
| | | <div class="grid grid-cols-5 gap-4 max-sm:grid-cols-2"> |
| | | <Card |
| | | v-for="item in voucherCards" |
| | | :key="item.title" |
| | | :body-style="{ padding: '12px 16px 16px' }" |
| | | class="h-full shadow-sm" |
| | | :style="{ borderTop: `3px solid ${item.color}` }" |
| | | > |
| | | <div class="mb-2 truncate text-muted-foreground"> |
| | | {{ item.title }} |
| | | </div> |
| | | <div |
| | | class="text-2xl font-bold" |
| | | :style="{ color: item.color }" |
| | | > |
| | | {{ item.value }} |
| | | </div> |
| | | </Card> |
| | | </div> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <!-- æåº¦è¶å¿ --> |
| | | <TrendChart :list="trendList" :loading="loading" /> |
| | | </div> |
| | | </Spin> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { EChartsOption, EchartsUIType } from '@vben/plugins/echarts'; |
| | | import type { FinStatisticsApi } from '#/api/erp/finance/statistics'; |
| | | |
| | | import { ref, watch } from 'vue'; |
| | | |
| | | import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; |
| | | |
| | | import { Card } from 'ant-design-vue'; |
| | | |
| | | interface Props { |
| | | list?: FinStatisticsApi.StatisticsTrend[]; |
| | | loading?: boolean; |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | list: () => [], |
| | | loading: false, |
| | | }); |
| | | |
| | | const chartRef = ref<EchartsUIType>(); |
| | | const { renderEcharts } = useEcharts(chartRef); |
| | | |
| | | /** æ±ç¶ç³»åå
Œ
±é
ç½®ï¼type é为åé¢éï¼æ
ç¨ as constï¼ */ |
| | | const BAR_SERIES = { type: 'bar', barMaxWidth: 24 } as const; |
| | | /** æçº¿ç³»åå
Œ
±é
ç½® */ |
| | | const LINE_SERIES = { type: 'line', smooth: true, symbolSize: 6 } as const; |
| | | |
| | | /** éé¢æçº¿/æ±ç¶å¾é
ç½®ï¼series å¨ watch 䏿æä»½æ°æ®å¡«å
ï¼ */ |
| | | const baseChartOptions: EChartsOption = { |
| | | grid: { |
| | | left: 20, |
| | | right: 20, |
| | | bottom: 20, |
| | | top: 60, |
| | | containLabel: true, |
| | | }, |
| | | legend: { |
| | | top: 15, |
| | | }, |
| | | toolbox: { |
| | | feature: { |
| | | saveAsImage: { show: true, name: 'æ¶æ¯è¶å¿' }, |
| | | }, |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' }, |
| | | padding: [5, 10], |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | data: [], |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisTick: { show: false }, |
| | | }, |
| | | }; |
| | | |
| | | watch( |
| | | () => props.list, |
| | | (list) => { |
| | | if (!list) { |
| | | return; |
| | | } |
| | | renderEcharts({ |
| | | ...baseChartOptions, |
| | | xAxis: { |
| | | ...baseChartOptions.xAxis, |
| | | data: list.map((item) => item.month), |
| | | }, |
| | | series: [ |
| | | { |
| | | ...BAR_SERIES, |
| | | name: 'æ¶æ¬¾éé¢', |
| | | data: list.map((item) => item.receiptPrice ?? 0), |
| | | }, |
| | | { |
| | | ...BAR_SERIES, |
| | | name: '仿¬¾éé¢', |
| | | data: list.map((item) => item.paymentPrice ?? 0), |
| | | }, |
| | | { |
| | | ...LINE_SERIES, |
| | | name: 'å婿¶¦', |
| | | data: list.map((item) => item.netProfit ?? 0), |
| | | }, |
| | | ], |
| | | }); |
| | | }, |
| | | { immediate: true, deep: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Card title="æåº¦æ¶æ¯è¶å¿" class="h-full shadow-sm" :loading="loading"> |
| | | <EchartsUI ref="chartRef" height="360px" /> |
| | | </Card> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinAccountSubjectApi } from '#/api/erp/finance/subject'; |
| | | |
| | | import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | import { handleTree } from '@vben/utils'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getAccountSubjectList } from '#/api/erp/finance/subject'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'parentId', |
| | | label: 'ä¸çº§ç§ç®', |
| | | component: 'ApiTreeSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: async () => handleTree(await getAccountSubjectList()), |
| | | labelField: 'subjectName', |
| | | valueField: 'id', |
| | | childrenField: 'children', |
| | | placeholder: 'ä¸éæ©æ¶å建为ä¸çº§ç§ç®', |
| | | treeDefaultExpandAll: true, |
| | | }, |
| | | help: 'åªææ«çº§ç§ç®å¯ä»¥è¢«åè¯å¼ç¨ï¼å·²åå¨ä¸çº§ç§ç®æ¶ä¸å¯åæ¹ä¸ºä»ç§ç®çä¸çº§', |
| | | rules: z.number().optional(), |
| | | }, |
| | | { |
| | | fieldName: 'subjectCode', |
| | | label: 'ç§ç®ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç§ç®ç¼ç ï¼å¦ 1001', |
| | | allowClear: true, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'subjectName', |
| | | label: 'ç§ç®åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç§ç®åç§°ï¼å¦ åºåç°é', |
| | | allowClear: true, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'subjectType', |
| | | label: 'ç§ç®ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.FINANCE_SUBJECT_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©ç§ç®ç±»å', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'balanceDirection', |
| | | label: 'ä½é¢æ¹å', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.FINANCE_BALANCE_DIRECTION, 'number'), |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | buttonStyle: 'solid', |
| | | optionType: 'button', |
| | | }, |
| | | rules: z.number().default(CommonStatusEnum.ENABLE), |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | formItemClass: 'md:col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'subjectCode', |
| | | label: 'ç§ç®ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç§ç®ç¼ç ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'subjectName', |
| | | label: 'ç§ç®åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç§ç®åç§°', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'subjectType', |
| | | label: 'ç§ç®ç±»å', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.FINANCE_SUBJECT_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©ç§ç®ç±»å', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<FinAccountSubjectApi.AccountSubject>['columns'] { |
| | | return [ |
| | | { type: 'checkbox', width: 40 }, |
| | | { |
| | | field: 'subjectCode', |
| | | title: 'ç§ç®ç¼ç ', |
| | | minWidth: 140, |
| | | fixed: 'left', |
| | | treeNode: true, |
| | | }, |
| | | { |
| | | field: 'subjectName', |
| | | title: 'ç§ç®åç§°', |
| | | minWidth: 180, |
| | | }, |
| | | { |
| | | field: 'subjectType', |
| | | title: 'ç§ç®ç±»å', |
| | | minWidth: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.FINANCE_SUBJECT_TYPE }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'balanceDirection', |
| | | title: 'ä½é¢æ¹å', |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.FINANCE_BALANCE_DIRECTION }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.COMMON_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | title: '夿³¨', |
| | | minWidth: 150, |
| | | showOverflow: 'tooltip', |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | minWidth: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinAccountSubjectApi } from '#/api/erp/finance/subject'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { confirm, Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart, handleTree, isEmpty } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteAccountSubject, |
| | | exportAccountSubject, |
| | | getAccountSubjectPage, |
| | | } from '#/api/erp/finance/subject'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | /** è´¢å¡ä¼è®¡ç§ç®å表 */ |
| | | defineOptions({ name: 'FinAccountSubject' }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** 忢æ å½¢å±å¼/æ¶ç¼©ç¶æ */ |
| | | const isExpanded = ref(true); |
| | | function handleExpand() { |
| | | isExpanded.value = !isExpanded.value; |
| | | gridApi.grid.setAllTreeExpand(isExpanded.value); |
| | | } |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportAccountSubject(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'ä¼è®¡ç§ç®.xls', source: data }); |
| | | } |
| | | |
| | | /** å建ä¼è®¡ç§ç® */ |
| | | function handleCreate() { |
| | | formModalApi.setData({ parentId: undefined }).open(); |
| | | } |
| | | |
| | | /** æ°å¢ä¸çº§ç§ç® */ |
| | | function handleAppend(row: FinAccountSubjectApi.AccountSubject) { |
| | | formModalApi.setData({ parentId: row.id }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾ä¼è®¡ç§ç® */ |
| | | function handleEdit(row: FinAccountSubjectApi.AccountSubject) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å é¤ä¼è®¡ç§ç® */ |
| | | async function handleDelete(row: FinAccountSubjectApi.AccountSubject) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.subjectName]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteAccountSubject([row.id!]); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.subjectName])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¹éå é¤ä¼è®¡ç§ç® */ |
| | | async function handleDeleteBatch() { |
| | | await confirm($t('ui.actionMessage.deleteBatchConfirm')); |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deletingBatch'), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteAccountSubject(checkedIds.value); |
| | | checkedIds.value = []; |
| | | message.success($t('ui.actionMessage.deleteSuccess')); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const checkedIds = ref<number[]>([]); |
| | | function handleRowCheckboxChange({ |
| | | records, |
| | | }: { |
| | | records: FinAccountSubjectApi.AccountSubject[]; |
| | | }) { |
| | | checkedIds.value = records.map((item) => item.id!); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async (_params, formValues) => { |
| | | // 䏿¬¡æ§æåå
¨é¨å½ä¸çç§ç®ï¼åå¨å
åéç»è£
为æ ï¼å页对æ å½¢å±ç¤ºæ²¡ææä¹ |
| | | const page = await getAccountSubjectPage({ |
| | | pageNo: 1, |
| | | pageSize: -1, |
| | | ...formValues, |
| | | }); |
| | | return handleTree(page.list); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | treeConfig: { |
| | | parentField: 'parentId', |
| | | rowField: 'id', |
| | | transform: true, |
| | | expandAll: true, |
| | | reserve: true, |
| | | }, |
| | | } as VxeTableGridOptions<FinAccountSubjectApi.AccountSubject>, |
| | | gridEvents: { |
| | | checkboxAll: handleRowCheckboxChange, |
| | | checkboxChange: handleRowCheckboxChange, |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="ä¼è®¡ç§ç®å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ä¼è®¡ç§ç®']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['finance:account-subject:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['finance:account-subject:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: isExpanded ? 'æ¶ç¼©' : 'å±å¼', |
| | | type: 'primary', |
| | | onClick: handleExpand, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.deleteBatch'), |
| | | type: 'primary', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:account-subject:delete'], |
| | | disabled: isEmpty(checkedIds), |
| | | onClick: handleDeleteBatch, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'æ°å¢ä¸çº§', |
| | | type: 'link', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['finance:account-subject:create'], |
| | | onClick: handleAppend.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['finance:account-subject:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:account-subject:delete'], |
| | | disabled: !!(row.children && row.children.length > 0), |
| | | popConfirm: { |
| | | disabled: !!(row.children && row.children.length > 0), |
| | | title: `确认è¦å é¤ç§ç®ã${row.subjectCode} ${row.subjectName}ãåï¼åå¨ä¸çº§ç§ç®æå·²è¢«åè¯å¼ç¨æ¶æ æ³å é¤ã`, |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FinAccountSubjectApi } from '#/api/erp/finance/subject'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createAccountSubject, |
| | | getAccountSubject, |
| | | updateAccountSubject, |
| | | } from '#/api/erp/finance/subject'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | type SubjectFormModalData = Partial<FinAccountSubjectApi.AccountSubject>; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<FinAccountSubjectApi.AccountSubject>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['ä¼è®¡ç§ç®']) |
| | | : $t('ui.actionTitle.create', ['ä¼è®¡ç§ç®']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | labelWidth: 100, |
| | | }, |
| | | 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 FinAccountSubjectApi.AccountSubject; |
| | | if (formData.value?.id) { |
| | | data.id = formData.value.id; |
| | | } |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateAccountSubject(data) |
| | | : createAccountSubject(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; |
| | | } |
| | | formData.value = undefined; |
| | | await formApi.resetForm(); |
| | | const data = modalApi.getData<SubjectFormModalData>(); |
| | | if (!data) { |
| | | return; |
| | | } |
| | | // ç¼è¾ï¼å 载详æ
ï¼æ°å¢ä¸çº§ï¼ä»
é¢å¡«ä¸çº§ç§ç® |
| | | if (data.id) { |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getAccountSubject(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | } else if (data.parentId) { |
| | | await formApi.setValues({ parentId: data.parentId }); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-2/5" :title="getTitle"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinVoucherApi } from '#/api/erp/finance/voucher'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | import dayjs from 'dayjs'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'edit'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表åï¼åå½ç± entry-table åç¬ç»´æ¤ï¼ */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | component: 'Input', |
| | | fieldName: 'id', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'voucherNo', |
| | | label: 'åè¯åå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: 'ç空èªå¨çæ', |
| | | allowClear: true, |
| | | }, |
| | | help: 'ç空æ¶ç±ç³»ç»æãè®° + å¹´æ + 4 使µæ°´ãèªå¨çæ', |
| | | }, |
| | | { |
| | | fieldName: 'voucherDate', |
| | | label: 'åè¯æ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©åè¯æ¥æ', |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | class: 'w-full', |
| | | }, |
| | | rules: 'required', |
| | | defaultValue: dayjs().format('YYYY-MM-DD'), |
| | | }, |
| | | { |
| | | // ä¸åè¯åå·ãåè¯æ¥æå¹¶æä¸è¡ï¼é¿å
ä¸åæ
æ ¼é空åºä¸æ ¼ |
| | | fieldName: 'attachmentCount', |
| | | label: 'éä»¶å¼ æ°', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: 'w-full', |
| | | placeholder: '请è¾å
¥éä»¶å¼ æ°', |
| | | min: 0, |
| | | precision: 0, |
| | | }, |
| | | defaultValue: 0, |
| | | rules: z.number().min(0).default(0), |
| | | }, |
| | | { |
| | | fieldName: 'summary', |
| | | label: 'åè¯æè¦', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥åè¯æè¦ï¼å¦ï¼æ¯ä» 10 æåå
¬è´¹', |
| | | allowClear: true, |
| | | maxLength: 200, |
| | | }, |
| | | formItemClass: 'col-span-3', |
| | | }, |
| | | { |
| | | fieldName: 'entries', |
| | | label: 'åè¯åå½', |
| | | component: 'Input', |
| | | formItemClass: 'col-span-3', |
| | | }, |
| | | { |
| | | fieldName: 'blobIds', |
| | | label: 'åè¯éä»¶', |
| | | component: 'FileUpload', |
| | | componentProps: { |
| | | valueKey: 'id', |
| | | maxNumber: 10, |
| | | multiple: true, |
| | | helpText: 'æ¯æå¾çãPDFãExcel çæ ¼å¼ï¼å个æä»¶ä¸è¶
è¿ 50MB', |
| | | }, |
| | | formItemClass: 'col-span-3', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | formItemClass: 'col-span-3', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** åå½è¡¨æ ¼çåæ®µ */ |
| | | export function useEntryColumns( |
| | | disabled: boolean, |
| | | ): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { type: 'seq', title: 'åºå·', width: 55, fixed: 'left' }, |
| | | { |
| | | field: 'summary', |
| | | title: 'æè¦', |
| | | minWidth: 140, |
| | | slots: { default: 'summary' }, |
| | | }, |
| | | { |
| | | field: 'subjectCode', |
| | | title: 'ä¼è®¡ç§ç®', |
| | | minWidth: 180, |
| | | slots: { default: 'subject' }, |
| | | }, |
| | | { |
| | | field: 'debit', |
| | | title: 'åæ¹éé¢', |
| | | minWidth: 130, |
| | | align: 'center', |
| | | slots: { default: 'debit' }, |
| | | }, |
| | | { |
| | | field: 'credit', |
| | | title: 'è´·æ¹éé¢', |
| | | minWidth: 130, |
| | | align: 'center', |
| | | slots: { default: 'credit' }, |
| | | }, |
| | | { |
| | | field: 'auxiliaryType', |
| | | title: 'è¾
婿 ¸ç®', |
| | | minWidth: 120, |
| | | slots: { default: 'auxiliaryType' }, |
| | | }, |
| | | { |
| | | field: 'auxiliaryName', |
| | | title: 'è¾
婿 ¸ç®å¯¹è±¡', |
| | | minWidth: 150, |
| | | slots: { default: 'auxiliaryName' }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 70, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | visible: !disabled, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'voucherNo', |
| | | label: 'åè¯åå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥åè¯åå·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'summary', |
| | | label: 'åè¯æè¦', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥åè¯æè¦', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'voucherDate', |
| | | label: 'åè¯æ¥æ', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | showTime: false, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'creator', |
| | | label: 'å¶å人', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¶å人账å·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'åè¯ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.FINANCE_VOUCHER_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©åè¯ç¶æ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<FinVoucherApi.Voucher>['columns'] { |
| | | return [ |
| | | { |
| | | type: 'expand', |
| | | width: 45, |
| | | slots: { content: 'expand_content' }, |
| | | }, |
| | | { type: 'checkbox', width: 40 }, |
| | | { |
| | | field: 'voucherNo', |
| | | title: 'åè¯åå·', |
| | | minWidth: 150, |
| | | fixed: 'left', |
| | | }, |
| | | { |
| | | field: 'voucherDate', |
| | | title: 'åè¯æ¥æ', |
| | | minWidth: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'summary', |
| | | title: 'æè¦', |
| | | minWidth: 200, |
| | | showOverflow: 'tooltip', |
| | | }, |
| | | { |
| | | field: 'debit', |
| | | title: 'åæ¹å计', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'credit', |
| | | title: 'è´·æ¹å计', |
| | | minWidth: 130, |
| | | align: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'åè¯ç¶æ', |
| | | minWidth: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.FINANCE_VOUCHER_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | // å端 creator åçæ¯ç¨æ· IDï¼ç´æ¥å±ç¤ºæ¯æ°åï¼åéè·å¯¼åºé½æ¹ç¨ creatorNameï¼æµç§°ï¼ï¼ |
| | | // æç´¢æ¡ä»æ creator ä¼ åï¼è¯ä¹ä¸å |
| | | field: 'creatorName', |
| | | title: 'å¶å人', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'attachmentCount', |
| | | title: 'éä»¶å¼ æ°', |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | title: 'éä»¶', |
| | | field: 'attachmentList', |
| | | minWidth: 100, |
| | | slots: { default: 'attachment' }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | minWidth: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 260, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinVoucherApi } from '#/api/erp/finance/voucher'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { confirm, Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | ACTION_ICON, |
| | | TableAction, |
| | | useVbenVxeGrid, |
| | | VxeColumn, |
| | | VxeTable, |
| | | } from '#/adapter/vxe-table'; |
| | | import { |
| | | cancelVoucher, |
| | | deleteVoucherList, |
| | | exportVoucher, |
| | | getVoucherPageWithEntry, |
| | | postVoucher, |
| | | } from '#/api/erp/finance/voucher'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | /** è®°è´¦åè¯å表 */ |
| | | defineOptions({ name: 'FinVoucher' }); |
| | | |
| | | /** åè¯ç¶æï¼0æªè¿è´¦ 10å·²è¿è´¦ 20å·²ä½åº */ |
| | | const VOUCHER_STATUS_UNPOSTED = 0; |
| | | const VOUCHER_STATUS_POSTED = 10; |
| | | const VOUCHER_STATUS_CANCELLED = 20; |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportVoucher(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'è®°è´¦åè¯.xls', source: data }); |
| | | } |
| | | |
| | | /** æ°å¢è®°è´¦åè¯ */ |
| | | function handleCreate() { |
| | | formModalApi.setData({ formType: 'create' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾è®°è´¦åè¯ */ |
| | | function handleEdit(row: FinVoucherApi.Voucher) { |
| | | formModalApi.setData({ formType: 'edit', id: row.id }).open(); |
| | | } |
| | | |
| | | /** æ¥ç详æ
*/ |
| | | function handleDetail(row: FinVoucherApi.Voucher) { |
| | | formModalApi.setData({ formType: 'detail', id: row.id }).open(); |
| | | } |
| | | |
| | | /** å é¤è®°è´¦åè¯ */ |
| | | async function handleDelete(ids: number[]) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting'), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteVoucherList(ids); |
| | | checkedIds.value = []; |
| | | message.success($t('ui.actionMessage.deleteSuccess')); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** åè¯è¿è´¦ */ |
| | | async function handlePost(row: FinVoucherApi.Voucher) { |
| | | const hideLoading = message.loading({ content: 'è¿è´¦ä¸...', duration: 0 }); |
| | | try { |
| | | await postVoucher(row.id!); |
| | | message.success(`åè¯ã${row.voucherNo}ãè¿è´¦æå`); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** åè¯ä½åº */ |
| | | async function handleCancel(row: FinVoucherApi.Voucher) { |
| | | await confirm( |
| | | `确认è¦ä½åºåè¯ã${row.voucherNo}ãåï¼ä½åºå该åè¯ä¸å计å
¥æ»è´¦ä¸æç»è´¦ï¼ä¸ä¸å¯æ¢å¤ã`, |
| | | ); |
| | | const hideLoading = message.loading({ content: 'ä½åºä¸...', duration: 0 }); |
| | | try { |
| | | await cancelVoucher(row.id!); |
| | | message.success(`åè¯ã${row.voucherNo}ãå·²ä½åº`); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | const checkedIds = ref<number[]>([]); |
| | | function handleRowCheckboxChange({ |
| | | records, |
| | | }: { |
| | | records: FinVoucherApi.Voucher[]; |
| | | }) { |
| | | checkedIds.value = records.map((item) => item.id!); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getVoucherPageWithEntry({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<FinVoucherApi.Voucher>, |
| | | gridEvents: { |
| | | checkboxAll: handleRowCheckboxChange, |
| | | checkboxChange: handleRowCheckboxChange, |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="è®°è´¦åè¯å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['è®°è´¦åè¯']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['finance:voucher:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['finance:voucher:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: 'æ¹éå é¤', |
| | | type: 'primary', |
| | | danger: true, |
| | | disabled: isEmpty(checkedIds), |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:voucher:delete'], |
| | | popConfirm: { |
| | | title: 'æ¯å¦å 餿é䏿°æ®ï¼å·²è¿è´¦çåè¯ä¸å
许å é¤ã', |
| | | confirm: handleDelete.bind(null, checkedIds), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | |
| | | <template #expand_content="{ row }"> |
| | | <VxeTable |
| | | :data="row.entries ?? []" |
| | | border |
| | | size="small" |
| | | show-overflow |
| | | class="mx-2" |
| | | > |
| | | <VxeColumn title="åºå·" width="60"> |
| | | <template #default="{ row: entry }"> |
| | | {{ entry.rowNo ?? '-' }} |
| | | </template> |
| | | </VxeColumn> |
| | | <VxeColumn title="æè¦" min-width="180"> |
| | | <template #default="{ row: entry }"> |
| | | {{ entry.summary || '-' }} |
| | | </template> |
| | | </VxeColumn> |
| | | <VxeColumn title="ä¼è®¡ç§ç®" min-width="220"> |
| | | <template #default="{ row: entry }"> |
| | | {{ entry.subjectCode }} {{ entry.subjectName }} |
| | | </template> |
| | | </VxeColumn> |
| | | <VxeColumn title="åæ¹éé¢" align="right" width="140"> |
| | | <template #default="{ row: entry }"> |
| | | {{ entry.debit ? entry.debit.toFixed(2) : '-' }} |
| | | </template> |
| | | </VxeColumn> |
| | | <VxeColumn title="è´·æ¹éé¢" align="right" width="140"> |
| | | <template #default="{ row: entry }"> |
| | | {{ entry.credit ? entry.credit.toFixed(2) : '-' }} |
| | | </template> |
| | | </VxeColumn> |
| | | <VxeColumn title="è¾
婿 ¸ç®" min-width="150"> |
| | | <template #default="{ row: entry }"> |
| | | {{ entry.auxiliaryName || '-' }} |
| | | </template> |
| | | </VxeColumn> |
| | | </VxeTable> |
| | | </template> |
| | | |
| | | <template #attachment="{ row }"> |
| | | <Tag v-if="!row.hasAttachment" color="warning">æªä¸ä¼ </Tag> |
| | | <Tag v-else color="success">å·²ä¸ä¼ </Tag> |
| | | </template> |
| | | |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.detail'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.VIEW, |
| | | auth: ['finance:voucher:query'], |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['finance:voucher:update'], |
| | | ifShow: () => row.status === VOUCHER_STATUS_UNPOSTED, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'è¿è´¦', |
| | | type: 'link', |
| | | icon: ACTION_ICON.AUDIT, |
| | | auth: ['finance:voucher:update-status'], |
| | | ifShow: () => row.status === VOUCHER_STATUS_UNPOSTED, |
| | | popConfirm: { |
| | | title: `确认è¿è´¦åè¯ã${row.voucherNo}ãåï¼è¿è´¦åå°è®¡å
¥æ»è´¦ä¸æç»è´¦ã`, |
| | | confirm: handlePost.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'ä½åº', |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:voucher:update-status'], |
| | | ifShow: () => |
| | | row.status === VOUCHER_STATUS_UNPOSTED || |
| | | row.status === VOUCHER_STATUS_POSTED, |
| | | onClick: handleCancel.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['finance:voucher:delete'], |
| | | ifShow: () => |
| | | row.status === VOUCHER_STATUS_UNPOSTED || |
| | | row.status === VOUCHER_STATUS_CANCELLED, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.voucherNo]), |
| | | confirm: handleDelete.bind(null, [row.id!]), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { FinAccountSubjectApi } from '#/api/erp/finance/subject'; |
| | | |
| | | import { nextTick, ref } from 'vue'; |
| | | |
| | | import { message, Modal } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getAccountSubjectPage } from '#/api/erp/finance/subject'; |
| | | |
| | | defineOptions({ name: 'FinAccountSelect' }); |
| | | |
| | | const emit = defineEmits<{ |
| | | selected: [subject: FinAccountSubjectApi.AccountSubject]; |
| | | }>(); |
| | | |
| | | const open = ref(false); // å¼¹çªæ¯å¦æå¼ |
| | | const selectedRow = ref<FinAccountSubjectApi.AccountSubject>(); // éä¸çç§ç® |
| | | |
| | | /** è¡¨æ ¼é
ç½® */ |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: [ |
| | | { |
| | | fieldName: 'subjectCode', |
| | | label: 'ç§ç®ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ç§ç®ç¼ç ', allowClear: true }, |
| | | }, |
| | | { |
| | | fieldName: 'subjectName', |
| | | label: 'ç§ç®åç§°', |
| | | component: 'Input', |
| | | componentProps: { placeholder: '请è¾å
¥ç§ç®åç§°', allowClear: true }, |
| | | }, |
| | | ], |
| | | }, |
| | | gridOptions: { |
| | | columns: [ |
| | | { type: 'radio', width: 50 }, |
| | | { |
| | | field: 'subjectCode', |
| | | title: 'ç§ç®ç¼ç ', |
| | | minWidth: 140, |
| | | treeNode: true, |
| | | }, |
| | | { field: 'subjectName', title: 'ç§ç®åç§°', minWidth: 200 }, |
| | | { |
| | | field: 'subjectType', |
| | | title: 'ç§ç®ç±»å', |
| | | minWidth: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: 'finance_subject_type' }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'balanceDirection', |
| | | title: 'ä½é¢æ¹å', |
| | | minWidth: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: 'finance_balance_direction' }, |
| | | }, |
| | | }, |
| | | ], |
| | | height: 480, |
| | | keepSource: true, |
| | | radioConfig: { |
| | | highlight: true, |
| | | trigger: 'row', |
| | | }, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async (_params, formValues) => { |
| | | // æ å½¢å±ç¤ºä¸éç¨å页ï¼ä¸æ¬¡æ§æåå
¨é¨å½ä¸ç§ç® |
| | | const page = await getAccountSubjectPage({ |
| | | pageNo: 1, |
| | | pageSize: -1, |
| | | status: 0, |
| | | ...formValues, |
| | | }); |
| | | return { |
| | | list: page.list, |
| | | total: page.list.length, |
| | | }; |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | treeConfig: { |
| | | parentField: 'parentId', |
| | | rowField: 'id', |
| | | transform: true, |
| | | expandAll: true, |
| | | reserve: true, |
| | | }, |
| | | } as VxeTableGridOptions<FinAccountSubjectApi.AccountSubject>, |
| | | gridEvents: { |
| | | radioChange: ({ row }: { row: FinAccountSubjectApi.AccountSubject }) => { |
| | | selectedRow.value = row; |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | /** æå¼å¼¹çª */ |
| | | async function openModal() { |
| | | open.value = true; |
| | | selectedRow.value = undefined; |
| | | // ç¹æ®ï¼å¼¹çªæ¯ destroy-on-closeï¼éé¢ Grid éå¼¹çªé建ï¼å¿
é¡»çæè½½å®æå gridApi ææçå®ä¾ |
| | | // ï¼vben ç gridApi.grid åå§æ¯ç©ºå¯¹è±¡ï¼mount æ¶æèµå¼ï¼ï¼å¦å clearRadioRow 伿¥ is not a function |
| | | await nextTick(); |
| | | await gridApi.grid.clearRadioRow(); |
| | | await gridApi.formApi.resetForm(); |
| | | await gridApi.query(); |
| | | } |
| | | |
| | | /** ç¡®è®¤éæ© */ |
| | | function handleConfirm() { |
| | | if (!selectedRow.value) { |
| | | message.warning('è¯·éæ©ä¸æ¡ä¼è®¡ç§ç®'); |
| | | return; |
| | | } |
| | | emit('selected', selectedRow.value); |
| | | open.value = false; |
| | | } |
| | | |
| | | defineExpose({ open: openModal }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal |
| | | v-model:open="open" |
| | | title="éæ©ä¼è®¡ç§ç®" |
| | | width="70%" |
| | | :destroy-on-close="true" |
| | | @ok="handleConfirm" |
| | | @cancel="open = false" |
| | | > |
| | | <Grid table-title="ä¼è®¡ç§ç®å表ï¼ä»
å¯ç¨ç¶æçç§ç®å¯è¢«åè¯å¼ç¨ï¼" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FinVoucherApi } from '#/api/erp/finance/voucher'; |
| | | |
| | | import { computed, nextTick, ref, watch } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | import { erpPriceInputFormatter } from '@vben/utils'; |
| | | |
| | | import { Input, InputNumber, Select } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | |
| | | import { useEntryColumns } from '../data'; |
| | | import AccountSelect from './account-select.vue'; |
| | | |
| | | interface EntryRow extends FinVoucherApi.VoucherEntry { |
| | | seq: number; // åç«¯è¡æ è¯ï¼ç¨äºè¡¨æ ¼å®ä½ |
| | | } |
| | | |
| | | interface Props { |
| | | entries?: FinVoucherApi.VoucherEntry[]; |
| | | disabled?: boolean; |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | entries: () => [], |
| | | disabled: false, |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:entries']); |
| | | |
| | | /** åå½è¡ï¼seq ç¨äºè¡¨æ ¼è¡å®ä½ï¼æ°å¢åè¯æ¶åå½è¿æ²¡æ idï¼ */ |
| | | const tableData = ref<EntryRow[]>([]); |
| | | let seq = 0; |
| | | |
| | | /** æå端åå½è½¬ä¸ºå¸¦ seq çè¡¨æ ¼è¡ */ |
| | | function toRows(entries: FinVoucherApi.VoucherEntry[]): EntryRow[] { |
| | | return entries.map((entry) => ({ ...entry, seq: ++seq })); |
| | | } |
| | | |
| | | /** å计ï¼åæ¹ãè´·æ¹ãå·®é¢ */ |
| | | const summaries = computed(() => { |
| | | const debit = tableData.value.reduce( |
| | | (sum, item) => sum + (item.debit || 0), |
| | | 0, |
| | | ); |
| | | const credit = tableData.value.reduce( |
| | | (sum, item) => sum + (item.credit || 0), |
| | | 0, |
| | | ); |
| | | return { debit, credit, diff: debit - credit }; |
| | | }); |
| | | |
| | | /** æ¯å¦å贷平衡 */ |
| | | const isBalanced = computed( |
| | | () => summaries.value.debit > 0 && summaries.value.diff === 0, |
| | | ); |
| | | |
| | | /** è¡¨æ ¼é
ç½® */ |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: useEntryColumns(props.disabled), |
| | | data: tableData.value, |
| | | minHeight: 300, |
| | | autoResize: true, |
| | | border: true, |
| | | rowConfig: { |
| | | keyField: 'seq', |
| | | isHover: true, |
| | | }, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | toolbarConfig: { |
| | | enabled: false, |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | /** çå¬å¤é¨ä¼ å
¥çåå½ */ |
| | | watch( |
| | | () => props.entries, |
| | | async (entries) => { |
| | | if (!entries) { |
| | | return; |
| | | } |
| | | tableData.value = toRows(entries); |
| | | await nextTick(); // ç¹æ®ï¼ä¿è¯ gridApi å·²ç»åå§å |
| | | await gridApi.grid.reloadData(tableData.value); |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | |
| | | /** éç¥ç¶ç»ä»¶åå½å·²åæ´ */ |
| | | function emitChange() { |
| | | const entries = tableData.value.map(({ seq: _seq, ...entry }) => entry); |
| | | emit('update:entries', entries); |
| | | } |
| | | |
| | | /** ä¿®æ¹æè¡æ°æ® */ |
| | | function handleRowChange(row: EntryRow) { |
| | | const index = tableData.value.findIndex((item) => item.seq === row.seq); |
| | | if (index === -1) { |
| | | tableData.value.push(row); |
| | | } else { |
| | | tableData.value[index] = row; |
| | | } |
| | | emitChange(); |
| | | } |
| | | |
| | | /** å½å
¥åæ¹é颿¶æ¸
空贷æ¹éé¢ï¼åä¹äº¦ç¶ï¼åä¸è¡åªè½æä¸æ¹æéé¢ï¼ */ |
| | | function handleDebitChange(row: EntryRow) { |
| | | if (row.debit && row.debit > 0) { |
| | | row.credit = 0; |
| | | } |
| | | handleRowChange(row); |
| | | } |
| | | |
| | | function handleCreditChange(row: EntryRow) { |
| | | if (row.credit && row.credit > 0) { |
| | | row.debit = 0; |
| | | } |
| | | handleRowChange(row); |
| | | } |
| | | |
| | | /** éæ©ä¼è®¡ç§ç® */ |
| | | const accountSelectRef = ref(); |
| | | const editingRow = ref<EntryRow>(); |
| | | |
| | | function handleOpenAccount(row: EntryRow) { |
| | | if (props.disabled) { |
| | | return; |
| | | } |
| | | editingRow.value = row; |
| | | accountSelectRef.value?.open(); |
| | | } |
| | | |
| | | function handleSelectAccount(subject: { |
| | | subjectCode: string; |
| | | subjectName: string; |
| | | }) { |
| | | const row = editingRow.value; |
| | | if (!row) { |
| | | return; |
| | | } |
| | | row.subjectCode = subject.subjectCode; |
| | | row.subjectName = subject.subjectName; |
| | | handleRowChange(row); |
| | | } |
| | | |
| | | /** æ·»å ä¸è¡ç©ºç½åå½ */ |
| | | function handleAdd() { |
| | | const row: EntryRow = { |
| | | seq: ++seq, |
| | | subjectCode: undefined, |
| | | subjectName: undefined, |
| | | summary: '', |
| | | debit: 0, |
| | | credit: 0, |
| | | auxiliaryType: undefined, |
| | | auxiliaryId: '', |
| | | auxiliaryName: '', |
| | | }; |
| | | tableData.value.push(row); |
| | | gridApi.grid.reloadData(tableData.value); |
| | | emitChange(); |
| | | } |
| | | |
| | | /** å é¤ä¸è¡åå½ */ |
| | | function handleDelete(row: EntryRow) { |
| | | const index = tableData.value.findIndex((item) => item.seq === row.seq); |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1); |
| | | } |
| | | gridApi.grid.reloadData(tableData.value); |
| | | emitChange(); |
| | | } |
| | | |
| | | /** è¡¨åæ ¡éªï¼ä¸å端 FinVoucherServiceImpl çæ ¡éªå£å¾ä¿æä¸è´ */ |
| | | function validate() { |
| | | const rows = tableData.value.filter( |
| | | (item) => item.subjectCode || item.debit || item.credit, |
| | | ); |
| | | if (rows.length === 0) { |
| | | throw new Error('请è³å°å½å
¥ä¸æ¡åè¯åå½'); |
| | | } |
| | | for (let i = 0; i < rows.length; i++) { |
| | | const item = rows[i]!; |
| | | if (!item.subjectCode) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼å·²å½å
¥éé¢ä½æªéæ©ä¼è®¡ç§ç®`); |
| | | } |
| | | const debit = item.debit || 0; |
| | | const credit = item.credit || 0; |
| | | if (debit <= 0 && credit <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼å·²éæ©ä¼è®¡ç§ç®ä½æªå½å
¥éé¢`); |
| | | } |
| | | if (debit > 0 && credit > 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼åæ¹ä¸è´·æ¹éé¢ä¸è½åæ¶å¤§äº 0`); |
| | | } |
| | | } |
| | | if (summaries.value.debit <= 0) { |
| | | throw new Error('åè¯åè´·å计å¿
é¡»å¤§äº 0'); |
| | | } |
| | | if (summaries.value.diff !== 0) { |
| | | throw new Error( |
| | | `åè¯åè´·ä¸å¹³è¡¡ï¼åæ¹å计 ${erpPriceInputFormatter(summaries.value.debit)}ï¼` + |
| | | `è´·æ¹å计 ${erpPriceInputFormatter(summaries.value.credit)}ï¼` + |
| | | `å·®é¢ ${erpPriceInputFormatter(summaries.value.diff)}`, |
| | | ); |
| | | } |
| | | } |
| | | |
| | | defineExpose({ validate }); |
| | | </script> |
| | | |
| | | <template> |
| | | <div> |
| | | <Grid class="w-full"> |
| | | <template #summary="{ row }"> |
| | | <Input |
| | | v-model:value="row.summary" |
| | | :disabled="disabled" |
| | | placeholder="请è¾å
¥æè¦" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | </template> |
| | | |
| | | <template #subject="{ row }"> |
| | | <div |
| | | class="flex cursor-pointer items-center" |
| | | :class="{ 'text-primary': !disabled }" |
| | | @click="handleOpenAccount(row)" |
| | | > |
| | | <span v-if="row.subjectCode"> |
| | | {{ row.subjectCode }} {{ row.subjectName }} |
| | | </span> |
| | | <span v-else class="text-muted-foreground">è¯·éæ©ä¼è®¡ç§ç®</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <template #debit="{ row }"> |
| | | <InputNumber |
| | | v-model:value="row.debit" |
| | | :precision="2" |
| | | :min="0" |
| | | :disabled="disabled" |
| | | class="w-full" |
| | | placeholder="åæ¹éé¢" |
| | | @change="handleDebitChange(row)" |
| | | /> |
| | | </template> |
| | | |
| | | <template #credit="{ row }"> |
| | | <InputNumber |
| | | v-model:value="row.credit" |
| | | :precision="2" |
| | | :min="0" |
| | | :disabled="disabled" |
| | | class="w-full" |
| | | placeholder="è´·æ¹éé¢" |
| | | @change="handleCreditChange(row)" |
| | | /> |
| | | </template> |
| | | |
| | | <template #auxiliaryType="{ row }"> |
| | | <Select |
| | | v-model:value="row.auxiliaryType" |
| | | :disabled="disabled" |
| | | allow-clear |
| | | class="w-full" |
| | | placeholder="æ " |
| | | @change="handleRowChange(row)" |
| | | > |
| | | <Select.Option |
| | | v-for="dict in getDictOptions( |
| | | DICT_TYPE.FINANCE_AUXILIARY_TYPE, |
| | | 'number', |
| | | )" |
| | | :key="dict.value" |
| | | :value="dict.value" |
| | | > |
| | | {{ dict.label }} |
| | | </Select.Option> |
| | | </Select> |
| | | </template> |
| | | |
| | | <template #auxiliaryName="{ row }"> |
| | | <Input |
| | | v-model:value="row.auxiliaryName" |
| | | :disabled="disabled" |
| | | placeholder="请è¾å
¥è¾
婿 ¸ç®å¯¹è±¡" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | </template> |
| | | |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'å é¤', |
| | | type: 'link', |
| | | danger: true, |
| | | popConfirm: { |
| | | title: '确认å é¤è¯¥åå½åï¼', |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | |
| | | <template #bottom> |
| | | <div class="mt-2 rounded border border-border bg-muted p-2"> |
| | | <div class="flex justify-between text-sm"> |
| | | <span class="font-medium text-foreground">å计ï¼</span> |
| | | <div class="flex space-x-4"> |
| | | <span> |
| | | åæ¹å计ï¼{{ erpPriceInputFormatter(summaries.debit) }} |
| | | </span> |
| | | <span> |
| | | è´·æ¹å计ï¼{{ erpPriceInputFormatter(summaries.credit) }} |
| | | </span> |
| | | <span :class="isBalanced ? 'text-green-600' : 'text-red-500'"> |
| | | å·®é¢ï¼{{ erpPriceInputFormatter(summaries.diff) }} |
| | | {{ isBalanced ? 'ï¼å贷平衡ï¼' : 'ï¼åè´·ä¸å¹³è¡¡ï¼' }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <TableAction |
| | | v-if="!disabled" |
| | | class="mt-2 flex justify-center" |
| | | :actions="[ |
| | | { |
| | | label: 'æ·»å åå½', |
| | | type: 'default', |
| | | onClick: handleAdd, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | |
| | | <!-- ä¼è®¡ç§ç®éæ© --> |
| | | <AccountSelect ref="accountSelectRef" @selected="handleSelectAccount" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | /* |
| | | * åæ¹/è´·æ¹éé¢åå±
ä¸ã |
| | | * vxe åç align: 'center' åªä½ç¨äºåå
æ ¼ï¼è ant ç InputNumber å
é¨ <input> |
| | | * ï¼.ant-input-number-inputï¼èªå¸¦ text-align: startï¼ä¼ææ°ååå左边ï¼å¯¼è´è¡¨å¤´å±
ä¸ã |
| | | * æ°åé å·¦çéä½ï¼æä»¥è¿éè¿è¾å
¥æ¡å
çæåä¸èµ·è¦çã |
| | | * æ¬ç»ä»¶åªæè¿ä¸¤ä¸ª InputNumberï¼åæ¹ãè´·æ¹ï¼ï¼ä¸åå½±åå
¶å®åã |
| | | */ |
| | | :deep(.ant-input-number .ant-input-number-input) { |
| | | text-align: center; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FinVoucherApi } from '#/api/erp/finance/voucher'; |
| | | import type { FormType } from '../data'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { $t } from '@vben/locales'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createVoucher, |
| | | getVoucher, |
| | | updateVoucher, |
| | | } from '#/api/erp/finance/voucher'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | import EntryTable from './entry-table.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<FinVoucherApi.Voucher>(); |
| | | |
| | | const formType = ref<FormType>('create'); // 表åç±»åï¼'create' | 'edit' | 'detail' |
| | | const entryTableRef = ref<InstanceType<typeof EntryTable>>(); |
| | | // åå½çå¯ä¸æ°æ®æºï¼å¿
é¡»ç¨ç¨³å®ç refï¼ä¸è½å¨æ¨¡æ¿éç°é []ã |
| | | // å¦åç¶ç»ä»¶æ¯æ¬¡æ¸²æé½ä¼ç» EntryTable ä¼ ä¸ä¸ªæ°ç空æ°ç»ï¼è§¦åå
¶ watch éå»ºè¡¨æ ¼ï¼ |
| | | // æç¨æ·åãæ·»å åå½ãå è¿å»çè¡å²æï¼æ°å¢æ¨¡å¼ä¸ formData 为 undefined æ¶å¿
ç°ï¼ã |
| | | const entries = ref<FinVoucherApi.VoucherEntry[]>([]); |
| | | |
| | | const getTitle = computed(() => { |
| | | if (formType.value === 'create') { |
| | | return $t('ui.actionTitle.create', ['è®°è´¦åè¯']); |
| | | } else if (formType.value === 'edit') { |
| | | return $t('ui.actionTitle.edit', ['è®°è´¦åè¯']); |
| | | } |
| | | return 'è®°è´¦åè¯è¯¦æ
'; |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | labelWidth: 100, |
| | | }, |
| | | // ä¸åæ
æ ¼ï¼åè¯åå·/åè¯æ¥æ/éä»¶å¼ æ° å æ»¡ä¸è¡ï¼å
¶ä½å段æ´è¡éºå¼ï¼ |
| | | // é¿å
两åå¸å±ä¸æè¦ãéä»¶å¼ æ°å³åè¾¹ç空ï¼ä¸æ¶æ¬¾åã仿¬¾åå¼¹çªä¿æä¸è´ï¼ |
| | | wrapperClass: 'grid-cols-3', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | /** |
| | | * 忥åå½å°è¡¨åå¼ï¼é表åä¸èµ·æäº¤ã |
| | | * 注æï¼è¿éåªå表åå¼ï¼**ä¸è¦æ value ååå° entries**ââ |
| | | * entries åªå¨æå¼å¼¹çªæ¶ä»æå¡ç«¯æç§ä¸æ¬¡ï¼ä¹åç宿¶æ°æ®ä»¥ EntryTable å
é¨è¡¨æ ¼ä¸ºåã |
| | | * 䏿¦ååï¼props.entries ååä¼è§¦å EntryTable ç watch é建æ´å¼ è¡¨æ ¼ï¼å¯¼è´æ¯æ²ä¸ä¸ª |
| | | * æ°å/é䏿¬¡ç§ç®å°±éé ä¸éè¡ï¼è¡æ è¯ååãè¾å
¥æ¡å¤±ç¦ï¼ã |
| | | */ |
| | | function handleUpdateEntries(value: FinVoucherApi.VoucherEntry[]) { |
| | | formApi.setValues({ entries: value }); |
| | | } |
| | | |
| | | /** åå»ºææ´æ°è®°è´¦åè¯ */ |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | // å彿 ¡éªä¸å端 FinVoucherServiceImpl çå£å¾ä¸è´ï¼å端å
æ¦ä¸éï¼é¿å
æ æä¹çè¯·æ± |
| | | const entryTableInstance = Array.isArray(entryTableRef.value) |
| | | ? entryTableRef.value[0] |
| | | : entryTableRef.value; |
| | | try { |
| | | entryTableInstance?.validate(); |
| | | } catch (error: any) { |
| | | message.error(error.message || 'åè¯å彿 ¡éªå¤±è´¥'); |
| | | return; |
| | | } |
| | | |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as FinVoucherApi.Voucher; |
| | | if (formData.value?.id) { |
| | | data.id = formData.value.id; |
| | | } |
| | | try { |
| | | await (formType.value === 'create' |
| | | ? createVoucher(data) |
| | | : updateVoucher(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; |
| | | } |
| | | formData.value = undefined; |
| | | entries.value = []; |
| | | await formApi.resetForm(); |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data?.formType ?? 'create'; |
| | | formApi.setDisabled(formType.value === 'detail'); |
| | | if (!data?.id) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getVoucher(data.id); |
| | | await formApi.setValues(formData.value, false); |
| | | entries.value = formData.value?.entries ?? []; |
| | | // ç¨ attachmentList ä»£æ¿ blobIdsï¼è®©ä¸ä¼ ç»ä»¶è½å±ç¤ºæä»¶ååé¢è§é¾æ¥ |
| | | if (formData.value?.attachmentList?.length) { |
| | | const blobIds = formData.value.attachmentList.map((item) => ({ |
| | | uid: String(item.id), |
| | | name: item.name || '', |
| | | url: item.url || '', |
| | | status: 'done', |
| | | id: item.id, |
| | | })); |
| | | await formApi.setFieldValue('blobIds', blobIds); |
| | | } |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal |
| | | :title="getTitle" |
| | | class="w-5/6" |
| | | :close-on-click-modal="false" |
| | | :show-confirm-button="formType !== 'detail'" |
| | | > |
| | | <Form class="mx-3"> |
| | | <template #entries> |
| | | <EntryTable |
| | | ref="entryTableRef" |
| | | :entries="entries" |
| | | :disabled="formType === 'detail'" |
| | | class="w-full" |
| | | @update:entries="handleUpdateEntries" |
| | | /> |
| | | </template> |
| | | </Form> |
| | | </Modal> |
| | | </template> |