From 02672b16bd7fccb96294d10713a3c1eb73bbb9e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Wed, 26 Aug 2026 15:09:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=AC=E8=B4=A6=E5=91=A8=E6=9C=9F?= =?UTF-8?q?=E8=B4=A6=E5=8D=95=E4=B8=8E=E5=90=88=E5=B9=B6=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=8C=E9=80=80=E6=AC=BE=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E4=B8=8E=E5=BC=80=E6=96=B9=E6=88=90=E5=8A=9F=E9=A1=B5=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=8D=A2=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 周期账单生成与变动记录抽屉;查看合并按账单日 Tabs;退款原因必填;开方成功页操作按钮自动换行。 --- .../components/search-time-range-picker.vue | 198 +++++++++ .../src/components/form/types/index.d.ts | 1 + .../public-account-lock/LockOrdersPanel.vue | 352 ++++++++++------ .../components/store-card/StoreCardModal.vue | 30 +- .../store-card/StoreFinancePanel.vue | 394 ++++++++++++++++++ .../components/store-card/StoreNameLink.vue | 55 +++ .../components/table-action/card-actions.vue | 206 +++++++-- .../src/composables/use-fresh-search-time.ts | 119 ++++++ apps/web-antd/src/utils/search-time-range.ts | 169 ++++++++ .../components/SalespersonCommissionPanel.vue | 236 +++++++++++ .../order/prescription/config/search.ts | 10 +- .../order/prescription/config/table.ts | 2 +- .../business/order/prescription/index.vue | 55 ++- .../components/ProductOrderCardList.vue | 56 ++- .../components/ProductOrderExportModal.vue | 6 +- .../order/product-order/components/refund.vue | 118 +++++- .../order/product-order/config/form.ts | 15 +- .../order/product-order/config/search.ts | 10 +- .../business/order/product-order/index.vue | 299 ++++--------- .../business/order/register/config/search.ts | 10 +- .../business/order/register/config/table.ts | 2 +- .../views/business/order/register/index.vue | 65 ++- .../business/product/health-food/index.vue | 10 +- .../business/product/medical-device/index.vue | 10 +- .../views/business/product/non-drug/index.vue | 10 +- .../business/product/service-pack/index.vue | 10 +- .../product/western-medicine/index.vue | 10 +- .../delivery-fee-reallocate-modal.vue | 31 +- .../warehouse-drug-management/admin/index.vue | 9 +- .../doctor/doctor-reception/api/index.ts | 41 ++ .../components/DoctorCreatedPatientModal.vue | 203 +++++++++ .../views/doctor/doctor-reception/index.vue | 233 ++++++++++- .../charge-cash-pay-record/config/search.ts | 8 +- .../finance/charge-cash-pay-record/index.vue | 27 +- .../components/GenerateReportModal.vue | 6 +- .../views/finance/fund-water/config/search.ts | 8 +- .../src/views/finance/fund-water/index.vue | 27 +- .../finance/public-account-pay/api/index.ts | 5 + .../components/apply-modal.vue | 6 +- .../components/daily-change-log-drawer.vue | 103 +++++ .../components/generate-daily-modal.vue | 38 +- .../components/merge-orders-modal.vue | 18 +- .../public-account-pay/config/daily-table.ts | 10 +- .../finance/public-account-pay/index.vue | 41 +- .../views/finance/reconciliation/index.vue | 7 +- .../finance/reconciliation/utils/formCache.ts | 4 +- .../finance/visualization-chart/index.vue | 35 +- .../finance/withdrawal-audit/config/search.ts | 8 +- .../views/finance/withdrawal-audit/index.vue | 27 +- .../log/queue-job/components/QueueJobList.vue | 29 +- .../src/views/log/queue-job/config/search.ts | 11 +- .../views/system/admin/_shared/admin-page.vue | 21 +- .../views/system/admin/_shared/role-meta.ts | 12 +- .../src/views/system/admin/dynamic/index.vue | 25 ++ .../components/bind-list-modal.vue | 71 +++- .../components/modal.vue | 93 +++-- .../delivery-warehouse-drug/config/form.ts | 99 +++-- .../utils/resolve-central-price.ts | 24 ++ .../components/audit-modal.vue | 152 +++---- .../config/table.ts | 26 ++ .../src/views/system/golden-formula/index.vue | 5 +- .../system/menu/components/auth-role.vue | 162 +++++++ apps/web-antd/src/views/system/menu/index.vue | 30 +- .../src/views/system/role/api/index.ts | 61 +++ .../system/role/components/auth-menu.vue | 153 ++----- .../backfill-custom-admin-menu-modal.vue | 89 ++++ .../role/components/backfill-menu-modal.vue | 105 +++++ .../role/components/create-admin-modal.vue | 96 +++++ .../views/system/role/components/modal.vue | 134 +++++- .../system/role/components/role-menu-tree.vue | 146 +++++++ .../role/components/sync-admin-menu-modal.vue | 84 ++++ .../src/views/system/role/config/table.ts | 3 +- apps/web-antd/src/views/system/role/index.vue | 100 ++++- .../src/views/system/store/api/index.ts | 33 ++ .../public-account-pay-config-panel.vue | 23 + 75 files changed, 4243 insertions(+), 897 deletions(-) create mode 100644 apps/web-antd/src/components/form/components/search-time-range-picker.vue create mode 100644 apps/web-antd/src/components/store-card/StoreFinancePanel.vue create mode 100644 apps/web-antd/src/components/store-card/StoreNameLink.vue create mode 100644 apps/web-antd/src/composables/use-fresh-search-time.ts create mode 100644 apps/web-antd/src/utils/search-time-range.ts create mode 100644 apps/web-antd/src/views/business/order/components/SalespersonCommissionPanel.vue create mode 100644 apps/web-antd/src/views/doctor/doctor-reception/components/DoctorCreatedPatientModal.vue create mode 100644 apps/web-antd/src/views/finance/public-account-pay/components/daily-change-log-drawer.vue create mode 100644 apps/web-antd/src/views/system/admin/dynamic/index.vue create mode 100644 apps/web-antd/src/views/system/menu/components/auth-role.vue create mode 100644 apps/web-antd/src/views/system/role/components/backfill-custom-admin-menu-modal.vue create mode 100644 apps/web-antd/src/views/system/role/components/backfill-menu-modal.vue create mode 100644 apps/web-antd/src/views/system/role/components/create-admin-modal.vue create mode 100644 apps/web-antd/src/views/system/role/components/role-menu-tree.vue create mode 100644 apps/web-antd/src/views/system/role/components/sync-admin-menu-modal.vue diff --git a/apps/web-antd/src/components/form/components/search-time-range-picker.vue b/apps/web-antd/src/components/form/components/search-time-range-picker.vue new file mode 100644 index 00000000..68881a28 --- /dev/null +++ b/apps/web-antd/src/components/form/components/search-time-range-picker.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/apps/web-antd/src/components/form/types/index.d.ts b/apps/web-antd/src/components/form/types/index.d.ts index d0126d3a..8c010fa9 100644 --- a/apps/web-antd/src/components/form/types/index.d.ts +++ b/apps/web-antd/src/components/form/types/index.d.ts @@ -5,5 +5,6 @@ export type CustomComponentType = | 'ApiSelect' | 'ApiTreeSelect' | 'IconPicker' + | 'SearchTimeRangePicker' | 'StoreMultiSearch' | 'WarehouseAdminDrugSearch'; diff --git a/apps/web-antd/src/components/public-account-lock/LockOrdersPanel.vue b/apps/web-antd/src/components/public-account-lock/LockOrdersPanel.vue index fd85fc92..f18e2ee4 100644 --- a/apps/web-antd/src/components/public-account-lock/LockOrdersPanel.vue +++ b/apps/web-antd/src/components/public-account-lock/LockOrdersPanel.vue @@ -1,12 +1,13 @@ + + + + diff --git a/apps/web-antd/src/components/store-card/StoreNameLink.vue b/apps/web-antd/src/components/store-card/StoreNameLink.vue new file mode 100644 index 00000000..8e981570 --- /dev/null +++ b/apps/web-antd/src/components/store-card/StoreNameLink.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/apps/web-antd/src/components/table-action/card-actions.vue b/apps/web-antd/src/components/table-action/card-actions.vue index f7193758..28fb6102 100644 --- a/apps/web-antd/src/components/table-action/card-actions.vue +++ b/apps/web-antd/src/components/table-action/card-actions.vue @@ -2,33 +2,24 @@ /** * 卡片视图专用操作栏(与列表交互解耦) * - * 列表单元格里 TableAction 平铺全部按钮没问题,但卡片 footer 空间小, - * 按钮超过 2 个会触发 TableAction 的两列 grid 换行堆叠,显得杂乱。 - * 这里改成 C 端惯例:只露出前 maxVisible 个主操作(默认 1 个,通常是「详情」), - * 其余操作全部合并进「···」下拉菜单。 - * - * 权限(auth)/ 显隐(ifShow)/ 二次确认(popConfirm)逻辑不重写: - * - 挑主操作前先按与 TableAction 相同的规则过滤,保证露出的一定是可见按钮; - * - 渲染仍复用 TableAction(flex=false 关闭多列 grid),菜单项行为完全一致。 + * 列表单元格里 TableAction 平铺全部按钮没问题,但卡片 footer 空间小。 + * 只露出前 maxVisible 个主操作(默认「详情」),其余收进「更多」气泡卡片(Popover)。 */ import type { ActionItem } from './types'; -import { computed } from 'vue'; +import { computed, ref } from 'vue'; import { useAccess } from '@vben/access'; import { isBoolean, isFunction } from '@vben/utils'; -import { Button } from 'ant-design-vue'; +import { Button, Popconfirm, Popover, Space } from 'ant-design-vue'; import { Icon } from '#/components/icon'; -import TableAction from './table-action.vue'; - const props = withDefaults( defineProps<{ actions?: ActionItem[]; dropDownActions?: ActionItem[]; - /** 露出的主操作数量,其余进「···」菜单 */ maxVisible?: number; }>(), { @@ -39,6 +30,7 @@ const props = withDefaults( ); const { hasAccessByCodes } = useAccess(); +const moreOpen = ref(false); /** 与 TableAction 同规则判断单个操作是否可见(auth + ifShow) */ function isActionVisible(action: ActionItem): boolean { @@ -51,38 +43,190 @@ function isActionVisible(action: ActionItem): boolean { return true; } -/** 过滤后的可见操作:前 maxVisible 个露出,其余与原下拉操作合并进「···」 */ +function mapAction(action: ActionItem) { + const { popConfirm } = action; + return { + ...action, + ...popConfirm, + onConfirm: popConfirm?.confirm, + onCancel: popConfirm?.cancel, + enable: !!popConfirm, + }; +} + +/** 过滤后的可见操作:前 maxVisible 个露出,其余合并进「更多」 */ const splitActions = computed(() => { const visible = (props.actions || []).filter((action) => isActionVisible(action), ); + const rest = [ + ...visible.slice(props.maxVisible), + ...(props.dropDownActions || []).filter((action) => isActionVisible(action)), + ].map(mapAction); return { - primary: visible.slice(0, props.maxVisible), - // 下拉里的项 TableAction 会再过滤一遍 auth/ifShow,这里直接透传原始项即可 - rest: [...visible.slice(props.maxVisible), ...(props.dropDownActions || [])], + primary: visible.slice(0, props.maxVisible).map(mapAction), + rest, }; }); + +function getButtonProps(action: ActionItem) { + const res = { + type: action.type || 'link', + size: action.size || 'small', + ...action, + }; + delete (res as any).icon; + delete (res as any).popConfirm; + delete (res as any).onClick; + return res; +} + +function runAction(action: ReturnType) { + if (action.disabled) return; + if (!action.enable && action.onClick) { + action.onClick(); + moreOpen.value = false; + } +} + +function getPopConfirmProps(attrs: Record) { + const originAttrs: any = { ...attrs }; + delete originAttrs.icon; + if (attrs.confirm && isFunction(attrs.confirm)) { + originAttrs.onConfirm = attrs.confirm; + delete originAttrs.confirm; + } + if (attrs.cancel && isFunction(attrs.cancel)) { + originAttrs.onCancel = attrs.cancel; + delete originAttrs.cancel; + } + return originAttrs; +} diff --git a/apps/web-antd/src/composables/use-fresh-search-time.ts b/apps/web-antd/src/composables/use-fresh-search-time.ts new file mode 100644 index 00000000..76d12a73 --- /dev/null +++ b/apps/web-antd/src/composables/use-fresh-search-time.ts @@ -0,0 +1,119 @@ +import type { Ref } from 'vue'; + +import { onActivated, onMounted, ref } from 'vue'; + +import { isSameRange } from '#/utils/search-time-range'; + +type FormApiLike = { + getValues: () => Promise>; + setValues: (values: Record) => Promise; + reset?: () => Promise; +}; + +export type UseFreshSearchTimeOptions = { + formApi: FormApiLike; + searchValues?: Ref>; + fieldName?: string; + /** 返回最新默认区间(字符串或 Dayjs 均可) */ + getDefaultRange: () => unknown; + /** 写入表单并刷新列表/卡片 */ + onRefresh?: (range: unknown) => void | Promise; +}; + +/** + * 搜索时间「本月至今」跨天自动滚到今天 + * - 未手动改时间:onMounted / onActivated 刷新默认区间并触发 onRefresh + * - 用户手选或点快捷预设后:标记 touched,回页不覆盖 + * - resetSearchTime:清 touched + 写最新默认 + onRefresh + */ +export function useFreshSearchTime(options: UseFreshSearchTimeOptions) { + const { + formApi, + searchValues, + fieldName = 'search_time', + getDefaultRange, + onRefresh, + } = options; + + const searchTimeTouched = ref(false); + + async function applyFreshDefaultRange() { + const range = getDefaultRange(); + await formApi.setValues({ [fieldName]: range }); + if (searchValues) { + searchValues.value = { + ...searchValues.value, + [fieldName]: range, + }; + } + return range; + } + + async function refreshIfUntouched() { + if (searchTimeTouched.value) return; + const range = getDefaultRange(); + if (range == null) return; + const applied = await applyFreshDefaultRange(); + await onRefresh?.(applied); + } + + function markSearchTimeTouched() { + searchTimeTouched.value = true; + } + + /** + * 监听表单字段变化:与默认区间不同则视为用户改过 + */ + async function syncTouchedFromForm() { + try { + const values = await formApi.getValues(); + const current = values?.[fieldName]; + const defaults = getDefaultRange(); + if (current == null || current === '') { + return; + } + if (!isSameRange(current, defaults)) { + searchTimeTouched.value = true; + } + } catch { + // 表单未就绪时忽略 + } + } + + async function resetSearchTime() { + searchTimeTouched.value = false; + if (formApi.reset) { + await formApi.reset(); + } + const range = getDefaultRange(); + if (range != null) { + await applyFreshDefaultRange(); + } else if (searchValues) { + try { + const values = await formApi.getValues(); + searchValues.value = { ...(values || {}) }; + } catch { + // ignore + } + } + await onRefresh?.(range); + return range; + } + + onMounted(() => { + void refreshIfUntouched(); + }); + + onActivated(() => { + void refreshIfUntouched(); + }); + + return { + searchTimeTouched, + applyFreshDefaultRange, + refreshIfUntouched, + markSearchTimeTouched, + syncTouchedFromForm, + resetSearchTime, + }; +} diff --git a/apps/web-antd/src/utils/search-time-range.ts b/apps/web-antd/src/utils/search-time-range.ts new file mode 100644 index 00000000..ac55828f --- /dev/null +++ b/apps/web-antd/src/utils/search-time-range.ts @@ -0,0 +1,169 @@ +import type { Dayjs } from 'dayjs'; + +import dayjs from 'dayjs'; + +export type SearchTimePreset = + | 'today' + | 'thisWeek' + | 'thisMonth' + | 'prevMonth' + | 'nextMonth'; + +const YMD = 'YYYY-MM-DD'; + +/** 今天日期(Dayjs) */ +export function todayDayjs(): Dayjs { + return dayjs().startOf('day'); +} + +/** 本周一(国内习惯:周一为周起点) */ +export function startOfWeekMonday(base: Dayjs = todayDayjs()): Dayjs { + const d = base.startOf('day'); + const day = d.day(); + const diff = day === 0 ? 6 : day - 1; + return d.subtract(diff, 'day'); +} + +/** 本月1日 ~ 今天(字符串,供 RangePicker valueFormat) */ +export function monthToTodayRangeString(): [string, string] { + const today = todayDayjs(); + return [today.startOf('month').format(YMD), today.format(YMD)]; +} + +/** 本月1日 ~ 今天(Dayjs) */ +export function monthToTodayRangeDayjs(): [Dayjs, Dayjs] { + const today = todayDayjs(); + return [today.startOf('month'), today]; +} + +/** 近 N 天(含今天),默认 7 天 */ +export function lastNDaysRangeString(days = 7): [string, string] { + const today = todayDayjs(); + const start = today.subtract(Math.max(days - 1, 0), 'day'); + return [start.format(YMD), today.format(YMD)]; +} + +/** 近 N 天(Dayjs) */ +export function lastNDaysRangeDayjs(days = 7): [Dayjs, Dayjs] { + const today = todayDayjs(); + const start = today.subtract(Math.max(days - 1, 0), 'day'); + return [start, today]; +} + +/** 将 Dayjs / 字符串 / 时间戳统一为 YYYY-MM-DD */ +export function normalizeRangeToYmd( + value: unknown, +): [string, string] | null { + if (!value || !Array.isArray(value) || value.length < 2) { + return null; + } + const a = pickYmd(value[0]); + const b = pickYmd(value[1]); + if (!a || !b) return null; + return [a, b]; +} + +function pickYmd(v: unknown): string { + if (v == null) return ''; + if ( + typeof v === 'object' && + v !== null && + 'format' in v && + typeof (v as { format: (s: string) => string }).format === 'function' + ) { + return (v as Dayjs).format(YMD); + } + const d = dayjs(v as string | number); + return d.isValid() ? d.format(YMD) : ''; +} + +/** 比较两段范围是否相同(按 YYYY-MM-DD) */ +export function isSameRange( + a: unknown, + b: unknown, +): boolean { + const na = normalizeRangeToYmd(a); + const nb = normalizeRangeToYmd(b); + if (!na || !nb) return false; + return na[0] === nb[0] && na[1] === nb[1]; +} + +/** 翻月锚点:优先取当前范围开始日,否则今天 */ +export function getMonthAnchorDayjs(current: unknown): Dayjs { + const normalized = normalizeRangeToYmd(current); + if (normalized?.[0]) { + const d = dayjs(normalized[0], YMD); + if (d.isValid()) return d.startOf('day'); + } + return todayDayjs(); +} + +/** 自然月整月,结束日不超过今天 */ +function fullMonthRangeCapped( + month: Dayjs, +): [Dayjs, Dayjs] { + const start = month.startOf('month'); + const end = month.endOf('month').startOf('day'); + const today = todayDayjs(); + return [start, end.isAfter(today) ? today : end]; +} + +/** 按预设计算区间(Dayjs) */ +export function resolveSearchTimePreset( + preset: SearchTimePreset, + current: unknown, +): [Dayjs, Dayjs] { + const today = todayDayjs(); + switch (preset) { + case 'today': + return [today, today]; + case 'thisWeek': + return [startOfWeekMonday(today), today]; + case 'thisMonth': + return [today.startOf('month'), today]; + case 'prevMonth': { + const anchor = getMonthAnchorDayjs(current); + return fullMonthRangeCapped(anchor.subtract(1, 'month')); + } + case 'nextMonth': { + const anchor = getMonthAnchorDayjs(current); + return fullMonthRangeCapped(anchor.add(1, 'month')); + } + default: + return monthToTodayRangeDayjs(); + } +} + +/** 按预设计算区间(字符串) */ +export function resolveSearchTimePresetString( + preset: SearchTimePreset, + current: unknown, +): [string, string] { + const [a, b] = resolveSearchTimePreset(preset, current); + return [a.format(YMD), b.format(YMD)]; +} + +/** Dayjs 区间转字符串 */ +export function rangeDayjsToString( + range: [Dayjs, Dayjs], +): [string, string] { + return [range[0].format(YMD), range[1].format(YMD)]; +} + +/** 字符串区间转 Dayjs */ +export function rangeStringToDayjs( + range: [string, string], +): [Dayjs, Dayjs] { + return [dayjs(range[0], YMD), dayjs(range[1], YMD)]; +} + +/** 将区间按 valueFormat 输出 */ +export function formatRangeByValueFormat( + range: [Dayjs, Dayjs], + valueFormat?: string, +): [string, string] | [Dayjs, Dayjs] { + if (valueFormat === YMD) { + return rangeDayjsToString(range); + } + return range; +} diff --git a/apps/web-antd/src/views/business/order/components/SalespersonCommissionPanel.vue b/apps/web-antd/src/views/business/order/components/SalespersonCommissionPanel.vue new file mode 100644 index 00000000..36841964 --- /dev/null +++ b/apps/web-antd/src/views/business/order/components/SalespersonCommissionPanel.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/apps/web-antd/src/views/business/order/prescription/config/search.ts b/apps/web-antd/src/views/business/order/prescription/config/search.ts index fb571765..20092b09 100644 --- a/apps/web-antd/src/views/business/order/prescription/config/search.ts +++ b/apps/web-antd/src/views/business/order/prescription/config/search.ts @@ -1,7 +1,7 @@ import type { VbenFormProps } from '#/adapter/form'; -import dayjs from 'dayjs'; import { getOrderPrescriptionTypeOption } from '#/views/business/order/product-order/api'; +import { monthToTodayRangeString } from '#/utils/search-time-range'; import { PRESCRIPTION_IS_ONLINE_OPTIONS } from './constants'; @@ -21,16 +21,12 @@ export const formOptions: VbenFormProps = { schema: [ { // 放第一位:时间范围有默认值(本月至今),默认收起时必须始终可见,否则用户不知道当前查的是哪段数据 - component: 'RangePicker', + component: 'SearchTimeRangePicker', componentProps: { format: 'YYYY-MM-DD', - // 提交纯日期,后端 getWhereBetween 会把结束日扩到当天 23:59:59 valueFormat: 'YYYY-MM-DD', }, - defaultValue: [ - dayjs().startOf('month').format('YYYY-MM-DD'), - dayjs().format('YYYY-MM-DD'), - ], + defaultValue: monthToTodayRangeString(), fieldName: 'search_time', label: '时间范围', }, diff --git a/apps/web-antd/src/views/business/order/prescription/config/table.ts b/apps/web-antd/src/views/business/order/prescription/config/table.ts index a9a2df25..84edd663 100644 --- a/apps/web-antd/src/views/business/order/prescription/config/table.ts +++ b/apps/web-antd/src/views/business/order/prescription/config/table.ts @@ -27,7 +27,7 @@ export const gridOptions: VxeGridProps = { { field: 'prescription_no', align: 'left', title: '处方订单号' }, { field: 'doctor_info.name', align: 'left', title: '开方医生' }, { field: 'user_patient.name', align: 'left', title: '就诊人名称' }, - { field: 'store.name', align: 'left', title: '开方诊所', slots: { default: 'store-name' } }, + { field: 'store.name', align: 'left', title: '开方诊所', minWidth: 140, slots: { default: 'store-name' } }, { field: 'status', title: '状态', slots: { default: 'status' } }, { field: 'is_online', diff --git a/apps/web-antd/src/views/business/order/prescription/index.vue b/apps/web-antd/src/views/business/order/prescription/index.vue index 1ad0318c..8e8e9e1e 100644 --- a/apps/web-antd/src/views/business/order/prescription/index.vue +++ b/apps/web-antd/src/views/business/order/prescription/index.vue @@ -17,9 +17,12 @@ import { Button, Tabs, Tag } from 'ant-design-vue'; import { useVbenForm } from '#/adapter/form'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; +import { useFreshSearchTime } from '#/composables/use-fresh-search-time'; import { TableAction } from '#/components/table-action'; +import { monthToTodayRangeString } from '#/utils/search-time-range'; import { useViewMode, ViewModeSwitch } from '#/components/view-mode-switch'; import StoreCardModal from '#/components/store-card/StoreCardModal.vue'; +import StoreNameLink from '#/components/store-card/StoreNameLink.vue'; import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue'; import { getPrescriptionListApi } from './api'; @@ -60,7 +63,11 @@ function collectDefaultSearchValues(): Record { const values: Record = {}; for (const item of formOptions.schema ?? []) { if (item.fieldName && item.defaultValue !== undefined) { - values[item.fieldName] = item.defaultValue; + if (item.fieldName === 'search_time') { + values[item.fieldName] = monthToTodayRangeString(); + } else { + values[item.fieldName] = item.defaultValue; + } } } return values; @@ -69,6 +76,11 @@ function collectDefaultSearchValues(): Record { /** 列表与卡片共用的搜索条件(不含 status Tab,Tab 单独合并) */ const searchValues = ref>(collectDefaultSearchValues()); +const freshSearchTimeBridge = { + resetSearchTime: async () => {}, + markSearchTimeTouched: () => {}, +}; + /** * 读搜索表单现值(不要只用 handleSubmit 快照) * 抽出独立 SearchForm 后,列表 query 必须自己取表单值,否则点查询/翻页会丢掉刚填的条件 @@ -95,6 +107,11 @@ const cardFormValues = computed(() => ({ */ const [SearchForm, searchFormApi] = useVbenForm({ ...formOptions, + handleValuesChange(_values, fieldsChanged) { + if (fieldsChanged?.includes('search_time')) { + freshSearchTimeBridge.markSearchTimeTouched(); + } + }, handleSubmit: async () => { searchValues.value = await readLiveSearchValues(); if (viewMode.value === 'list') { @@ -102,12 +119,7 @@ const [SearchForm, searchFormApi] = useVbenForm({ } }, handleReset: async () => { - // 自定义 handleReset 会接管默认行为,必须手动还原控件到 defaultValue(reset 为新 API,resetForm 已弃用) - await searchFormApi.reset(); - searchValues.value = collectDefaultSearchValues(); - if (viewMode.value === 'list') { - gridApi.reload(); - } + await freshSearchTimeBridge.resetSearchTime(); }, }); @@ -144,6 +156,20 @@ const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, }); +const freshSearchTime = useFreshSearchTime({ + formApi: searchFormApi, + searchValues, + getDefaultRange: () => monthToTodayRangeString(), + onRefresh: async () => { + searchValues.value = await readLiveSearchValues(); + if (viewMode.value === 'list') { + gridApi.reload(); + } + }, +}); +freshSearchTimeBridge.resetSearchTime = freshSearchTime.resetSearchTime; +freshSearchTimeBridge.markSearchTimeTouched = freshSearchTime.markSearchTimeTouched; + // Tab 切换:列表模式重查(卡片模式由 cardFormValues computed 触发 CardList watch) watch(statusTab, () => { if (viewMode.value === 'list') { @@ -223,16 +249,11 @@ function rowIsOnlineMeta(row: Record) {