Files
xk-admin/apps/web-antd/src/views/system/system-config/index.vue

350 lines
9.3 KiB
Vue
Raw Normal View History

2026-06-18 15:05:02 +08:00
<script lang="ts" setup>
import type { Component } from 'vue';
import type { ModuleTabItem } from './components/module-tabs-panel.vue';
/**
* 系统配置页壳:左侧一级模块 + 右侧二级 Tab(与 AI 模块同一交互)
* 各子面板自管 load/save;旧菜单 key 会映射到新模块/Tab
*/
import { computed, onMounted, ref } from 'vue';
2026-06-18 15:05:02 +08:00
import { Page } from '@vben/common-ui';
import { Card } from 'ant-design-vue';
2026-07-02 16:21:09 +08:00
import AiModelConfigPanel from './components/ai-model-config-panel.vue';
import DictSearchConfigPanel from './components/dict-search-config-panel.vue';
import DispenseSignConfigPanel from './components/dispense-sign-config-panel.vue';
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';
import HomeDisplayConfigPanel from './components/home-display-config-panel.vue';
import InputAuditConfigPanel from './components/input-audit-config-panel.vue';
import ImageTransferConfigPanel from './components/image-transfer-config-panel.vue';
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';
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';
import PriceAdjustConfigPanel from './components/price-adjust-config-panel.vue';
import ExcipientPriceConfigPanel from './components/excipient-price-config-panel.vue';
import PublicAccountPayConfigPanel from './components/public-account-pay-config-panel.vue';
import SalespersonConfigPanel from './components/salesperson-config-panel.vue';
import WorkbenchConfigPanel from './components/workbench-config-panel.vue';
import WithdrawalRulesConfigPanel from './components/withdrawal-rules-config-panel.vue';
import WxWorkbenchDefaultFavPanel from './components/wx-workbench-default-fav-panel.vue';
2026-06-18 15:05:02 +08:00
defineOptions({ name: 'SystemConfig' });
const MENU_STORAGE_KEY = 'system_config_active_tab';
2026-07-02 16:21:09 +08:00
interface MenuItem {
key: string;
title: string;
/** 已自带二级 Tab 的整块面板(AI) */
component?: Component;
tabs?: ModuleTabItem[];
}
2026-07-02 16:21:09 +08:00
const menuItems: MenuItem[] = [
{
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,
},
{
key: 'excipient_price',
title: '辅料定价',
component: ExcipientPriceConfigPanel,
},
{ key: 'logistics', title: '物流展示', component: LogisticsConfigPanel },
2026-08-27 09:38:11 +08:00
{
key: 'free_shipping',
title: '满额包邮',
component: FreeShippingConfigPanel,
},
{
key: 'public_account_pay',
title: '公账支付',
component: PublicAccountPayConfigPanel,
},
],
},
{
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,
},
{
key: 'image_transfer',
title: '传方',
component: ImageTransferConfigPanel,
},
{
key: 'finance',
title: '财务',
tabs: [
{
key: 'withdrawal_rules',
title: '提现规则',
component: WithdrawalRulesConfigPanel,
},
],
},
{
key: 'ai_model',
title: 'AI模型',
component: AiModelConfigPanel,
},
];
2026-06-18 15:05:02 +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' },
public_account_pay: { menu: 'order', tab: 'public_account_pay' },
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
const activeKey = ref('mp_patient');
const currentMenu = computed(() => {
return menuItems.find((m) => m.key === activeKey.value) || menuItems[0];
});
2026-06-18 15:05:02 +08:00
function readStoredMenu() {
const stored = localStorage.getItem(MENU_STORAGE_KEY);
if (!stored) {
return;
}
if (menuItems.some((m) => m.key === stored)) {
activeKey.value = stored;
return;
2026-06-18 15:05:02 +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
}
function selectMenu(key: string) {
activeKey.value = key;
localStorage.setItem(MENU_STORAGE_KEY, key);
2026-06-18 15:05:02 +08:00
}
onMounted(readStoredMenu);
2026-06-18 15:05:02 +08:00
</script>
<template>
<Page auto-content-height title="系统配置">
<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">
<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"
/>
</section>
2026-06-18 15:05:02 +08:00
</div>
</Card>
</Page>
</template>
<style scoped>
.sys-cfg-card {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.sys-cfg-layout {
display: flex;
height: 100%;
min-height: 480px;
max-height: calc(100vh - 160px);
}
.sys-cfg-nav {
flex-shrink: 0;
width: 188px;
padding: 8px;
overflow-y: auto;
background: hsl(var(--muted) / 25%);
border-right: 1px solid hsl(var(--border));
}
.sys-cfg-nav-item {
display: block;
width: 100%;
padding: 10px 12px;
margin-bottom: 4px;
font-size: 14px;
color: hsl(var(--foreground));
text-align: left;
cursor: pointer;
background: transparent;
border: 1px solid transparent;
border-radius: 8px;
transition:
background 0.15s ease,
border-color 0.15s ease,
color 0.15s ease;
}
.sys-cfg-nav-item:hover {
background: hsl(var(--accent));
}
.sys-cfg-nav-item.active {
color: hsl(var(--primary));
background: hsl(var(--primary) / 10%);
border-color: hsl(var(--primary) / 35%);
box-shadow: 0 0 6px hsl(var(--primary) / 18%);
}
.sys-cfg-main {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
min-height: 0;
}
/* 子面板统一:内容滚动 + 底栏不滚走 */
.sys-cfg-main :deep(.cfg-panel) {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.sys-cfg-main :deep(.cfg-panel-body) {
flex: 1;
min-height: 0;
padding: 16px 20px;
overflow: auto;
}
.sys-cfg-main :deep(.module-tabs-body) {
padding: 8px 0 0;
overflow: hidden;
}
.sys-cfg-main :deep(.cfg-panel-footer) {
z-index: 2;
flex-shrink: 0;
padding: 12px 20px;
background: hsl(var(--card, var(--background)));
border-top: 1px solid hsl(var(--border));
}
</style>