feat(财务): 新增天医币订单和用户余额模块
Some checks failed
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled

- 添加天医币订单相关API和页面
- 添加用户余额相关API和页面
- 在系统字典中新增来源和订单类型配置
- 调整部分UI样式和表单配置
This commit is contained in:
2026-01-12 09:20:38 +08:00
parent f93751e74a
commit 963e2a8d39
14 changed files with 406 additions and 328 deletions

View File

@@ -1,2 +1,4 @@
export * from './import';
export * from './reconciliate';
export * from './surplus';
export * from './tianyibi';

View File

@@ -0,0 +1,10 @@
import { requestClient } from '#/api/request';
export const balanceApi = {
/**
* 获取用户余额列表
*/
getUserBalanceList: (data: { limit: number; page: number; tel?: string }) => {
return requestClient.post('/common/user/getUserList', data);
},
};

View File

@@ -0,0 +1,37 @@
import { requestClient } from '#/api/request';
export const tianyibiApi = {
/**
* 获取天医币订单列表
*/
getPointOrdersList: (data: {
endTime?: string;
limit: number;
page: number;
startTime?: string;
tel?: string;
}) => {
return requestClient.post('/common/orders/getPointOrdersListNoUse', data);
},
/**
* 手动确认消耗
*/
manualConsumeTianyibi: (data: { orderId: number }) => {
return requestClient.post('/common/orders/manualConsumePoint', data);
},
/**
* 取消消耗
*/
cancelConsumeTianyibi: (data: { orderId: number }) => {
return requestClient.post('/common/orders/cancelConsumePoint', data);
},
/**
* 自动消耗
*/
autoConsumeTianyibi: () => {
return requestClient.post('/common/orders/autoConsumePoint');
},
};

View File

@@ -29,6 +29,24 @@ const routes: RouteRecordRaw[] = [
path: '/posting/reconciliate-bills',
component: () => import('#/views/posting/reconciliate/index.vue'),
},
{
meta: {
title: '天医币订单',
keepAlive: true,
},
name: 'TianyibiOrders',
path: '/posting/tianyibi-orders',
component: () => import('#/views/posting/tianyibiOrders/index.vue'),
},
{
meta: {
title: '用户余额',
keepAlive: true,
},
name: 'UserSurplus',
path: '/posting/user-surplus',
component: () => import('#/views/posting/userSurplus/index.vue'),
},
],
},
];

View File

@@ -12,6 +12,17 @@ export const useSysStore = defineStore('sys', () => {
'1': '对账失败',
'2': '对账成功',
},
source: {
'0': '一路健康',
'1': '吴门医述',
},
orderType: {
'0': '充值',
'1': 'vip',
'2': '课',
'3': '实物',
'4': '培训班',
},
} as const;
// 自动推断字典类型
@@ -64,6 +75,38 @@ export const useSysStore = defineStore('sys', () => {
color: 'success',
},
],
source: [
{
label: '一路健康',
value: '0',
},
{
label: '吴门医述',
value: '1',
},
],
orderType: [
{
label: '充值',
value: '0',
},
{
label: 'vip',
value: '1',
},
{
label: '课',
value: '2',
},
{
label: '实物',
value: '3',
},
{
label: '培训班',
value: '4',
},
],
} as const;
// 自动推断字典列表类型

View File

@@ -62,7 +62,6 @@ const formOptions: VbenFormProps = {
defaultValue: '',
fieldName: 'checkoff',
label: '核对状态',
// 核对状态占1列
formItemClass: 'col-span-2',
},
],

View File

@@ -326,7 +326,6 @@ function onCompleteCheckCreated() {
:deep(.ant-card-body) {
padding: 1px !important;
height: calc(100% - 41px); // 减去标签页头部高度
overflow: hidden;
// background-color: #f1f3f6;
}
}

View File

@@ -0,0 +1,214 @@
<script lang="ts" setup>
import type { VbenFormProps } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { Page } from '@vben/common-ui';
import { Button, message, notification } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { tianyibiApi } from '#/api/posting';
import { useSysStore } from '#/store';
const sysStore = useSysStore();
interface RowType {
id: number;
source: number;
orderSn: string;
orderOldId: string;
tel: string;
fee: number;
type: number;
orderTime: string;
useFlag: string;
}
const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
wrapperClass: 'grid-cols-6',
schema: [
{
component: 'RadioGroup',
componentProps: {
options: [
{
label: '未使用',
value: '0',
},
{
label: '已使用',
value: '1',
},
],
optionType: 'button',
},
defaultValue: '0',
fieldName: 'useFlag',
label: '状态',
},
{
component: 'Input',
componentProps: {
allowClear: true,
},
fieldName: 'tel',
label: '手机号',
},
{
component: 'Select',
componentProps: {
allowClear: true,
options: sysStore.getDictList('source'),
},
fieldName: 'source',
label: '来源',
},
{
component: 'Select',
componentProps: {
allowClear: true,
options: sysStore.getDictList('orderType').filter((item) => item.value !== '0'),
},
fieldName: 'type',
label: '订单类型',
},
{
component: 'RangePicker',
componentProps: {
allowClear: true,
placeholder: ['开始时间', '结束时间'],
valueFormat: 'YYYY-MM-DD',
},
fieldName: 'orderTime',
label: '订单时间',
formItemClass: 'col-span-2',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: false,
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};
// 处理订单时间范围
const handleFormValues = (values: any) => {
const { orderTime, ...rest } = values;
return orderTime && orderTime.length === 2
? {
...rest,
startTime: orderTime[0],
endTime: orderTime[1],
}
: {
...rest,
startTime: '',
endTime: '',
};
};
const gridOptions: VxeTableGridOptions<RowType> = {
columns: [
{ field: 'orderSn', title: '订单编号', width: 250 },
{
field: 'source',
title: '来源',
formatter: ({ cellValue }) => {
return sysStore.getDictMap('source', cellValue);
},
},
{ field: 'tel', title: '手机号' },
{ field: 'fee', title: '金额' },
{
field: 'type',
title: '类型',
formatter: ({ cellValue }) => {
return sysStore.getDictMap('orderType', cellValue);
},
},
{ field: 'orderTime', title: '订单时间', formatter: 'formatDateTime', minWidth: 160 },
{
title: '操作',
slots: {
default: 'action',
},
},
],
exportConfig: {},
height: 'auto',
keepSource: true,
rowConfig: {
isHover: true,
},
pagerConfig: {
enabled: true,
},
proxyConfig: {
response: {
result: 'records',
list: 'records',
total: 'total',
},
ajax: {
query: async ({ page }, formValues) => {
const params = handleFormValues(formValues);
return await tianyibiApi.getPointOrdersList({
page: page.currentPage,
limit: page.pageSize,
...params,
});
},
},
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
async function onStartAutoMatch() {
const hide = message.loading('系统自动匹配消耗中...', 0);
await tianyibiApi.autoConsumeTianyibi();
hide();
notification.success({
message: '自动匹配消耗成功',
});
// 刷新表格数据
gridApi?.query();
}
async function onConsumption(row: RowType, code: number) {
const api = code === 1 ? tianyibiApi.manualConsumeTianyibi : tianyibiApi.cancelConsumeTianyibi;
await api({ orderId: row.id });
notification.success({
message: code === 1 ? '确认消耗成功' : '取消消耗成功',
});
// 刷新表格数据
gridApi?.query();
}
</script>
<template>
<Page auto-content-height>
<Grid>
<template #toolbar-actions>
<div class="flex gap-2">
<Button type="primary" @click="onStartAutoMatch()">启动自动匹配消耗</Button>
</div>
</template>
<template #action="{ row }">
<Button v-if="!row.useFlag" type="primary" size="small" @click="onConsumption(row, 1)">
确认消耗
</Button>
<Button v-else type="primary" danger ghost size="small" @click="onConsumption(row, 0)">
取消消耗
</Button>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,82 @@
<script lang="ts" setup>
import type { VbenFormProps } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { balanceApi } from '#/api/posting';
interface RowType {
id: number;
tel: string;
totalPoint: number;
point: number;
}
const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
wrapperClass: 'grid-cols-6',
schema: [
{
component: 'Input',
componentProps: {
allowClear: true,
},
fieldName: 'tel',
label: '手机号',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: false,
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};
const gridOptions: VxeTableGridOptions<RowType> = {
columns: [
{ field: 'tel', title: '用户手机号' },
{ field: 'totalPoint', title: '总充值' },
{ field: 'point', title: '可用' },
],
exportConfig: {},
height: 'auto',
keepSource: true,
rowConfig: {
isHover: true,
},
pagerConfig: {
enabled: true,
},
proxyConfig: {
response: {
result: 'records',
list: 'records',
total: 'total',
},
ajax: {
query: async ({ page }, formValues) => {
return await balanceApi.getUserBalanceList({
page: page.currentPage,
limit: page.pageSize,
...formValues,
});
},
},
},
};
const [Grid] = useVbenVxeGrid({
formOptions,
gridOptions,
});
</script>
<template>
<Page auto-content-height>
<Grid />
</Page>
</template>