2026-06-18 15:05:02 +08:00
|
|
|
|
<script lang="ts" setup>
|
2026-08-18 09:12:07 +08:00
|
|
|
|
import type { Component } from 'vue';
|
|
|
|
|
|
|
|
|
|
|
|
import type { ModuleTabItem } from './components/module-tabs-panel.vue';
|
|
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
/**
|
2026-08-18 09:12:07 +08:00
|
|
|
|
* 系统配置页壳:左侧一级模块 + 右侧二级 Tab(与 AI 模块同一交互)
|
|
|
|
|
|
* 各子面板自管 load/save;旧菜单 key 会映射到新模块/Tab
|
2026-08-17 09:51:07 +08:00
|
|
|
|
*/
|
2026-08-18 09:12:07 +08:00
|
|
|
|
import { computed, onMounted, ref } from 'vue';
|
2026-06-18 15:05:02 +08:00
|
|
|
|
|
|
|
|
|
|
import { Page } from '@vben/common-ui';
|
|
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
import { Card } from 'ant-design-vue';
|
2026-07-02 16:21:09 +08:00
|
|
|
|
|
2026-08-06 17:56:28 +08:00
|
|
|
|
import AiModelConfigPanel from './components/ai-model-config-panel.vue';
|
2026-08-17 09:51:07 +08:00
|
|
|
|
import DictSearchConfigPanel from './components/dict-search-config-panel.vue';
|
2026-08-18 09:12:07 +08:00
|
|
|
|
import DispenseSignConfigPanel from './components/dispense-sign-config-panel.vue';
|
2026-08-17 09:51:07 +08:00
|
|
|
|
import DoctorLoginConfigPanel from './components/doctor-login-config-panel.vue';
|
2026-08-27 09:38:11 +08:00
|
|
|
|
import FreeShippingConfigPanel from './components/free-shipping-config-panel.vue';
|
2026-08-18 09:12:07 +08:00
|
|
|
|
import HomeDisplayConfigPanel from './components/home-display-config-panel.vue';
|
2026-08-17 09:51:07 +08:00
|
|
|
|
import InputAuditConfigPanel from './components/input-audit-config-panel.vue';
|
2026-09-23 16:22:14 +08:00
|
|
|
|
import ImageTransferConfigPanel from './components/image-transfer-config-panel.vue';
|
2026-08-17 09:51:07 +08:00
|
|
|
|
import InvoiceNoticeConfigPanel from './components/invoice-notice-config-panel.vue';
|
|
|
|
|
|
import LogisticsConfigPanel from './components/logistics-config-panel.vue';
|
|
|
|
|
|
import MedicalRecordConfigPanel from './components/medical-record-config-panel.vue';
|
|
|
|
|
|
import MiniprogramConfigPanel from './components/miniprogram-config-panel.vue';
|
2026-08-18 09:12:07 +08:00
|
|
|
|
import ModuleTabsPanel from './components/module-tabs-panel.vue';
|
2026-07-20 15:05:34 +08:00
|
|
|
|
import OaNotifyConfigPanel from './components/oa-notify-config-panel.vue';
|
2026-08-17 09:51:07 +08:00
|
|
|
|
import PriceAdjustConfigPanel from './components/price-adjust-config-panel.vue';
|
2026-09-29 10:28:12 +08:00
|
|
|
|
import ExcipientPriceConfigPanel from './components/excipient-price-config-panel.vue';
|
2026-08-21 12:51:42 +08:00
|
|
|
|
import PublicAccountPayConfigPanel from './components/public-account-pay-config-panel.vue';
|
2026-08-17 09:51:07 +08:00
|
|
|
|
import SalespersonConfigPanel from './components/salesperson-config-panel.vue';
|
2026-08-18 09:12:07 +08:00
|
|
|
|
import WorkbenchConfigPanel from './components/workbench-config-panel.vue';
|
2026-09-23 16:22:14 +08:00
|
|
|
|
import WithdrawalRulesConfigPanel from './components/withdrawal-rules-config-panel.vue';
|
2026-08-18 09:12:07 +08:00
|
|
|
|
import WxWorkbenchDefaultFavPanel from './components/wx-workbench-default-fav-panel.vue';
|
2026-06-18 15:05:02 +08:00
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'SystemConfig' });
|
|
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
const MENU_STORAGE_KEY = 'system_config_active_tab';
|
2026-07-02 16:21:09 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
interface MenuItem {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
title: string;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
/** 已自带二级 Tab 的整块面板(AI) */
|
|
|
|
|
|
component?: Component;
|
|
|
|
|
|
tabs?: ModuleTabItem[];
|
2026-08-01 08:04:31 +08:00
|
|
|
|
}
|
2026-07-02 16:21:09 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
const menuItems: MenuItem[] = [
|
2026-08-18 09:12:07 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'mp_patient',
|
|
|
|
|
|
title: '患者端小程序',
|
|
|
|
|
|
tabs: [
|
|
|
|
|
|
{ key: 'empty', title: '空状态图', component: MiniprogramConfigPanel },
|
|
|
|
|
|
{ key: 'home', title: '首页展示', component: HomeDisplayConfigPanel },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'mp_doctor',
|
|
|
|
|
|
title: '医生端小程序',
|
|
|
|
|
|
tabs: [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'default_fav',
|
|
|
|
|
|
title: '默认常用入口',
|
|
|
|
|
|
component: WxWorkbenchDefaultFavPanel,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'workbench',
|
|
|
|
|
|
title: '工作台',
|
|
|
|
|
|
component: WorkbenchConfigPanel,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'dispense_sign',
|
|
|
|
|
|
title: '处方签名',
|
|
|
|
|
|
component: DispenseSignConfigPanel,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'order',
|
|
|
|
|
|
title: '订单',
|
|
|
|
|
|
tabs: [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'price_adjust',
|
|
|
|
|
|
title: '订单调价',
|
|
|
|
|
|
component: PriceAdjustConfigPanel,
|
|
|
|
|
|
},
|
2026-09-29 10:28:12 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'excipient_price',
|
|
|
|
|
|
title: '辅料定价',
|
|
|
|
|
|
component: ExcipientPriceConfigPanel,
|
|
|
|
|
|
},
|
2026-08-18 09:12:07 +08:00
|
|
|
|
{ key: 'logistics', title: '物流展示', component: LogisticsConfigPanel },
|
2026-08-27 09:38:11 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'free_shipping',
|
|
|
|
|
|
title: '满额包邮',
|
|
|
|
|
|
component: FreeShippingConfigPanel,
|
|
|
|
|
|
},
|
2026-08-21 12:51:42 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'public_account_pay',
|
|
|
|
|
|
title: '公账支付',
|
|
|
|
|
|
component: PublicAccountPayConfigPanel,
|
|
|
|
|
|
},
|
2026-08-18 09:12:07 +08:00
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'security',
|
|
|
|
|
|
title: '权限安全',
|
|
|
|
|
|
tabs: [
|
|
|
|
|
|
{ key: 'login', title: '登录安全', component: DoctorLoginConfigPanel },
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'salesperson',
|
|
|
|
|
|
title: '业务员权限',
|
|
|
|
|
|
component: SalespersonConfigPanel,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'medical',
|
|
|
|
|
|
title: '病历词条',
|
|
|
|
|
|
tabs: [
|
|
|
|
|
|
{ key: 'entry', title: '词条', component: MedicalRecordConfigPanel },
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'dict_search',
|
|
|
|
|
|
title: '字典搜索',
|
|
|
|
|
|
component: DictSearchConfigPanel,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'notice',
|
|
|
|
|
|
title: '通知',
|
|
|
|
|
|
tabs: [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'invoice',
|
|
|
|
|
|
title: '开票通知',
|
|
|
|
|
|
component: InvoiceNoticeConfigPanel,
|
|
|
|
|
|
},
|
|
|
|
|
|
{ key: 'oa', title: 'OA通知', component: OaNotifyConfigPanel },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'audit',
|
|
|
|
|
|
title: '审核',
|
|
|
|
|
|
component: InputAuditConfigPanel,
|
|
|
|
|
|
},
|
2026-09-23 16:22:14 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'image_transfer',
|
|
|
|
|
|
title: '传方',
|
|
|
|
|
|
component: ImageTransferConfigPanel,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'finance',
|
|
|
|
|
|
title: '财务',
|
|
|
|
|
|
tabs: [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'withdrawal_rules',
|
|
|
|
|
|
title: '提现规则',
|
|
|
|
|
|
component: WithdrawalRulesConfigPanel,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
2026-08-18 09:12:07 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: 'ai_model',
|
|
|
|
|
|
title: 'AI模型',
|
|
|
|
|
|
component: AiModelConfigPanel,
|
|
|
|
|
|
},
|
2026-08-17 09:51:07 +08:00
|
|
|
|
];
|
2026-06-18 15:05:02 +08:00
|
|
|
|
|
2026-08-18 09:12:07 +08:00
|
|
|
|
/** 旧左侧菜单 key → 新模块 + 二级 Tab,避免刷新后丢选中 */
|
|
|
|
|
|
const LEGACY_MENU_MAP: Record<string, { menu: string; tab?: string }> = {
|
|
|
|
|
|
price_adjust: { menu: 'order', tab: 'price_adjust' },
|
|
|
|
|
|
input_audit: { menu: 'audit', tab: 'input_audit' },
|
|
|
|
|
|
miniprogram: { menu: 'mp_patient' },
|
|
|
|
|
|
logistics: { menu: 'order', tab: 'logistics' },
|
2026-08-21 12:51:42 +08:00
|
|
|
|
public_account_pay: { menu: 'order', tab: 'public_account_pay' },
|
2026-08-18 09:12:07 +08:00
|
|
|
|
salesperson: { menu: 'security', tab: 'salesperson' },
|
|
|
|
|
|
doctor_login: { menu: 'security', tab: 'login' },
|
|
|
|
|
|
dict_search: { menu: 'medical', tab: 'dict_search' },
|
|
|
|
|
|
medical_record: { menu: 'medical', tab: 'entry' },
|
|
|
|
|
|
invoice_notice: { menu: 'notice', tab: 'invoice' },
|
|
|
|
|
|
oa_notify: { menu: 'notice', tab: 'oa' },
|
|
|
|
|
|
ai_model: { menu: 'ai_model' },
|
|
|
|
|
|
};
|
2026-06-18 15:05:02 +08:00
|
|
|
|
|
2026-08-18 09:12:07 +08:00
|
|
|
|
const activeKey = ref('mp_patient');
|
|
|
|
|
|
|
|
|
|
|
|
const currentMenu = computed(() => {
|
|
|
|
|
|
return menuItems.find((m) => m.key === activeKey.value) || menuItems[0];
|
2026-08-17 09:51:07 +08:00
|
|
|
|
});
|
2026-06-18 15:05:02 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
function readStoredMenu() {
|
|
|
|
|
|
const stored = localStorage.getItem(MENU_STORAGE_KEY);
|
2026-08-18 09:12:07 +08:00
|
|
|
|
if (!stored) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (menuItems.some((m) => m.key === stored)) {
|
2026-08-17 09:51:07 +08:00
|
|
|
|
activeKey.value = stored;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
return;
|
2026-06-18 15:05:02 +08:00
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
const mapped = LEGACY_MENU_MAP[stored];
|
|
|
|
|
|
if (!mapped) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
activeKey.value = mapped.menu;
|
|
|
|
|
|
if (mapped.tab) {
|
|
|
|
|
|
localStorage.setItem(`system_config_${mapped.menu}_sub_tab`, mapped.tab);
|
|
|
|
|
|
} else if (stored === 'miniprogram') {
|
|
|
|
|
|
// 旧「小程序」二级 Tab 迁到患者端,避免刷新后丢「首页展示」
|
|
|
|
|
|
const oldSub = localStorage.getItem('system_config_miniprogram_sub_tab');
|
|
|
|
|
|
if (oldSub) {
|
|
|
|
|
|
localStorage.setItem('system_config_mp_patient_sub_tab', oldSub);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
localStorage.setItem(MENU_STORAGE_KEY, mapped.menu);
|
2026-06-18 15:05:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
function selectMenu(key: string) {
|
|
|
|
|
|
activeKey.value = key;
|
|
|
|
|
|
localStorage.setItem(MENU_STORAGE_KEY, key);
|
2026-06-18 15:05:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
onMounted(readStoredMenu);
|
2026-06-18 15:05:02 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<Page auto-content-height title="系统配置">
|
2026-08-17 09:51:07 +08:00
|
|
|
|
<Card class="sys-cfg-card" :body-style="{ padding: 0, height: '100%' }">
|
|
|
|
|
|
<div class="sys-cfg-layout">
|
|
|
|
|
|
<aside class="sys-cfg-nav">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="item in menuItems"
|
|
|
|
|
|
:key="item.key"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="sys-cfg-nav-item"
|
|
|
|
|
|
:class="{ active: activeKey === item.key }"
|
|
|
|
|
|
@click="selectMenu(item.key)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ item.title }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
<section class="sys-cfg-main">
|
2026-08-18 09:12:07 +08:00
|
|
|
|
<component
|
|
|
|
|
|
v-if="currentMenu.component"
|
|
|
|
|
|
:is="currentMenu.component"
|
|
|
|
|
|
:key="currentMenu.key"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<ModuleTabsPanel
|
|
|
|
|
|
v-else-if="currentMenu.tabs"
|
|
|
|
|
|
:key="currentMenu.key"
|
|
|
|
|
|
:module-key="currentMenu.key"
|
|
|
|
|
|
:tabs="currentMenu.tabs"
|
|
|
|
|
|
/>
|
2026-08-17 09:51:07 +08:00
|
|
|
|
</section>
|
2026-06-18 15:05:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Page>
|
|
|
|
|
|
</template>
|
2026-08-17 09:51:07 +08:00
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.sys-cfg-card {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
height: 100%;
|
|
|
|
|
|
min-height: 0;
|
2026-08-17 09:51:07 +08:00
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
.sys-cfg-layout {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
min-height: 480px;
|
|
|
|
|
|
max-height: calc(100vh - 160px);
|
|
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
.sys-cfg-nav {
|
|
|
|
|
|
flex-shrink: 0;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
width: 188px;
|
|
|
|
|
|
padding: 8px;
|
2026-08-17 09:51:07 +08:00
|
|
|
|
overflow-y: auto;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
background: hsl(var(--muted) / 25%);
|
2026-08-17 09:51:07 +08:00
|
|
|
|
border-right: 1px solid hsl(var(--border));
|
|
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
.sys-cfg-nav-item {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
font-size: 14px;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
color: hsl(var(--foreground));
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
|
border-radius: 8px;
|
2026-08-17 09:51:07 +08:00
|
|
|
|
transition:
|
|
|
|
|
|
background 0.15s ease,
|
|
|
|
|
|
border-color 0.15s ease,
|
|
|
|
|
|
color 0.15s ease;
|
|
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
.sys-cfg-nav-item:hover {
|
|
|
|
|
|
background: hsl(var(--accent));
|
|
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
.sys-cfg-nav-item.active {
|
|
|
|
|
|
color: hsl(var(--primary));
|
2026-08-18 09:12:07 +08:00
|
|
|
|
background: hsl(var(--primary) / 10%);
|
|
|
|
|
|
border-color: hsl(var(--primary) / 35%);
|
2026-08-17 09:51:07 +08:00
|
|
|
|
box-shadow: 0 0 6px hsl(var(--primary) / 18%);
|
|
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
.sys-cfg-main {
|
2026-08-18 09:12:07 +08:00
|
|
|
|
display: flex;
|
2026-08-17 09:51:07 +08:00
|
|
|
|
flex: 1;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
flex-direction: column;
|
2026-08-17 09:51:07 +08:00
|
|
|
|
min-width: 0;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
/* 子面板统一:内容滚动 + 底栏不滚走 */
|
|
|
|
|
|
.sys-cfg-main :deep(.cfg-panel) {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
.sys-cfg-main :deep(.cfg-panel-body) {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
padding: 16px 20px;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
overflow: auto;
|
2026-08-17 09:51:07 +08:00
|
|
|
|
}
|
2026-08-18 09:12:07 +08:00
|
|
|
|
|
|
|
|
|
|
.sys-cfg-main :deep(.module-tabs-body) {
|
|
|
|
|
|
padding: 8px 0 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 09:51:07 +08:00
|
|
|
|
.sys-cfg-main :deep(.cfg-panel-footer) {
|
2026-08-18 09:12:07 +08:00
|
|
|
|
z-index: 2;
|
2026-08-17 09:51:07 +08:00
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
padding: 12px 20px;
|
2026-08-18 09:12:07 +08:00
|
|
|
|
background: hsl(var(--card, var(--background)));
|
|
|
|
|
|
border-top: 1px solid hsl(var(--border));
|
2026-08-17 09:51:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|