From 75111681b4fd1dc0d80d756e8d542e71e101b4ba Mon Sep 17 00:00:00 2001 From: chenghuan Date: Tue, 6 Jan 2026 18:03:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=B4=A2=E5=8A=A1):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=85=A5=E8=B4=A6=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增账单导入和账单核对功能 - 添加账单导入页面,支持微信、支付宝、银行账单文件上传 - 实现账单核对功能,包括自动核对和人工核对 - 添加多种类型订单展示组件(VIP、课程、实物商品等) - 实现订单选择和提交功能 - 添加CardList组件用于展示可选项和已选项 --- apps/finance/src/api/posting/import.ts | 17 + apps/finance/src/api/posting/index.ts | 2 + apps/finance/src/api/posting/reconciliate.ts | 115 +++++ .../components/SelectDropdownRender/index.vue | 84 ++++ apps/finance/src/components/card-list/api.ts | 197 +++++++++ .../src/components/card-list/card-list.vue | 393 ++++++++++++++++++ .../finance/src/components/card-list/index.ts | 11 + .../finance/src/components/card-list/types.ts | 134 ++++++ .../src/components/card-list/use-card-list.ts | 41 ++ .../src/router/routes/modules/posting.ts | 36 ++ .../posting/import/components/FileBox.vue | 100 +++++ .../src/views/posting/import/index.vue | 141 +++++++ .../reconciliate/components/Orders.vue | 208 +++++++++ .../reconciliate/components/Selected.vue | 176 ++++++++ .../components/TrainingClassProduct.vue | 86 ++++ .../reconciliate/components/VipProduct.vue | 76 ++++ .../components/WumenCourseProduct.vue | 83 ++++ .../components/WumenCourseRecharge.vue | 74 ++++ .../components/WumenCourseRecord.vue | 77 ++++ .../components/WumenPhysicalProduct.vue | 82 ++++ .../components/YiluCourseProduct.vue | 82 ++++ .../components/YiluCourseRecharge.vue | 74 ++++ .../components/YiluCourseRecord.vue | 77 ++++ .../components/YiluPhysicalProduct.vue | 86 ++++ .../composables/useCardListForm.ts | 18 + .../composables/useCardListGrid.ts | 159 +++++++ .../src/views/posting/reconciliate/index.vue | 229 ++++++++++ .../posting/reconciliate/modules/Manual.vue | 331 +++++++++++++++ .../src/views/posting/reconciliate/types.d.ts | 29 ++ 29 files changed, 3218 insertions(+) create mode 100644 apps/finance/src/api/posting/import.ts create mode 100644 apps/finance/src/api/posting/index.ts create mode 100644 apps/finance/src/api/posting/reconciliate.ts create mode 100644 apps/finance/src/components/SelectDropdownRender/index.vue create mode 100644 apps/finance/src/components/card-list/api.ts create mode 100644 apps/finance/src/components/card-list/card-list.vue create mode 100644 apps/finance/src/components/card-list/index.ts create mode 100644 apps/finance/src/components/card-list/types.ts create mode 100644 apps/finance/src/components/card-list/use-card-list.ts create mode 100644 apps/finance/src/router/routes/modules/posting.ts create mode 100644 apps/finance/src/views/posting/import/components/FileBox.vue create mode 100644 apps/finance/src/views/posting/import/index.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/Orders.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/Selected.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/TrainingClassProduct.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/VipProduct.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/WumenCourseProduct.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/WumenCourseRecharge.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/WumenCourseRecord.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/WumenPhysicalProduct.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/YiluCourseProduct.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/YiluCourseRecharge.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/YiluCourseRecord.vue create mode 100644 apps/finance/src/views/posting/reconciliate/components/YiluPhysicalProduct.vue create mode 100644 apps/finance/src/views/posting/reconciliate/composables/useCardListForm.ts create mode 100644 apps/finance/src/views/posting/reconciliate/composables/useCardListGrid.ts create mode 100644 apps/finance/src/views/posting/reconciliate/index.vue create mode 100644 apps/finance/src/views/posting/reconciliate/modules/Manual.vue create mode 100644 apps/finance/src/views/posting/reconciliate/types.d.ts diff --git a/apps/finance/src/api/posting/import.ts b/apps/finance/src/api/posting/import.ts new file mode 100644 index 0000000..8e126a8 --- /dev/null +++ b/apps/finance/src/api/posting/import.ts @@ -0,0 +1,17 @@ +import { requestClient } from '#/api/request'; + +export const importBillsApi = { + /** + * 获取文件列表 + */ + getImportFileList: (data: { month?: number; year: number }) => { + return requestClient.post('/common/import/getImportList', data); + }, + + /** + * 上传文件 + */ + uploadFile: (data: any) => { + return requestClient.upload('/common/import/importData', data); + }, +}; diff --git a/apps/finance/src/api/posting/index.ts b/apps/finance/src/api/posting/index.ts new file mode 100644 index 0000000..d53c773 --- /dev/null +++ b/apps/finance/src/api/posting/index.ts @@ -0,0 +1,2 @@ +export * from './import'; +export * from './reconciliate'; diff --git a/apps/finance/src/api/posting/reconciliate.ts b/apps/finance/src/api/posting/reconciliate.ts new file mode 100644 index 0000000..b5906ed --- /dev/null +++ b/apps/finance/src/api/posting/reconciliate.ts @@ -0,0 +1,115 @@ +import { requestClient } from '#/api/request'; + +export const reconciliateBillsApi = { + /** + * 获取账单列表 + */ + getReconciliationList: (data: any) => { + return requestClient.post('/common/payment/getPaymentList', data); + }, + + /** + * 人工对账--订单对账 + */ + manualCheckOrder: (data: any) => { + return requestClient.post('/common/payment/manualCheckoff', data); + }, + + /** + * 人工对账--添加订单后对账 + */ + manualCheckCreated: (data: any) => { + return requestClient.post('/common/payment/checkoffByAddOrder', { list: data }); + }, + + /** + * 自动对账 + */ + autoCheck: (data: any) => { + return requestClient.post('/common/payment/autoCheckoff', data); + }, + + /** + * 获取订单列表 + */ + getOrderList: (data: any) => { + return requestClient.post('/common/payment/manualList', data); + }, + + /** + * 获取吴门医述开课记录 + */ + getWumenRecommendCourseBuyList: (data: any) => { + return requestClient.post('/common/payment/recommendWumenCourseBuyList', data); + }, + + /** + * 获取吴门医述课程商品列表 + */ + getWumenCourseBuyList: (data: any) => { + return requestClient.post('/common/payment/wumenCourseBuyList', data); + }, + + /** + * 获取吴门医述充值记录列表 + */ + getWumenPointBuyList: (data: any) => { + return requestClient.post('/common/payment/wumenPointBuyList', data); + }, + + /** + * 获取吴门医述实物商品列表 + */ + getWumenPhysicalBuyList: (data: any) => { + return requestClient.post('/common/payment/wumenPhysicalBuyList', data); + }, + + /** + * 获取一路健康开课记录列表 + */ + getYiluRecommendCourseBuyList: (data: any) => { + return requestClient.post('/common/payment/recommendCcourseBuyList', data); + }, + + /** + * 获取一路健康课程商品列表 + */ + getYiluCourseBuyList: (data: any) => { + return requestClient.post('/common/payment/courseBuyList', data); + }, + + /** + * 获取一路健康充值记录列表 + */ + getYiluPointBuyList: (data: any) => { + return requestClient.post('/common/payment/yljkPointBuyList', data); + }, + + /** + * 获取一路健康实物商品列表 + */ + getPhysicalBuyList: (data: any) => { + return requestClient.post('/common/payment/physicalBuyList', data); + }, + + /** + * 获取VIP列表 + */ + getVipList: (data: any) => { + return requestClient.post('/common/payment/vipList', data); + }, + + /** + * 获取报名培训班列表 + */ + getTrainingClassList: (data: any) => { + return requestClient.post('/common/payment/trainingClassList', data); + }, + + /** + * 获取推荐用户 + */ + getRecommendUser: (data: any) => { + return requestClient.post('/common/payment/recommendUser', data); + }, +}; diff --git a/apps/finance/src/components/SelectDropdownRender/index.vue b/apps/finance/src/components/SelectDropdownRender/index.vue new file mode 100644 index 0000000..5e17264 --- /dev/null +++ b/apps/finance/src/components/SelectDropdownRender/index.vue @@ -0,0 +1,84 @@ + + diff --git a/apps/finance/src/components/card-list/api.ts b/apps/finance/src/components/card-list/api.ts new file mode 100644 index 0000000..f96514f --- /dev/null +++ b/apps/finance/src/components/card-list/api.ts @@ -0,0 +1,197 @@ +import type { CardListApi, CardListPagination, CardListProps } from './types'; + +import { bindMethods, isFunction, mergeWithArrayOverride, StateHandler } from '@vben/utils'; + +import { Store } from '@vben-core/shared/store'; + +function getDefaultState(): CardListProps { + return { + class: '', + gridClass: '', + gridOptions: { + columns: [], + data: [], + showTitle: true, + gridColumns: 3, + pagerConfig: { + enabled: true, + current: 1, + pageSize: 10, + total: 0, + showSizeChanger: true, + showQuickJumper: true, + showTotal: (total: number) => `共 ${total} 条`, + }, + cardHeight: 'auto', + cardWidth: '100%', + cardGap: '10px', + loading: false, + emptyText: '暂无数据', + }, + gridEvents: {}, + }; +} + +export class CardListApiInstance = any> implements CardListApi { + public state: CardListProps | null = null; + public store: Store>; + + private isMounted = false; + private stateHandler: StateHandler; + + constructor(options: CardListProps = {}) { + const storeState = { ...options }; + + const defaultState = getDefaultState(); + this.store = new Store(mergeWithArrayOverride(storeState, defaultState), { + onUpdate: () => { + this.state = this.store.state; + }, + }); + + this.state = this.store.state; + this.stateHandler = new StateHandler(); + bindMethods(this); + } + + clear() { + this.setData([]); + } + + getData() { + return (this.state?.gridOptions?.data || []).map((item) => ({ ...item })) as T[]; + } + + /** 检查组件是否已挂载 */ + getIsMounted() { + return this.isMounted; + } + + getState() { + return this.state; + } + + /** 添加项目 */ + insertAt(items: T[], index?: number) { + const data = this.getData(); + const newData = [...data]; + + if (typeof index === 'number' && index >= 0 && index <= newData.length) { + newData.splice(index, 0, ...items); + } else { + // 需判断如果原数据已经存在,则不添加 + newData.push(...items); + } + + this.setData(newData); + } + + mount() { + this.isMounted = true; + } + + refresh() { + // 触发数据刷新 + const currentData = this.state?.gridOptions?.data || []; + this.setData([...currentData] as any); + } + + remove(predicate: ((row: T, index: number) => boolean) | number) { + const data = this.getData(); + + const newData = + typeof predicate === 'number' + ? data.filter((_, index) => index !== predicate) + : data.filter((row, index) => !predicate(row, index)); + + this.setData(newData); + } + + reset() { + const defaultState = getDefaultState(); + this.setState({ + gridOptions: defaultState.gridOptions, + }); + } + + setData(data: T[]) { + this.setState({ + gridOptions: { + ...this.state?.gridOptions, + data: data as any, + }, + }); + } + + setErrorItems(errorItems: any[]) { + // 遍历data,将errorItems中的id匹配到的item添加error属性 + const newData = this.getData().map((item) => { + const errorData = errorItems.find((error) => error.id === item.id); + return errorData ? { ...item, error: true } : { ...item, error: false }; + }); + this.setState({ + gridOptions: { + ...this.state?.gridOptions, + data: newData as any, + }, + }); + } + + setLoading(loading: boolean) { + this.setState({ + gridOptions: { + ...this.state?.gridOptions, + loading, + }, + }); + } + + setPagination(pagination: any) { + // 确保分页对象符合CardListPagination类型要求 + const validPagination: CardListPagination = { + current: pagination?.current || 1, + pageSize: pagination?.pageSize || 10, + total: pagination?.total, + showSizeChanger: pagination?.showSizeChanger, + showQuickJumper: pagination?.showQuickJumper, + showTotal: pagination?.showTotal, + }; + + this.setState({ + gridOptions: { + ...this.state?.gridOptions, + pagerConfig: validPagination, + }, + }); + } + + setState( + state: ((prev: CardListProps) => Partial>) | Partial>, + ) { + if (isFunction(state)) { + const stateFn = state as (prev: CardListProps) => Partial>; + this.store.setState((prev: any) => { + return mergeWithArrayOverride(stateFn(prev), prev); + }); + } else { + this.store.setState((prev: any) => mergeWithArrayOverride(state, prev)); + } + } + + unmount() { + this.isMounted = false; + this.stateHandler.reset(); + } + + updateItem(predicate: ((row: T, index: number) => boolean) | number, newItem: Partial) { + const data = this.getData(); + + const newData = data.map((row, index) => { + const matched = typeof predicate === 'number' ? index === predicate : predicate(row, index); + + return matched ? { ...row, ...newItem } : row; + }); + + this.setData(newData); + } +} diff --git a/apps/finance/src/components/card-list/card-list.vue b/apps/finance/src/components/card-list/card-list.vue new file mode 100644 index 0000000..43d551b --- /dev/null +++ b/apps/finance/src/components/card-list/card-list.vue @@ -0,0 +1,393 @@ + + + + + diff --git a/apps/finance/src/components/card-list/index.ts b/apps/finance/src/components/card-list/index.ts new file mode 100644 index 0000000..5e99857 --- /dev/null +++ b/apps/finance/src/components/card-list/index.ts @@ -0,0 +1,11 @@ +export { CardListApiInstance } from './api'; +export { default as CardList } from './card-list.vue'; +export type { + CardListApi, + CardListColumn, + CardListOptions, + CardListPagination, + CardListProps, + ExtendedCardListApi, +} from './types'; +export { useCardList } from './use-card-list'; diff --git a/apps/finance/src/components/card-list/types.ts b/apps/finance/src/components/card-list/types.ts new file mode 100644 index 0000000..2ce5381 --- /dev/null +++ b/apps/finance/src/components/card-list/types.ts @@ -0,0 +1,134 @@ +import type { Ref } from 'vue'; + +import type { ClassType, DeepPartial } from '@vben/types'; + +export interface CardListColumn { + /** 字段名 */ + field: string; + /** 显示标题 */ + title: string; + /** 格式化函数 */ + formatter?: (value: any, row: T) => string; + /** 插槽配置 */ + slots?: { + default?: string; + }; + /** 显示条件 */ + show?: ((row: T) => boolean) | boolean; + /** 编辑渲染组件 */ + editRender?: { name: string; props?: Record }; +} + +export interface CardListPagination { + /** 是否启用分页 */ + enabled?: boolean; + /** 当前页码 */ + current?: number; + /** 每页条数 */ + pageSize?: number; + /** 总条数 */ + total?: number; + /** 显示分页大小选择器 */ + showSizeChanger?: boolean; + /** 显示快速跳转 */ + showQuickJumper?: boolean; + /** 显示总条数 */ + showTotal?: ((total: number, range: [number, number]) => string) | boolean; +} + +export interface CardListOptions { + /** 列配置 */ + columns?: CardListColumn[]; + /** 数据源 */ + data?: T[]; + /** 是否显示卡片标题 */ + showTitle?: boolean; + /** 标题字段名,如果不指定则使用第一个字段 */ + titleField?: string; + /** 卡片类名 */ + cardClass?: string; + /** 网格列数 */ + gridColumns?: number; + /** 网格高度 */ + gridHeight?: string; + /** 分页配置 */ + pagerConfig?: CardListPagination; + /** 卡片高度 */ + cardHeight?: string; + /** 卡片宽度 */ + cardWidth?: string; + /** 卡片间距 */ + cardGap?: string; + /** 加载状态 */ + loading?: boolean; + /** 空状态文本 */ + emptyText?: string; + /** 错误数据 */ + errorData?: T[]; +} + +export interface CardListProps { + /** 标题 */ + title?: string; + /** 标题帮助 */ + titleHelp?: string; + /** 组件class */ + class?: ClassType; + /** 网格class */ + gridClass?: ClassType; + /** 网格配置 */ + gridOptions?: DeepPartial>; + /** 网格事件 */ + gridEvents?: { + /** 卡片点击 */ + cardClick?: (row: T) => void; + /** 页码变化 */ + pageChange?: (page: number, pageSize: number) => void; + /** 每页条数变化 */ + pageSizeChange?: (pageSize: number) => void; + }; +} + +export type CardListApi = { + /** 清空数据 */ + clear: () => void; + /** 获取数据 */ + getData: () => T[]; + /** 获取状态 */ + getState: () => CardListProps | null; + /** 添加项目 */ + insertAt: (items: T[], index?: number) => void; + /** 挂载组件 */ + mount: () => void; + /** 刷新数据 */ + refresh: () => void; + /** 删除项目 */ + remove: (predicate: ((row: T, index: number) => boolean) | number) => void; + /** 重置 */ + reset: () => void; + /** 设置数据 */ + setData: (data: T[]) => void; + /** 设置错误数据 */ + setErrorItems: (errorItems: { id: number | string }[]) => void; + /** 设置加载状态 */ + setLoading: (loading: boolean) => void; + /** 设置分页 */ + setPagination: (pagination: CardListPagination) => void; + /** 设置状态 */ + setState: (state: Partial>) => void; + /** 卸载组件 */ + unmount: () => void; + /** 更新项目 */ + updateItem: ( + predicate: ((row: T, index: number) => boolean) | number, + newItem: Partial, + ) => void; +}; + +export type ExtendedCardListApi = CardListApi & { + useStore: >(selector?: (state: CardListProps) => R) => Readonly>; +}; + +export type UseVbenCardList = = any>( + options: CardListProps, +) => readonly [any, ExtendedCardListApi]; diff --git a/apps/finance/src/components/card-list/use-card-list.ts b/apps/finance/src/components/card-list/use-card-list.ts new file mode 100644 index 0000000..25dd4ca --- /dev/null +++ b/apps/finance/src/components/card-list/use-card-list.ts @@ -0,0 +1,41 @@ +import type { SlotsType } from 'vue'; + +import type { CardListProps, ExtendedCardListApi } from './types'; + +import { defineComponent, h, onBeforeUnmount } from 'vue'; + +import { useStore } from '@vben-core/shared/store'; + +import { CardListApiInstance } from './api'; +import CardList from './card-list.vue'; + +export function useCardList = any>(options: CardListProps) { + const api = new CardListApiInstance(options); + const extendedApi = api as any as ExtendedCardListApi; + + extendedApi.useStore = (selector) => { + return useStore(api.store, selector); + }; + + const CardListComponent = defineComponent( + (props: CardListProps, { attrs, slots }) => { + onBeforeUnmount(() => { + api.unmount(); + }); + + api.setState({ ...props, ...attrs }); + return () => h(CardList, { ...props, ...attrs, api: extendedApi }, slots); + }, + { + name: 'CardList', + inheritAttrs: false, + slots: Object as SlotsType<{ + [key: string]: { field: string; index: number; row: T; value: any }; + }>, + }, + ); + + return [CardListComponent, extendedApi] as const; +} + +export type useCardList = typeof useCardList; diff --git a/apps/finance/src/router/routes/modules/posting.ts b/apps/finance/src/router/routes/modules/posting.ts new file mode 100644 index 0000000..0fa4f2a --- /dev/null +++ b/apps/finance/src/router/routes/modules/posting.ts @@ -0,0 +1,36 @@ +import type { RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'ic:baseline-view-in-ar', + keepAlive: true, + order: 1000, + title: '入账管理', + }, + name: 'Posting', + path: '/posting', + children: [ + { + meta: { + title: '账单导入', + keepAlive: true, + }, + name: 'ImportBills', + path: '/posting/import-bills', + component: () => import('#/views/posting/import/index.vue'), + }, + { + meta: { + title: '账单核对', + keepAlive: true, + }, + name: 'ReconciliateBills', + path: '/posting/reconciliate-bills', + component: () => import('#/views/posting/reconciliate/index.vue'), + }, + ], + }, +]; + +export default routes; diff --git a/apps/finance/src/views/posting/import/components/FileBox.vue b/apps/finance/src/views/posting/import/components/FileBox.vue new file mode 100644 index 0000000..3081972 --- /dev/null +++ b/apps/finance/src/views/posting/import/components/FileBox.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/apps/finance/src/views/posting/import/index.vue b/apps/finance/src/views/posting/import/index.vue new file mode 100644 index 0000000..e47665b --- /dev/null +++ b/apps/finance/src/views/posting/import/index.vue @@ -0,0 +1,141 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/Orders.vue b/apps/finance/src/views/posting/reconciliate/components/Orders.vue new file mode 100644 index 0000000..c91723a --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/Orders.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/Selected.vue b/apps/finance/src/views/posting/reconciliate/components/Selected.vue new file mode 100644 index 0000000..3e11b44 --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/Selected.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/TrainingClassProduct.vue b/apps/finance/src/views/posting/reconciliate/components/TrainingClassProduct.vue new file mode 100644 index 0000000..0a1279d --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/TrainingClassProduct.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/VipProduct.vue b/apps/finance/src/views/posting/reconciliate/components/VipProduct.vue new file mode 100644 index 0000000..1fe0942 --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/VipProduct.vue @@ -0,0 +1,76 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/WumenCourseProduct.vue b/apps/finance/src/views/posting/reconciliate/components/WumenCourseProduct.vue new file mode 100644 index 0000000..898dbcf --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/WumenCourseProduct.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/WumenCourseRecharge.vue b/apps/finance/src/views/posting/reconciliate/components/WumenCourseRecharge.vue new file mode 100644 index 0000000..5e30fe0 --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/WumenCourseRecharge.vue @@ -0,0 +1,74 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/WumenCourseRecord.vue b/apps/finance/src/views/posting/reconciliate/components/WumenCourseRecord.vue new file mode 100644 index 0000000..8175f0b --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/WumenCourseRecord.vue @@ -0,0 +1,77 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/WumenPhysicalProduct.vue b/apps/finance/src/views/posting/reconciliate/components/WumenPhysicalProduct.vue new file mode 100644 index 0000000..42ff615 --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/WumenPhysicalProduct.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/YiluCourseProduct.vue b/apps/finance/src/views/posting/reconciliate/components/YiluCourseProduct.vue new file mode 100644 index 0000000..acc45af --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/YiluCourseProduct.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/YiluCourseRecharge.vue b/apps/finance/src/views/posting/reconciliate/components/YiluCourseRecharge.vue new file mode 100644 index 0000000..f661510 --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/YiluCourseRecharge.vue @@ -0,0 +1,74 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/YiluCourseRecord.vue b/apps/finance/src/views/posting/reconciliate/components/YiluCourseRecord.vue new file mode 100644 index 0000000..e27b464 --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/YiluCourseRecord.vue @@ -0,0 +1,77 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/components/YiluPhysicalProduct.vue b/apps/finance/src/views/posting/reconciliate/components/YiluPhysicalProduct.vue new file mode 100644 index 0000000..b7763d5 --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/components/YiluPhysicalProduct.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/composables/useCardListForm.ts b/apps/finance/src/views/posting/reconciliate/composables/useCardListForm.ts new file mode 100644 index 0000000..f51528b --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/composables/useCardListForm.ts @@ -0,0 +1,18 @@ +import type { VbenFormProps } from '#/adapter/form'; + +export function useCardListForm(userFormOptions?: VbenFormProps) { + const defaultFormOptions: VbenFormProps = { + collapsed: false, + wrapperClass: 'grid-cols-4', + showCollapseButton: false, + submitOnChange: true, + submitOnEnter: false, + }; + + const formOptions: VbenFormProps = { + ...defaultFormOptions, + ...userFormOptions, + }; + + return { formOptions }; +} diff --git a/apps/finance/src/views/posting/reconciliate/composables/useCardListGrid.ts b/apps/finance/src/views/posting/reconciliate/composables/useCardListGrid.ts new file mode 100644 index 0000000..4750d3d --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/composables/useCardListGrid.ts @@ -0,0 +1,159 @@ +import type { Ref } from 'vue'; + +import type { VxeGridListeners, VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { watch } from 'vue'; + +import { useVbenVxeGrid } from '#/adapter/vxe-table'; + +interface UseCardListGridOptions { + payment: Ref; + selectedData: Ref; + rowKey: string; + rowKeyPrefix?: string; + columns: VxeTableGridOptions['columns']; + query: (params: any) => Promise; + transformData: (row: RowType[]) => OrderType[]; + emit: (event: 'completeCheck' | 'deletedChecked', payload: any) => void; + + formOptions?: any; + gridOptions?: Partial>; + gridEvents?: VxeGridListeners; +} + +export function useCardListGrid( + options: UseCardListGridOptions, +) { + const { + payment, + selectedData, + rowKey, + rowKeyPrefix, + columns, + query, + transformData, + emit, + formOptions, + gridOptions: userGridOptions, + gridEvents: userGridEvents, + } = options; + + function setChecked(data: OrderType[]) { + const currentPageData = gridApi.grid.getData(); + const rows = currentPageData.filter((row: any) => + data.some((item: any) => item.id === row[rowKey]), + ); + rows.length > 0 && gridApi.grid.setCheckboxRow(rows, true); + } + + function cancelCheck(id: number) { + gridApi.grid.setCheckboxRow({ [rowKey]: id }, false); + } + + const defaultGridOptions: VxeTableGridOptions = { + columns, + exportConfig: {}, + height: 'auto', + keepSource: true, + rowConfig: { + keyField: rowKey, + isHover: true, + }, + checkboxConfig: { + reserve: true, + showReserveStatus: true, + highlight: true, + trigger: 'row', + }, + pagerConfig: { + enabled: true, + }, + proxyConfig: { + response: { + result: 'records', + total: 'total', + list: 'records', + }, + ajax: { + query: async ({ page }, formValues) => { + const data = await query({ + page: page.currentPage, + limit: page.pageSize, + ...formValues, + }); + // 确保 listKey 是字符串类型 + const responseList = userGridOptions?.proxyConfig?.response?.list; + const listKey = typeof responseList === 'string' ? responseList : 'records'; + const list = data[listKey].map((item: any) => ({ + ...item, + [rowKey]: `${rowKeyPrefix || ''}${item[rowKey]}`, + })); + data[listKey] = list; + return data; + }, + querySuccess: () => { + setChecked(selectedData.value || []); + }, + }, + }, + }; + + const finalGridOptions: VxeTableGridOptions = { + ...defaultGridOptions, + ...userGridOptions, + // 对 proxyConfig 进行特殊处理,确保用户配置能覆盖默认配置 + proxyConfig: { + ...defaultGridOptions.proxyConfig, + ...userGridOptions?.proxyConfig, + // 对 proxyConfig.ajax 进行特殊处理 + ajax: { + ...defaultGridOptions.proxyConfig?.ajax, + ...userGridOptions?.proxyConfig?.ajax, + }, + // 对 proxyConfig.response 进行特殊处理 + response: { + ...defaultGridOptions.proxyConfig?.response, + ...userGridOptions?.proxyConfig?.response, + }, + }, + }; + + const defaultGridEvents: VxeGridListeners = { + checkboxChange: ({ checked, row }: any) => { + checked ? emit('completeCheck', transformData([row])) : emit('deletedChecked', [row[rowKey]]); + }, + checkboxAll: ({ checked }: any) => { + const rows = gridApi.grid.getData(); + const ids = rows.map((item: any) => item[rowKey]); + checked ? emit('completeCheck', transformData(rows)) : emit('deletedChecked', ids); + }, + }; + + const finalGridEvents: VxeGridListeners = { + ...defaultGridEvents, + ...userGridEvents, + }; + + const [Grid, gridApi] = useVbenVxeGrid({ + formOptions, + gridOptions: finalGridOptions as any, + gridEvents: finalGridEvents, + }); + + watch( + payment, + (newPayment, oldPayment) => { + if (newPayment?.id && newPayment.id !== oldPayment?.id) { + gridApi?.reload(); + } + }, + { immediate: false }, + ); + + return { + Grid, + gridApi, + cancelCheck, + setChecked, + }; +} diff --git a/apps/finance/src/views/posting/reconciliate/index.vue b/apps/finance/src/views/posting/reconciliate/index.vue new file mode 100644 index 0000000..6df9ee1 --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/index.vue @@ -0,0 +1,229 @@ + + + diff --git a/apps/finance/src/views/posting/reconciliate/modules/Manual.vue b/apps/finance/src/views/posting/reconciliate/modules/Manual.vue new file mode 100644 index 0000000..a1b487f --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/modules/Manual.vue @@ -0,0 +1,331 @@ + + + + + diff --git a/apps/finance/src/views/posting/reconciliate/types.d.ts b/apps/finance/src/views/posting/reconciliate/types.d.ts new file mode 100644 index 0000000..124de5a --- /dev/null +++ b/apps/finance/src/views/posting/reconciliate/types.d.ts @@ -0,0 +1,29 @@ +// 实际支付记录 +export interface PaymentRowType { + checkoff: number; + ctime: string; + fee: string; + financeSn: string; + id: number; + transactionSn: string; + relationSn: string; + type: number; +} + +// 创建订单参数 +export interface CreateOrderType { + id: number | string; + paymentId: number | string; + courseId: number | string; + tel: string; + come: string; + orderType: string; + productName: string; + catalogueId: number | string; + orderMoney: string; + districtMoney: string; + realMoney: string; + startTime: string; + endTime: string; + vipType?: string; +}