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

350 lines
9.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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';
import { Page } from '@vben/common-ui';
import { Card } from 'ant-design-vue';
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';
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';
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';
defineOptions({ name: 'SystemConfig' });
const MENU_STORAGE_KEY = 'system_config_active_tab';
interface MenuItem {
key: string;
title: string;
/** 已自带二级 Tab 的整块面板(AI) */
component?: Component;
tabs?: ModuleTabItem[];
}
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 },
{
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,
},
];
/** 旧左侧菜单 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' },
};
const activeKey = ref('mp_patient');
const currentMenu = computed(() => {
return menuItems.find((m) => m.key === activeKey.value) || menuItems[0];
});
function readStoredMenu() {
const stored = localStorage.getItem(MENU_STORAGE_KEY);
if (!stored) {
return;
}
if (menuItems.some((m) => m.key === stored)) {
activeKey.value = stored;
return;
}
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);
}
function selectMenu(key: string) {
activeKey.value = key;
localStorage.setItem(MENU_STORAGE_KEY, key);
}
onMounted(readStoredMenu);
</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>
</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>