From 98eb751392d07b9933a648b2b07246c3e1f69235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Mon, 19 Oct 2026 16:20:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20GlassCard=E5=8F=8C=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E3=80=81=E6=95=B0=E6=8D=AE=E7=9C=8B=E6=9D=BF=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E3=80=81=E5=9C=B0=E5=9D=80=E7=B2=98=E8=B4=B4=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E3=80=81AI=E5=B9=B3=E5=8F=B0=E4=B8=8E=E7=B4=A0=E6=9D=90?= =?UTF-8?q?=E5=BA=93=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/Code-Standards.mdc | 20 + apps/desktop/package.json | 2 +- apps/web-antd/src/api/core/file-gallery.ts | 98 +- .../address-paste-block/AddressPasteBlock.vue | 516 ++ .../form/components/api-log-route-picker.vue | 347 + .../form/components/api-log-user-picker.vue | 409 +- .../src/components/glass-card/GlassCard.vue | 299 + .../components/glass-card/GlassCardList.vue | 188 + .../src/components/glass-card/index.ts | 7 + .../src/components/glass-tag/GlassTag.vue | 342 + .../src/components/glass-tag/index.ts | 5 + .../components/store-card/StoreCardModal.vue | 181 +- .../store-card/StoreFinancePanel.vue | 462 +- .../store-card/config/account-change-table.ts | 69 + .../store-card/config/settlement-table.ts | 117 + .../components/store-card/overlay-z-index.ts | 14 + .../components/table-action/card-actions.vue | 429 +- .../components/table-action/table-action.vue | 28 +- .../src/components/table-action/types.d.ts | 7 + .../src/composables/use-dual-view-search.ts | 113 + .../composables/use-file-gallery-filter.ts | 30 +- apps/web-antd/src/layouts/basic.vue | 27 +- apps/web-antd/src/util/address-dict-api.ts | 15 + apps/web-antd/src/util/address-index.ts | 95 +- .../web-antd/src/util/address-recognizer.d.ts | 34 + apps/web-antd/src/util/address-recognizer.js | 843 +++ apps/web-antd/src/util/region-names.json | 5587 +++++++++++++++++ apps/web-antd/src/util/ship-address-copy.ts | 64 + .../business/chat/components/ChatArea.vue | 158 +- .../business/chat/components/ChatHeader.vue | 206 +- .../chat/components/CustomTextarea.vue | 166 +- .../chat/components/MessageBubble.vue | 354 +- .../business/chat/components/MessageInput.vue | 289 +- .../business/chat/components/MessageList.vue | 37 +- .../business/chat/stores/layoutDensity.ts | 72 + .../business/clinic-salesperson/earnings.vue | 87 +- .../business/clinic-salesperson/index.vue | 183 +- .../business/order/prescription/api/index.ts | 11 + .../components/PrescriptionOrderCardList.vue | 125 +- .../components/change-status-modal.vue | 91 + .../order/prescription/config/constants.ts | 29 + .../order/prescription/config/search.ts | 30 +- .../business/order/prescription/index.vue | 114 +- .../components/ProductOrderCardList.vue | 164 +- .../order/product-order/components/detail.vue | 177 +- .../components/logistics-modal.vue | 130 +- .../order/product-order/components/modal.vue | 96 +- .../order/product-order/config/form.ts | 12 +- .../business/order/product-order/index.vue | 1100 ++-- .../business/order/register/api/index.ts | 10 + .../components/RegisterOrderCardList.vue | 180 +- .../components/reopen-consulting-modal.vue | 84 + .../business/order/register/config/search.ts | 14 +- .../views/business/order/register/index.vue | 109 +- .../views/dashboard/analytics/api/index.ts | 14 + .../analytics/components/RankList.vue | 164 + .../src/views/dashboard/analytics/index.vue | 1282 +++- .../doctor/doctor-reception/api/index.ts | 293 +- .../components/ChineseMedicineConfig.vue | 429 +- .../components/CreateUserPatientModal.vue | 1021 ++- .../components/DoctorCreatedPatientModal.vue | 123 +- .../EditCreatedUserPatientModal.vue | 528 ++ .../components/ImageOcrDrawer.vue | 781 ++- .../views/doctor/doctor-reception/index.vue | 4171 ++++++------ .../components/QuickReplyBubbles.vue | 70 +- .../components/OrderListFloatButton.vue | 41 +- .../components/QuickReplyBubbles.vue | 71 +- .../components/InvoiceCenterCardList.vue | 193 + .../invoice-center/config/constants.ts | 34 + .../finance/invoice-center/config/table.ts | 31 +- .../views/finance/invoice-center/index.vue | 333 +- .../components/MyInvoiceCardList.vue | 141 + .../finance/my-invoice/config/constants.ts | 18 + .../views/finance/my-invoice/config/table.ts | 103 +- .../src/views/finance/my-invoice/index.vue | 170 +- .../config/daily-table.ts | 178 +- .../public-account-confirm/config/table.ts | 115 +- .../finance/public-account-confirm/index.vue | 304 +- .../finance/public-account-pay/api/index.ts | 23 +- .../components/PublicAccountDailyCardList.vue | 172 + .../components/PublicAccountOrderCardList.vue | 151 + .../components/apply-modal.vue | 22 +- .../components/confirm-modal.vue | 32 +- .../components/daily-change-log-drawer.vue | 32 +- .../components/daily-extend-modal.vue | 279 + .../public-account-pay/config/constants.ts | 64 + .../public-account-pay/config/daily-table.ts | 186 +- .../public-account-pay/config/table.ts | 118 +- .../finance/public-account-pay/index.vue | 642 +- .../finance/visualization-chart/api/index.ts | 5 +- .../components/KpiGlassCard.vue | 252 + .../components/StoreRankPanel.vue | 218 + .../finance/visualization-chart/index.vue | 802 ++- .../visualization-chart/utils/chart-theme.ts | 83 + .../components/settlement-detail-drawer.vue | 23 +- .../config/account-change-search.ts | 14 +- .../views/log/api-log-history/api/index.ts | 20 + .../log/api-log-history/components/modal.vue | 154 + .../log/api-log-history/config/search.ts | 72 + .../views/log/api-log-history/config/table.ts | 100 + .../src/views/log/api-log-history/index.vue | 272 + .../src/views/log/api-log/api/index.ts | 10 +- .../api-log/components/ApiOpLogCardList.vue | 288 + .../views/log/api-log/components/modal.vue | 130 +- .../src/views/log/api-log/config/search.ts | 51 +- .../src/views/log/api-log/config/table.ts | 56 +- apps/web-antd/src/views/log/api-log/index.vue | 249 +- .../src/views/log/api-log/utils/filters.ts | 14 + .../src/views/log/api-route-map/api/index.ts | 49 + .../components/ApiRouteMapCardList.vue | 286 + .../log/api-route-map/components/modal.vue | 80 + .../log/api-route-map/config/constants.ts | 31 + .../views/log/api-route-map/config/form.ts | 86 + .../views/log/api-route-map/config/search.ts | 87 + .../views/log/api-route-map/config/table.ts | 71 + .../src/views/log/api-route-map/index.vue | 223 + .../log/old-api-log/components/modal.vue | 18 +- .../src/views/log/old-api-log/config/table.ts | 6 +- .../src/views/log/old-api-log/index.vue | 7 +- .../src/views/log/op-log/components/modal.vue | 16 +- .../src/views/log/op-log/config/table.ts | 6 +- apps/web-antd/src/views/log/op-log/index.vue | 12 +- apps/web-antd/src/views/notice/api/index.ts | 24 +- .../notice/canvas-icon/NoticeCanvasIcon.vue | 135 + .../src/views/notice/canvas-icon/drawers.ts | 829 +++ .../src/views/notice/canvas-icon/ticker.ts | 29 + apps/web-antd/src/views/notice/index.vue | 549 +- .../ai/generation/components/detail-modal.vue | 93 +- .../src/views/system/ai/generation/index.vue | 540 +- .../components/ImageOcrCardList.vue | 221 + .../system/ai/image-ocr-record/index.vue | 185 +- .../ai/platform/components/model-modal.vue | 78 +- .../platform/components/platform-detail.vue | 310 +- .../views/system/ai/platform/config/form.ts | 4 +- .../platform/config/provider-capabilities.ts | 61 + .../src/views/system/ai/platform/index.vue | 190 +- .../components/modal.vue | 70 +- .../delivery-warehouse-order/config/form.ts | 12 +- .../components/file-detail-drawer.vue | 184 +- .../components/folder-sidebar.vue | 5 +- .../src/views/system/file-gallery/index.vue | 1517 ++++- .../src/views/system/hy-open-app/index.vue | 7 +- .../src/views/system/menu/api/index.ts | 55 +- .../system/menu/components/MenuCardList.vue | 568 ++ .../menu/components/menu-kind-pick-card.vue | 448 ++ .../views/system/menu/components/modal.vue | 534 +- .../menu/components/move-menu-modal.vue | 320 + .../src/views/system/menu/config/constants.ts | 42 + .../src/views/system/menu/config/form.ts | 178 +- .../src/views/system/menu/config/search.ts | 93 +- .../src/views/system/menu/config/table.ts | 5 +- apps/web-antd/src/views/system/menu/index.vue | 291 +- .../src/views/system/region/index.vue | 19 +- .../components/CompareTrendChart.vue | 77 +- .../components/DashboardStatCard.vue | 48 +- .../components/InactiveStoreWarningList.vue | 76 +- .../components/InputCountTrendChart.vue | 84 +- .../components/RankingList.vue | 62 +- .../components/RegionDistributionChart.vue | 88 +- .../components/StoreAmountRankingList.vue | 71 +- .../system/salesperson-dashboard/index.vue | 251 +- .../src/views/system/store/api/index.ts | 86 +- .../components/ai-model-config-panel.vue | 156 +- .../components/dashboard-config-panel.vue | 114 + .../config/dashboard-templates.ts | 30 + .../src/views/system/system-config/index.vue | 6 + .../src/views/system/vip/duration/index.vue | 85 +- .../feature/components/bind-levels-modal.vue | 82 +- .../src/views/system/vip/feature/index.vue | 93 +- .../src/views/system/vip/level/index.vue | 74 +- .../record/components/VipRecordCardList.vue | 157 + .../views/system/vip/record/config/search.ts | 15 +- .../views/system/vip/record/config/table.ts | 85 +- .../src/views/system/vip/record/index.vue | 48 +- .../vip/store/components/VipStoreCardList.vue | 135 + .../views/system/vip/store/config/search.ts | 12 +- .../views/system/vip/store/config/table.ts | 118 +- .../src/views/system/vip/store/index.vue | 85 +- lefthook.yml | 80 +- package.json | 2 +- 180 files changed, 32500 insertions(+), 7812 deletions(-) create mode 100644 apps/web-antd/src/components/address-paste-block/AddressPasteBlock.vue create mode 100644 apps/web-antd/src/components/form/components/api-log-route-picker.vue create mode 100644 apps/web-antd/src/components/glass-card/GlassCard.vue create mode 100644 apps/web-antd/src/components/glass-card/GlassCardList.vue create mode 100644 apps/web-antd/src/components/glass-card/index.ts create mode 100644 apps/web-antd/src/components/glass-tag/GlassTag.vue create mode 100644 apps/web-antd/src/components/glass-tag/index.ts create mode 100644 apps/web-antd/src/components/store-card/config/account-change-table.ts create mode 100644 apps/web-antd/src/components/store-card/config/settlement-table.ts create mode 100644 apps/web-antd/src/components/store-card/overlay-z-index.ts create mode 100644 apps/web-antd/src/composables/use-dual-view-search.ts create mode 100644 apps/web-antd/src/util/address-dict-api.ts create mode 100644 apps/web-antd/src/util/address-recognizer.d.ts create mode 100644 apps/web-antd/src/util/address-recognizer.js create mode 100644 apps/web-antd/src/util/region-names.json create mode 100644 apps/web-antd/src/util/ship-address-copy.ts create mode 100644 apps/web-antd/src/views/business/chat/stores/layoutDensity.ts create mode 100644 apps/web-antd/src/views/business/order/prescription/components/change-status-modal.vue create mode 100644 apps/web-antd/src/views/business/order/register/components/reopen-consulting-modal.vue create mode 100644 apps/web-antd/src/views/dashboard/analytics/api/index.ts create mode 100644 apps/web-antd/src/views/dashboard/analytics/components/RankList.vue create mode 100644 apps/web-antd/src/views/doctor/doctor-reception/components/EditCreatedUserPatientModal.vue create mode 100644 apps/web-antd/src/views/finance/invoice-center/components/InvoiceCenterCardList.vue create mode 100644 apps/web-antd/src/views/finance/invoice-center/config/constants.ts create mode 100644 apps/web-antd/src/views/finance/my-invoice/components/MyInvoiceCardList.vue create mode 100644 apps/web-antd/src/views/finance/my-invoice/config/constants.ts create mode 100644 apps/web-antd/src/views/finance/public-account-pay/components/PublicAccountDailyCardList.vue create mode 100644 apps/web-antd/src/views/finance/public-account-pay/components/PublicAccountOrderCardList.vue create mode 100644 apps/web-antd/src/views/finance/public-account-pay/components/daily-extend-modal.vue create mode 100644 apps/web-antd/src/views/finance/visualization-chart/components/KpiGlassCard.vue create mode 100644 apps/web-antd/src/views/finance/visualization-chart/components/StoreRankPanel.vue create mode 100644 apps/web-antd/src/views/finance/visualization-chart/utils/chart-theme.ts create mode 100644 apps/web-antd/src/views/log/api-log-history/api/index.ts create mode 100644 apps/web-antd/src/views/log/api-log-history/components/modal.vue create mode 100644 apps/web-antd/src/views/log/api-log-history/config/search.ts create mode 100644 apps/web-antd/src/views/log/api-log-history/config/table.ts create mode 100644 apps/web-antd/src/views/log/api-log-history/index.vue create mode 100644 apps/web-antd/src/views/log/api-log/components/ApiOpLogCardList.vue create mode 100644 apps/web-antd/src/views/log/api-log/utils/filters.ts create mode 100644 apps/web-antd/src/views/log/api-route-map/api/index.ts create mode 100644 apps/web-antd/src/views/log/api-route-map/components/ApiRouteMapCardList.vue create mode 100644 apps/web-antd/src/views/log/api-route-map/components/modal.vue create mode 100644 apps/web-antd/src/views/log/api-route-map/config/constants.ts create mode 100644 apps/web-antd/src/views/log/api-route-map/config/form.ts create mode 100644 apps/web-antd/src/views/log/api-route-map/config/search.ts create mode 100644 apps/web-antd/src/views/log/api-route-map/config/table.ts create mode 100644 apps/web-antd/src/views/log/api-route-map/index.vue create mode 100644 apps/web-antd/src/views/notice/canvas-icon/NoticeCanvasIcon.vue create mode 100644 apps/web-antd/src/views/notice/canvas-icon/drawers.ts create mode 100644 apps/web-antd/src/views/notice/canvas-icon/ticker.ts create mode 100644 apps/web-antd/src/views/system/ai/image-ocr-record/components/ImageOcrCardList.vue create mode 100644 apps/web-antd/src/views/system/ai/platform/config/provider-capabilities.ts create mode 100644 apps/web-antd/src/views/system/menu/components/MenuCardList.vue create mode 100644 apps/web-antd/src/views/system/menu/components/menu-kind-pick-card.vue create mode 100644 apps/web-antd/src/views/system/menu/components/move-menu-modal.vue create mode 100644 apps/web-antd/src/views/system/menu/config/constants.ts create mode 100644 apps/web-antd/src/views/system/system-config/components/dashboard-config-panel.vue create mode 100644 apps/web-antd/src/views/system/system-config/config/dashboard-templates.ts create mode 100644 apps/web-antd/src/views/system/vip/record/components/VipRecordCardList.vue create mode 100644 apps/web-antd/src/views/system/vip/store/components/VipStoreCardList.vue diff --git a/.cursor/rules/Code-Standards.mdc b/.cursor/rules/Code-Standards.mdc index ced6a183..28434c60 100644 --- a/.cursor/rules/Code-Standards.mdc +++ b/.cursor/rules/Code-Standards.mdc @@ -44,6 +44,26 @@ alwaysApply: true - HTTP 请求统一用 `requestClient`(来自 `#/api/request`),**禁止直接 axios / fetch** - 行操作和工具栏按钮统一用 `` 组件(来自 `#/components/table-action`) - 业务选择器(员工、医生、门店、推广员、地区等)必须复用 `#/components/form/components/*-picker.vue`,不要重写 +- **卡片统一用液态玻璃组件**:新增/改动的卡片一律用 `GlassCard`(`#/components/glass-card`),禁止再手写一套 `backdrop-filter` 玻璃样式或新起 antd `Card`;历史页面暂不强制回改 +- 列表页需要「列表/卡片」双视图时:`useViewMode` + `ViewModeSwitch`(`#/components/view-mode-switch`)切换,卡片模式用 `GlassCardList`(分页拉数)+ `#card` 插槽里放 `GlassCard`;搜索表单抽到页面顶部两种视图共用(参考 `business/order/product-order/index.vue`) +- **GlassCard 底部操作按钮(强制)**:统一用 `CardActions`(`#/components/table-action`),默认即玻璃胶囊(`variant` 默认 `glass`:半透明底 + blur + 1px 同色系边框微光;主操作 primary、危险 destructive);布局默认 `layout="fill"` 等分沾满 footer,需要居中时显式 `layout="center"`;尺寸用 `size`:`xs` 超级小 / `sm` 小 / `md` 中(默认) / `lg` 大 / `half` 沾满一半 / `full` 沾满整行。放在 `GlassCard` 的 `#footer` 插槽里,**禁止**再在卡片里用 antd `Button` / `TableAction` 的 link 文字链或灰框 default 按钮;禁止包一层 `flex justify-end` 把按钮挤到一角。示例: + + ```vue + + + + + ``` ## API 层规范 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index e1f3ed36..7cd20076 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@vben/desktop", - "version": "0.1.1", + "version": "0.1.3", "private": true, "description": "萧康云医 PC 桌面端(Electron 壳,打包加载 web-antd 生产构建产物)", "author": "萧康云医", diff --git a/apps/web-antd/src/api/core/file-gallery.ts b/apps/web-antd/src/api/core/file-gallery.ts index 480f6850..52cb757c 100644 --- a/apps/web-antd/src/api/core/file-gallery.ts +++ b/apps/web-antd/src/api/core/file-gallery.ts @@ -30,9 +30,26 @@ export interface FileTypeItem { sort: number; } +/** 扫描写入的引用位置(详情展示,含表/字段注释) */ +export interface FileGalleryRefLoc { + connection: string; + connection_text: string; + table: string; + /** 表 COMMENT,可能为空 */ + table_comment?: string; + column: string; + /** 字段 COMMENT,可能为空 */ + column_comment?: string; + count: number; + label: string; +} + export interface FileGalleryDetail extends FileGalleryItem { source_text?: string; updated_at?: string; + ref_scanned_at?: number | string; + /** 引用位置列表,未扫描或未命中为空数组 */ + ref_locs?: FileGalleryRefLoc[]; } export interface FileGalleryStats { @@ -43,6 +60,8 @@ export interface FileGalleryStats { recyclable_size: number; recyclable_size_text: string; has_scanned: boolean; + /** 上次 OSS 同步完成日期 YYYYMMDD,空表示尚未同步 */ + oss_last_sync_date?: string; } export interface FileGalleryListParams { @@ -53,6 +72,9 @@ export interface FileGalleryListParams { group_id?: number; keyword?: string; exclude_qrcode?: number; + /** all|referenced|unreferenced */ + ref_filter?: string; + /** @deprecated 用 ref_filter=unreferenced */ unref_only?: number; folder_id?: number; unfiled?: number; @@ -97,31 +119,36 @@ export async function getFileTypes() { return requestClient.get(`${prefix}file-types`); } -/** 规划 OSS 同步日期分组(full 全部 / incremental 从库内最新日期起) */ -export async function planFileGalleryOssSync(data?: { mode?: 'full' | 'incremental' }) { +/** 规划 OSS 同步日期分组(full 全部 / incremental 从上次同步日期起) */ +export async function planFileGalleryOssSync(data?: { + mode?: 'full' | 'incremental'; +}) { return requestClient.post<{ + last_sync_date: string; mode: 'full' | 'incremental'; prefixes: string[]; - total: number; since_date: string; + total: number; }>(`${prefix}sync-oss-plan`, data ?? {}); } -/** 从 OSS 同步一批(按日期 prefix + marker;全部组结束后 finalize=1) */ +/** 从 OSS 同步一批(按日期 prefix + marker;全部组结束后 finalize=1 并落库 last_sync_date) */ export async function syncFileGalleryFromOss(data?: { + finalize?: number; + last_sync_date?: string; limit?: number; marker?: string; prefix?: string; - finalize?: number; }) { return requestClient.post<{ + done: boolean; + folder_created: number; + last_sync_date?: string; + next_marker: string; + prefix: string; scanned: number; skipped: number; synced: number; - next_marker: string; - done: boolean; - folder_created: number; - prefix: string; }>(`${prefix}sync-oss`, data ?? {}); } @@ -157,18 +184,57 @@ export async function markFileQrcode(id: number, is_qrcode: number) { ); } -/** 扫描业务表 URL 引用(两库全表文本列) */ -export async function scanFileGalleryRefs() { +/** 扫描计划中的单表:pending 未开始 / scanning 扫描中 / done 完成 */ +export interface FileRefScanTableItem { + connection: string; + table: string; + /** 兼容字段,等同 total_columns */ + columns: number; + total_columns?: number; + done_columns?: number; + failed: number; + status?: 'done' | 'pending' | 'scanning'; +} + +/** 预览将扫描的表清单(全部 pending,不开始扫) */ +export async function planFileGalleryScanRefs() { + return requestClient.get<{ + column_total: number; + plan_tables: FileRefScanTableItem[]; + table_count: number; + }>(`${prefix}scan-refs-plan`); +} + +/** 分批扫描业务表 URL 引用;循环传 job_id 直到 done,支持断点续扫 */ +export async function scanFileGalleryRefs(data?: { + batch_size?: number; + job_id?: string; +}) { return requestClient.post<{ - elapsed_ms: number; + batch_size: number; + batch_tables?: FileRefScanTableItem[]; + current_table?: string; + done: boolean; + done_table_count?: number; + elapsed_ms?: number; + job_id: string; mysql_columns: number; mysql_failed: number; old_mysql_columns: number; old_mysql_failed: number; - referenced: number; - scanned_files: number; - unreferenced: number; - }>(`${prefix}scan-refs`); + pending_table_count?: number; + phase: string; + plan_tables?: FileRefScanTableItem[]; + processed: number; + progress: number; + referenced?: number; + scanned_files?: number; + scanned_tables?: FileRefScanTableItem[]; + scanning_table_count?: number; + table_count?: number; + total: number; + unreferenced?: number; + }>(`${prefix}scan-refs`, data ?? {}); } /** 批量加入分组(移动到文件夹) */ diff --git a/apps/web-antd/src/components/address-paste-block/AddressPasteBlock.vue b/apps/web-antd/src/components/address-paste-block/AddressPasteBlock.vue new file mode 100644 index 00000000..ecbc8762 --- /dev/null +++ b/apps/web-antd/src/components/address-paste-block/AddressPasteBlock.vue @@ -0,0 +1,516 @@ + + + + + diff --git a/apps/web-antd/src/components/form/components/api-log-route-picker.vue b/apps/web-antd/src/components/form/components/api-log-route-picker.vue new file mode 100644 index 00000000..3729c454 --- /dev/null +++ b/apps/web-antd/src/components/form/components/api-log-route-picker.vue @@ -0,0 +1,347 @@ + + + + + + + diff --git a/apps/web-antd/src/components/form/components/api-log-user-picker.vue b/apps/web-antd/src/components/form/components/api-log-user-picker.vue index 1dab28d8..17266ac7 100644 --- a/apps/web-antd/src/components/form/components/api-log-user-picker.vue +++ b/apps/web-antd/src/components/form/components/api-log-user-picker.vue @@ -1,8 +1,13 @@ + + + + diff --git a/apps/web-antd/src/components/glass-card/GlassCardList.vue b/apps/web-antd/src/components/glass-card/GlassCardList.vue new file mode 100644 index 00000000..5759388b --- /dev/null +++ b/apps/web-antd/src/components/glass-card/GlassCardList.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/apps/web-antd/src/components/glass-card/index.ts b/apps/web-antd/src/components/glass-card/index.ts new file mode 100644 index 00000000..510fe494 --- /dev/null +++ b/apps/web-antd/src/components/glass-card/index.ts @@ -0,0 +1,7 @@ +/** + * 液态玻璃卡片组件出口 + * - GlassCard:卡片外壳(新增/改动的卡片统一使用) + * - GlassCardList:卡片视图列表容器(列表/卡片双视图的 card 模式) + */ +export { default as GlassCard } from './GlassCard.vue'; +export { default as GlassCardList } from './GlassCardList.vue'; diff --git a/apps/web-antd/src/components/glass-tag/GlassTag.vue b/apps/web-antd/src/components/glass-tag/GlassTag.vue new file mode 100644 index 00000000..2b1e3e22 --- /dev/null +++ b/apps/web-antd/src/components/glass-tag/GlassTag.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/apps/web-antd/src/components/glass-tag/index.ts b/apps/web-antd/src/components/glass-tag/index.ts new file mode 100644 index 00000000..010be95e --- /dev/null +++ b/apps/web-antd/src/components/glass-tag/index.ts @@ -0,0 +1,5 @@ +/** + * 液态玻璃标签组件出口 + * - GlassTag:紧凑芯片(对照药名、状态标签等) + */ +export { default as GlassTag } from './GlassTag.vue'; diff --git a/apps/web-antd/src/components/store-card/StoreCardModal.vue b/apps/web-antd/src/components/store-card/StoreCardModal.vue index 76c4f3f8..73b882b1 100644 --- a/apps/web-antd/src/components/store-card/StoreCardModal.vue +++ b/apps/web-antd/src/components/store-card/StoreCardModal.vue @@ -5,12 +5,11 @@ */ import type { Dayjs } from 'dayjs'; -import { computed, ref } from 'vue'; +import { computed, onBeforeUnmount, ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { useUserStore } from '@vben/stores'; -import { monthToTodayRangeDayjs } from '#/utils/search-time-range'; import { Avatar, Button, @@ -18,16 +17,23 @@ import { Descriptions, Empty, Image, + message, Select, Spin, Table, Tabs, Tag, - message, } from 'ant-design-vue'; +import StoreFinancePanel from '#/components/store-card/StoreFinancePanel.vue'; +import { monthToTodayRangeDayjs } from '#/utils/search-time-range'; import DoctorCardModal from '#/views/doctor/doctor/components/DoctorCardModal.vue'; +import { + canAddStoreDoctor, + isPlatformSuperAdmin, +} from '#/views/system/admin/_shared/platform-admin-role'; import { getStoreBankCardReportDetail } from '#/views/system/store-bank-card/api'; +import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser'; import { getStoreCardApi, getStoreCardStatsApi, @@ -35,20 +41,32 @@ import { openQrCodeApi, } from '#/views/system/store/api'; import { getSalespersonList } from '#/views/system/store/api/salesperson'; -import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser'; import AddDoctorFromStoreModal from '#/views/system/store/components/AddDoctorFromStoreModal.vue'; -import { canAddStoreDoctor, isPlatformSuperAdmin } from '#/views/system/admin/_shared/platform-admin-role'; -import StoreFinancePanel from '#/components/store-card/StoreFinancePanel.vue'; + +import { + popupToBody, + STORE_CARD_BODY_CLASS, + STORE_CARD_OVERLAY_Z_INDEX, +} from './overlay-z-index'; const RangePicker = DatePicker.RangePicker; +/** 打开期间给 body 打标,抬高挂到 body 的预览/下拉层级 */ +function setStoreCardBodyOpen(open: boolean) { + document.body.classList.toggle(STORE_CARD_BODY_CLASS, open); +} + const userStore = useUserStore(); // 是否允许「添加医生」:超管/管理员/省经理/市经理/业务员 const canAddDoctor = computed(() => canAddStoreDoctor(userStore.userInfo)); // 是否可查看门店资金账户:仅超管/系统管理员 -const canViewStoreFinance = computed(() => isPlatformSuperAdmin(userStore.userInfo)); +const canViewStoreFinance = computed(() => + isPlatformSuperAdmin(userStore.userInfo), +); -const financePanelRef = ref | null>(null); +const financePanelRef = ref | null>( + null, +); const storeId = ref(0); const loading = ref(false); @@ -92,8 +110,18 @@ const drugTypeOptions = [ ]; const drugColumns = [ - { title: '药品名称', dataIndex: 'drug_name', key: 'drug_name', ellipsis: true }, - { title: '规格', dataIndex: 'specification', key: 'specification', width: 140 }, + { + title: '药品名称', + dataIndex: 'drug_name', + key: 'drug_name', + ellipsis: true, + }, + { + title: '规格', + dataIndex: 'specification', + key: 'specification', + width: 140, + }, { title: '售价', dataIndex: 'price', key: 'price', width: 100 }, { title: '进价', dataIndex: 'buy_price', key: 'buy_price', width: 100 }, ]; @@ -115,6 +143,8 @@ const [Modal, modalApi] = useVbenModal({ modalApi.close(); }, onOpenChange(isOpen: boolean) { + // 预览/下拉挂在 body 上,默认 z-index 低于 Vben 弹窗 2000,打开时抬高 + setStoreCardBodyOpen(isOpen); if (!isOpen) { financePanelRef.value?.reset?.(); return; @@ -133,6 +163,10 @@ const [Modal, modalApi] = useVbenModal({ }, }); +onBeforeUnmount(() => { + setStoreCardBodyOpen(false); +}); + const [DoctorModal, DoctorModalApi] = useVbenModal({ connectedComponent: DoctorCardModal, }); @@ -176,8 +210,8 @@ async function loadCard() { if (isPharmacy.value) { drugType.value = 2; } - } catch (e: any) { - message.error(e?.message || '加载门店详情失败'); + } catch (error: any) { + message.error(error?.message || '加载门店详情失败'); } finally { loading.value = false; } @@ -198,8 +232,8 @@ async function refreshStats() { search_time: res.search_time, }; } - } catch (e: any) { - message.error(e?.message || '刷新统计失败'); + } catch (error: any) { + message.error(error?.message || '刷新统计失败'); } } @@ -256,7 +290,7 @@ async function loadPromoters() { } } -function onTabChange(key: string | number) { +function onTabChange(key: number | string) { activeTab.value = String(key); if (key === 'bank') loadBank(); if (key === 'drugs') loadDrugs(); @@ -275,8 +309,8 @@ async function ensureQrCode() { await openQrCodeApi(storeId.value); await loadCard(); message.success('二维码已生成'); - } catch (e: any) { - message.error(e?.message || '生成二维码失败'); + } catch (error: any) { + message.error(error?.message || '生成二维码失败'); } finally { generatingQr.value = false; } @@ -290,14 +324,19 @@ function clinicTypeText(v: number) { @@ -549,16 +632,34 @@ function clinicTypeText(v: number) { :deep(.custom-vertical-tabs .ant-tabs-nav) { width: 140px; } + :deep(.custom-vertical-tabs .ant-tabs-tab) { padding: 12px 16px; margin-bottom: 4px; border-radius: 6px; transition: all 0.3s; } + :deep(.custom-vertical-tabs .ant-tabs-tab-active) { background-color: var( --ant-primary-color-active-deprecated-f-12, - rgba(22, 119, 255, 0.08) + rgb(22 119 255 / 8%) ); } + + + diff --git a/apps/web-antd/src/components/store-card/StoreFinancePanel.vue b/apps/web-antd/src/components/store-card/StoreFinancePanel.vue index 02e8c143..96b65fd7 100644 --- a/apps/web-antd/src/components/store-card/StoreFinancePanel.vue +++ b/apps/web-antd/src/components/store-card/StoreFinancePanel.vue @@ -1,22 +1,39 @@