feat: 补齐素材文件夹、挂号改期、系统配置分组与工作台简报开关
素材库按 object_key 目录同步文件夹侧栏,选图和筛选可按目录收窄。 挂号列表支持管理端代改期,号源日历与患者端同源。 系统配置按端拆模块(患者首页展示、医生默认常用入口、工作台简报总开关、处方签名),旧菜单 key 做迁移以免刷新丢选中。 AI 生成记录补每日简报场景和缓存命中筛选;个人中心改走后端菜单 Profile。
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { ModuleTabItem } from './components/module-tabs-panel.vue';
|
||||
|
||||
/**
|
||||
* 系统配置页壳:左侧一级菜单 + 右侧动态面板
|
||||
* 各模块自管 load/save;AI 在右侧保留二级 Tab
|
||||
* 系统配置页壳:左侧一级模块 + 右侧二级 Tab(与 AI 模块同一交互)
|
||||
* 各子面板自管 load/save;旧菜单 key 会映射到新模块/Tab
|
||||
*/
|
||||
import { computed, onMounted, ref, type Component } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
@@ -11,15 +15,20 @@ 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 HomeDisplayConfigPanel from './components/home-display-config-panel.vue';
|
||||
import InputAuditConfigPanel from './components/input-audit-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 SalespersonConfigPanel from './components/salesperson-config-panel.vue';
|
||||
import WorkbenchConfigPanel from './components/workbench-config-panel.vue';
|
||||
import WxWorkbenchDefaultFavPanel from './components/wx-workbench-default-fav-panel.vue';
|
||||
|
||||
defineOptions({ name: 'SystemConfig' });
|
||||
|
||||
@@ -28,35 +37,146 @@ const MENU_STORAGE_KEY = 'system_config_active_tab';
|
||||
interface MenuItem {
|
||||
key: string;
|
||||
title: string;
|
||||
component: Component;
|
||||
/** 已自带二级 Tab 的整块面板(AI) */
|
||||
component?: Component;
|
||||
tabs?: ModuleTabItem[];
|
||||
}
|
||||
|
||||
const menuItems: MenuItem[] = [
|
||||
{ key: 'price_adjust', title: '订单调价', component: PriceAdjustConfigPanel },
|
||||
{ key: 'input_audit', title: '录入审核', component: InputAuditConfigPanel },
|
||||
{ key: 'miniprogram', title: '小程序', component: MiniprogramConfigPanel },
|
||||
{ key: 'logistics', title: '物流展示', component: LogisticsConfigPanel },
|
||||
{ key: 'salesperson', title: '业务员权限', component: SalespersonConfigPanel },
|
||||
{ key: 'doctor_login', title: '登录安全', component: DoctorLoginConfigPanel },
|
||||
{ key: 'dict_search', title: '字典搜索', component: DictSearchConfigPanel },
|
||||
{ key: 'medical_record', title: '词条', component: MedicalRecordConfigPanel },
|
||||
{ key: 'invoice_notice', title: '开票通知', component: InvoiceNoticeConfigPanel },
|
||||
{ key: 'oa_notify', title: 'OA通知', component: OaNotifyConfigPanel },
|
||||
{ key: 'ai_model', title: 'AI模型', component: AiModelConfigPanel },
|
||||
{
|
||||
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: 'logistics', title: '物流展示', component: LogisticsConfigPanel },
|
||||
],
|
||||
},
|
||||
{
|
||||
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: 'ai_model',
|
||||
title: 'AI模型',
|
||||
component: AiModelConfigPanel,
|
||||
},
|
||||
];
|
||||
|
||||
const activeKey = ref('price_adjust');
|
||||
/** 旧左侧菜单 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' },
|
||||
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 currentPanel = computed(() => {
|
||||
const hit = menuItems.find((m) => m.key === activeKey.value);
|
||||
return hit?.component || PriceAdjustConfigPanel;
|
||||
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 && menuItems.some((m) => m.key === stored)) {
|
||||
activeKey.value = stored;
|
||||
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) {
|
||||
@@ -84,7 +204,17 @@ onMounted(readStoredMenu);
|
||||
</button>
|
||||
</aside>
|
||||
<section class="sys-cfg-main">
|
||||
<component :is="currentPanel" :key="activeKey" />
|
||||
<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>
|
||||
@@ -93,58 +223,65 @@ onMounted(readStoredMenu);
|
||||
|
||||
<style scoped>
|
||||
.sys-cfg-card {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
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 {
|
||||
width: 188px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--muted) / 0.25);
|
||||
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%;
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
color: hsl(var(--foreground));
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 4px;
|
||||
cursor: pointer;
|
||||
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 {
|
||||
border-color: hsl(var(--primary) / 35%);
|
||||
background: hsl(var(--primary) / 0.1);
|
||||
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;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 子面板统一:内容滚动 + 底栏不滚走 */
|
||||
.sys-cfg-main :deep(.cfg-panel) {
|
||||
display: flex;
|
||||
@@ -152,17 +289,24 @@ onMounted(readStoredMenu);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sys-cfg-main :deep(.cfg-panel-body) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
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) {
|
||||
flex-shrink: 0;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--card, var(--background)));
|
||||
padding: 12px 20px;
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
padding: 12px 20px;
|
||||
background: hsl(var(--card, var(--background)));
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user