feat: 中药导入模块修复、部门财务的优化

This commit is contained in:
李琦
2026-08-06 20:15:42 +08:00
parent 174693f6db
commit cfc8390573
85 changed files with 4405 additions and 377 deletions

View File

@@ -87,7 +87,7 @@ const [Modal, modalApi] = useVbenModal({
: null;
lockPlatformId.value = isOpen ? !!openData.lockPlatformId : false;
if (!isOpen) {
formApi.resetFields();
formApi.resetForm();
selectedPlatformId.value = undefined;
platforms.value = [];
return;
@@ -119,7 +119,7 @@ const [Modal, modalApi] = useVbenModal({
formApi.setValues({ ...values, platform_id: pid, api_key: '' });
}
} else {
formApi.resetFields();
formApi.resetForm();
const presetPid = Number(openData.platform_id || values?.platform_id || 0) || undefined;
const firstId = presetPid || platforms.value[0]?.id;
selectedPlatformId.value = firstId;

View File

@@ -6,7 +6,7 @@ import { AI_GENERATION_SCENE_OPTIONS, AI_GENERATION_STATUS_OPTIONS } from './con
/**
* AI 生成记录搜索表单
* 时间范围字段 search_time,查询前映射为 start_time/end_time 时间戳
* 平台/模型用下拉(options 在页面 onMounted 注入),时间范围映射为 start_time/end_time
*/
export const formOptions: VbenFormProps = {
collapsed: false,
@@ -14,7 +14,8 @@ export const formOptions: VbenFormProps = {
{
component: 'VbenInput',
componentProps: {
placeholder: '名称 / 模型 / 错误摘要',
placeholder: '名称 / 错误摘要',
allowClear: true,
},
defaultValue: '',
fieldName: 'keyword',
@@ -43,32 +44,31 @@ export const formOptions: VbenFormProps = {
label: '场景',
},
{
component: 'VbenInput',
component: 'Select',
componentProps: {
placeholder: '如 spark / deepseek',
placeholder: '全部平台',
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
options: [],
},
defaultValue: '',
fieldName: 'provider',
label: '供应商',
defaultValue: undefined,
fieldName: 'platform_id',
label: '平台',
},
{
component: 'VbenInput',
component: 'Select',
componentProps: {
placeholder: '模型编码',
placeholder: '全部模型',
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
options: [],
},
defaultValue: '',
defaultValue: undefined,
fieldName: 'model',
label: '模型',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '密钥 ID',
},
defaultValue: '',
fieldName: 'api_key_id',
label: '密钥ID',
},
{
component: 'RangePicker',
componentProps: {

View File

@@ -2,8 +2,9 @@
/**
* AI 生成记录列表页
* 顶部用量摘要(随当前筛选刷新)+ VxeGrid 明细
* 平台/模型筛选下拉:从 option 接口注入,选平台后联动刷新模型列表
*/
import { onMounted, reactive } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
@@ -12,6 +13,8 @@ import { Card, Col, Row, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { getAiModelOption } from '../platform/api/model';
import { getAiPlatformOption } from '../platform/api';
import { getAiGenerationList, getAiGenerationUsageStats } from './api';
import DetailModal from './components/detail-modal.vue';
import { formOptions, normalizeAiGenerationFilters } from './config/search';
@@ -31,6 +34,9 @@ const stats = reactive({
fail_count: 0,
});
/** 全量模型 option,选平台时按 platform_id 过滤 */
const allModelOptions = ref<Array<{ label: string; value: string; platform_id?: number }>>([]);
/** 把毫秒格式化为秒文案 */
function formatDuration(ms: number) {
return formatDurationMs(ms);
@@ -55,8 +61,45 @@ async function refreshStats(formValues?: Record<string, any>) {
}
}
const [Grid] = useVbenVxeGrid({
formOptions,
/** 按平台刷新模型下拉;清空平台时恢复全部模型 */
function refreshModelSchema(platformId?: number) {
const filtered =
platformId && platformId > 0
? allModelOptions.value.filter((m) => Number(m.platform_id) === platformId)
: allModelOptions.value;
gridApi.formApi?.updateSchema?.([
{
fieldName: 'model',
componentProps: {
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
options: filtered,
placeholder: '全部模型',
},
},
]);
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
...formOptions,
schema: formOptions.schema?.map((item) =>
item.fieldName === 'platform_id'
? {
...item,
componentProps: {
...item.componentProps,
onChange: (val: number | undefined) => {
// 切换平台时清掉已选模型,避免跨平台残留
gridApi.formApi?.setFieldValue?.('model', undefined);
refreshModelSchema(val ? Number(val) : undefined);
},
},
}
: item,
),
},
gridOptions: {
...gridOptions,
proxyConfig: {
@@ -90,8 +133,42 @@ function statusColor(status: number) {
return 'processing';
}
onMounted(() => {
onMounted(async () => {
void refreshStats();
try {
const [platformRes, modelRes] = await Promise.all([
getAiPlatformOption(),
getAiModelOption(),
]);
const platforms = Array.isArray(platformRes)
? platformRes
: platformRes?.data || [];
const models = Array.isArray(modelRes) ? modelRes : modelRes?.data || [];
allModelOptions.value = models.map((m: any) => ({
label: m.label || `${m.name || ''}(${m.code || m.value || ''})`,
value: String(m.value ?? m.code ?? ''),
platform_id: Number(m.platform_id || 0),
}));
gridApi.formApi?.updateSchema?.([
{
fieldName: 'platform_id',
componentProps: {
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
options: platforms,
placeholder: '全部平台',
onChange: (val: number | undefined) => {
gridApi.formApi?.setFieldValue?.('model', undefined);
refreshModelSchema(val ? Number(val) : undefined);
},
},
},
]);
refreshModelSchema();
} catch {
// option 失败时下拉为空,仍可用关键词筛
}
});
</script>

View File

@@ -45,7 +45,7 @@ const [Modal, modalApi] = useVbenModal({
? modalApi.getData()?.onSuccess || null
: null;
if (!isOpen) {
formApi.resetFields();
formApi.resetForm();
return;
}
const { values, update } = modalApi.getData<Record<string, any>>() || {};
@@ -53,7 +53,7 @@ const [Modal, modalApi] = useVbenModal({
if (values && update) {
formApi.setValues({ ...values });
} else {
formApi.resetFields();
formApi.resetForm();
formApi.setValues({ sort: 0, status: 1 });
}
},

View File

@@ -115,7 +115,7 @@ const [Modal, modalApi] = useVbenModal({
},
onOpenChange(isOpen: boolean) {
if (!isOpen) {
formApi.resetFields();
formApi.resetForm();
onSuccess.value = null;
return;
}
@@ -125,7 +125,7 @@ const [Modal, modalApi] = useVbenModal({
if (data.values && data.update) {
formApi.setValues({ ...data.values });
} else {
formApi.resetFields();
formApi.resetForm();
formApi.setValues({
platform_id: data.platform_id || data.values?.platform_id || 0,
sort: 0,

View File

@@ -177,7 +177,7 @@ const [FormModal, formModalApi] = useVbenModal({
},
onOpenChange(isOpen: boolean) {
if (!isOpen) {
formApi.resetFields();
formApi.resetForm();
return;
}
const data = formModalApi.getData<{ values?: any; update?: boolean }>();
@@ -185,7 +185,7 @@ const [FormModal, formModalApi] = useVbenModal({
if (data?.values && data.update) {
formApi.setValues({ ...data.values });
} else {
formApi.resetFields();
formApi.resetForm();
}
},
});

View File

@@ -0,0 +1,28 @@
/**
* 药品计量单位字典 API(yii_west_unit)
*/
import { requestClient } from '#/api/request';
const prefix = 'west-unit-dict/';
export async function getWestUnitDictList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
export async function getWestUnitDictOption() {
return requestClient.get<{ value: number; label: string }[]>(
`${prefix}option`,
);
}
export async function createWestUnitDict(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
export async function updateWestUnitDict(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
export async function deleteWestUnitDict(data: { ids: number[] }) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,214 @@
<script lang="ts" setup>
/**
* 药品计量单位字典(yii_west_unit)
* 挂在字典管理下,供开方/仓库导入复用
*/
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import {
createWestUnitDict,
deleteWestUnitDict,
getWestUnitDictList,
updateWestUnitDict,
} from './api';
defineOptions({ name: 'WestUnitDict' });
const hasTopTableDropDownActions = ref(false);
const isUpdate = ref(false);
const formOptions = {
schema: [
{
component: 'VbenInput',
fieldName: 'keyword',
label: '关键字',
componentProps: { placeholder: '单位名称' },
},
],
};
const gridOptions = {
checkboxConfig: { highlight: true, labelField: '' },
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', title: 'ID', width: 80 },
{ field: 'name', title: '单位名称', minWidth: 180 },
{ field: 'created_at', title: '创建时间', width: 170 },
{ field: 'updated_at', title: '更新时间', width: 170 },
{ title: '操作', slots: { default: 'action' }, width: 140, fixed: 'right' },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }: any, formValues: any) => {
return await getWestUnitDictList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
toolbarConfig: {
search: true,
refresh: true,
slots: { buttons: 'toolbar-buttons' },
},
};
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
hasTopTableDropDownActions.value =
gridApi.grid.getCheckboxRecords().length > 0;
},
checkboxAll() {
hasTopTableDropDownActions.value =
gridApi.grid.getCheckboxRecords().length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions: gridOptions as any,
gridEvents,
});
const [Form, formApi] = useVbenForm({
wrapperClass: 'grid-cols-12',
commonConfig: {
formItemClass: 'col-span-12',
componentProps: { class: 'w-full' },
},
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
dependencies: { show: false, triggerFields: ['id'] },
},
{
component: 'VbenInput',
fieldName: 'name',
label: '单位名称',
rules: 'required',
componentProps: { placeholder: '如:盒、瓶、g、袋', maxlength: 20 },
},
],
showDefaultActions: false,
});
const [FormModal, formModalApi] = useVbenModal({
class: 'w-[480px]',
draggable: true,
onConfirm: async () => {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
formModalApi.setState({ confirmLoading: true });
try {
if (isUpdate.value) {
await updateWestUnitDict(values);
} else {
await createWestUnitDict(values);
}
message.success('保存成功');
gridApi.query();
formModalApi.close();
} finally {
formModalApi.setState({ confirmLoading: false });
}
},
onOpenChange(isOpen: boolean) {
if (!isOpen) {
formApi.resetForm();
return;
}
const data = formModalApi.getData<{ values?: any; update?: boolean }>();
isUpdate.value = !!data?.update;
if (data?.values && data.update) {
formApi.setValues({ ...data.values });
} else {
formApi.resetForm();
}
},
});
function showModal(row: any = {}, update = false) {
formModalApi.setData({ values: row, update });
formModalApi.open();
}
async function handleDelete(ids: number[]) {
await deleteWestUnitDict({ ids });
message.success('删除成功');
gridApi.query();
}
</script>
<template>
<Page auto-content-height title="单位管理">
<FormModal :title="`${isUpdate ? '编辑' : '新增'}单位`">
<Form />
</FormModal>
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
onClick: () => showModal({}, false),
},
{
label: '批量删除',
danger: true,
ifShow: () => hasTopTableDropDownActions,
popConfirm: {
title: '确认删除所选?被药品引用的单位无法删除。',
confirm: () => {
const ids = gridApi.grid
.getCheckboxRecords()
.map((r: any) => r.id);
handleDelete(ids);
},
},
},
]"
/>
</template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
onClick: () => showModal(row, true),
},
{
label: '删除',
type: 'link',
danger: true,
popConfirm: {
title: '确认删除该单位?',
confirm: () => handleDelete([row.id]),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -58,7 +58,7 @@ const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (!isOpen) {
formApi.resetFields();
formApi.resetForm();
return;
}
const { values, update } = modalApi.getData<Record<string, any>>() || {};
@@ -75,7 +75,7 @@ const [Modal, modalApi] = useVbenModal({
: '',
});
} else {
formApi.resetFields();
formApi.resetForm();
formApi.setValues({ sort: 0, status: 1, drugs_json: '' });
}
},

View File

@@ -68,7 +68,7 @@ const [Modal, modalApi] = useVbenModal({
} else {
// 新增时重置表单并设置默认值
isUpdate.value = false;
formApi.resetFields();
formApi.resetForm();
formApi.setValues({
id: '',
title: '',
@@ -83,7 +83,7 @@ const [Modal, modalApi] = useVbenModal({
}
} else {
// 关闭时重置表单
formApi.resetFields();
formApi.resetForm();
}
},
});

View File

@@ -76,7 +76,7 @@ const [Modal, modalApi] = useVbenModal({
});
} else {
// 关闭时重置表单
formApi.resetFields();
formApi.resetForm();
}
},
});

View File

@@ -69,7 +69,7 @@ const [Modal, modalApi] = useVbenModal({
} else {
// 新增时重置表单并设置默认值
isUpdate.value = false;
formApi.resetFields();
formApi.resetForm();
formApi.setValues({
id: '',
drug_license_image: '',
@@ -85,7 +85,7 @@ const [Modal, modalApi] = useVbenModal({
}
} else {
// 关闭时重置表单
formApi.resetFields();
formApi.resetForm();
}
},
});

View File

@@ -66,7 +66,7 @@ const [Modal, modalApi] = useVbenModal({
} else {
// 新增时重置表单并设置默认值
isUpdate.value = false;
formApi.resetFields();
formApi.resetForm();
formApi.setValues({
id: '',
name: '',
@@ -79,7 +79,7 @@ const [Modal, modalApi] = useVbenModal({
}
} else {
// 关闭时重置表单
formApi.resetFields();
formApi.resetForm();
}
},
});

View File

@@ -40,7 +40,7 @@ const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (!isOpen) {
formApi.resetFields();
formApi.resetForm();
return;
}
const { values, update } = modalApi.getData<Record<string, any>>() || {};
@@ -48,7 +48,7 @@ const [Modal, modalApi] = useVbenModal({
if (values && update) {
formApi.setValues({ ...values });
} else {
formApi.resetFields();
formApi.resetForm();
formApi.setValues({ sort: 0, status: 1, description: '' });
}
},

View File

@@ -42,7 +42,7 @@ const [Modal, modalApi] = useVbenModal({
async onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (!isOpen) {
formApi.resetFields();
formApi.resetForm();
return;
}
// 打开时拉取功能字典,注入 permissions 下拉
@@ -71,7 +71,7 @@ const [Modal, modalApi] = useVbenModal({
permissions: Array.isArray(values.permissions) ? values.permissions : [],
});
} else {
formApi.resetFields();
formApi.resetForm();
formApi.setValues({
tier_group: 0,
level_weight: 0,

View File

@@ -96,7 +96,7 @@ const [Modal, modalApi] = useVbenModal({
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formApi.resetFields();
formApi.resetForm();
return;
}
const data = modalApi.getData<Record<string, any>>() || {};