feat: scaffold mediai admin
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="page-stack">
|
||||
<section class="page-toolbar">
|
||||
<div>
|
||||
<h1>病例中心</h1>
|
||||
<p>维护 AI 问诊病例、难度、科室和发布状态。</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button :icon="Upload">批量导入</el-button>
|
||||
<el-button :icon="Plus" type="primary">新建病例</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="filter-bar">
|
||||
<el-input v-model="keyword" :prefix-icon="Search" clearable placeholder="搜索病例名称/编号" />
|
||||
<el-select v-model="department" clearable placeholder="科室">
|
||||
<el-option label="心内科" value="心内科" />
|
||||
<el-option label="儿科" value="儿科" />
|
||||
<el-option label="内分泌科" value="内分泌科" />
|
||||
<el-option label="神经内科" value="神经内科" />
|
||||
</el-select>
|
||||
<el-select v-model="status" clearable placeholder="状态">
|
||||
<el-option label="已发布" value="已发布" />
|
||||
<el-option label="审核中" value="审核中" />
|
||||
<el-option label="草稿" value="草稿" />
|
||||
</el-select>
|
||||
<el-button :icon="Refresh">重置</el-button>
|
||||
</section>
|
||||
|
||||
<section class="data-section">
|
||||
<el-table :data="filteredRows" row-key="id">
|
||||
<el-table-column prop="id" label="病例编号" width="140" />
|
||||
<el-table-column prop="name" label="病例名称" min-width="200" />
|
||||
<el-table-column prop="department" label="科室" width="120" />
|
||||
<el-table-column prop="difficulty" label="难度" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.difficulty === '高' ? 'danger' : row.difficulty === '中' ? 'warning' : 'success'">{{ row.difficulty }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="usage" label="训练次数" width="120" sortable />
|
||||
<el-table-column prop="updatedAt" label="更新时间" width="130" />
|
||||
<el-table-column prop="status" label="状态" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '已发布' ? 'success' : row.status === '审核中' ? 'warning' : 'info'">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="190" fixed="right">
|
||||
<template #default>
|
||||
<el-button link type="primary">编辑</el-button>
|
||||
<el-button link type="primary">预览</el-button>
|
||||
<el-button link type="danger">下架</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { Plus, Refresh, Search, Upload } from '@element-plus/icons-vue'
|
||||
import { caseRows } from '@/mock/dashboard'
|
||||
|
||||
const keyword = ref('')
|
||||
const department = ref('')
|
||||
const status = ref('')
|
||||
|
||||
const filteredRows = computed(() =>
|
||||
caseRows.filter(item => {
|
||||
const matchKeyword = !keyword.value || item.name.includes(keyword.value) || item.id.includes(keyword.value)
|
||||
const matchDepartment = !department.value || item.department === department.value
|
||||
const matchStatus = !status.value || item.status === status.value
|
||||
return matchKeyword && matchDepartment && matchStatus
|
||||
})
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="dashboard-page page-stack">
|
||||
<section class="hero-strip">
|
||||
<div>
|
||||
<span class="eyebrow">MediAI Command Center</span>
|
||||
<h1>数据驾驶舱</h1>
|
||||
<p>集中查看病例训练、机构活跃、AI评估质量和教学任务进展。</p>
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<el-button :icon="Download">导出报表</el-button>
|
||||
<el-button :icon="Plus" type="primary">新增训练任务</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="stats-grid">
|
||||
<article v-for="item in stats" :key="item.label" class="stat-card">
|
||||
<div :class="['stat-mark', item.tone]">{{ item.label.slice(0, 1) }}</div>
|
||||
<div>
|
||||
<span>{{ item.label }}</span>
|
||||
<strong>{{ item.value }}</strong>
|
||||
<em>{{ item.change }}</em>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="dashboard-grid">
|
||||
<ChartPanel title="训练趋势" subtitle="近 7 日训练完成量与通过率" :option="trendOption">
|
||||
<template #actions>
|
||||
<el-segmented v-model="range" :options="['周', '月', '季']" />
|
||||
</template>
|
||||
</ChartPanel>
|
||||
<ChartPanel title="能力画像" subtitle="按最新训练评估汇总" :option="radarOption" />
|
||||
</section>
|
||||
|
||||
<section class="content-grid">
|
||||
<div class="data-section">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<h2>重点病例</h2>
|
||||
<p>高频训练和待审核内容</p>
|
||||
</div>
|
||||
<el-button link type="primary">查看全部</el-button>
|
||||
</div>
|
||||
<el-table :data="caseRows" height="300">
|
||||
<el-table-column prop="name" label="病例名称" min-width="170" />
|
||||
<el-table-column prop="department" label="科室" width="110" />
|
||||
<el-table-column prop="difficulty" label="难度" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.difficulty === '高' ? 'danger' : row.difficulty === '中' ? 'warning' : 'success'">{{ row.difficulty }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="usage" label="训练次数" width="110" />
|
||||
<el-table-column prop="status" label="状态" width="100" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="data-section">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<h2>最新动态</h2>
|
||||
<p>平台关键事件流</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-timeline>
|
||||
<el-timeline-item v-for="item in activityTimeline" :key="item" timestamp="今天" placement="top">
|
||||
{{ item }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { Download, Plus } from '@element-plus/icons-vue'
|
||||
import ChartPanel from '@/components/ChartPanel.vue'
|
||||
import { abilityRadar, activityTimeline, caseRows, stats } from '@/mock/dashboard'
|
||||
|
||||
const range = ref('周')
|
||||
|
||||
const trendOption = computed<EChartsOption>(() => ({
|
||||
color: ['#2563eb', '#16a34a'],
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { left: 36, right: 20, top: 34, bottom: 28 },
|
||||
xAxis: { type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
||||
yAxis: { type: 'value', splitLine: { lineStyle: { color: '#eef2f7' } } },
|
||||
series: [
|
||||
{ name: '完成量', type: 'bar', barWidth: 18, data: [320, 438, 386, 520, 628, 446, 580], itemStyle: { borderRadius: [5, 5, 0, 0] } },
|
||||
{ name: '通过率', type: 'line', smooth: true, data: [82, 86, 84, 88, 91, 87, 92] }
|
||||
]
|
||||
}))
|
||||
|
||||
const radarOption = computed<EChartsOption>(() => ({
|
||||
color: ['#0f766e'],
|
||||
radar: {
|
||||
radius: 94,
|
||||
indicator: abilityRadar.map(item => ({ name: item.name, max: 100 }))
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'radar',
|
||||
areaStyle: { color: 'rgba(15, 118, 110, 0.16)' },
|
||||
data: [{ value: abilityRadar.map(item => item.value), name: '综合能力' }]
|
||||
}
|
||||
]
|
||||
}))
|
||||
</script>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="page-stack">
|
||||
<section class="page-toolbar">
|
||||
<div>
|
||||
<h1>机构管理</h1>
|
||||
<p>管理医院、医学院、科室组织及账号配额。</p>
|
||||
</div>
|
||||
<el-button :icon="Plus" type="primary">新增机构</el-button>
|
||||
</section>
|
||||
|
||||
<section class="institution-grid">
|
||||
<article v-for="item in institutions" :key="item.name" class="institution-card">
|
||||
<div class="institution-head">
|
||||
<div>
|
||||
<h2>{{ item.name }}</h2>
|
||||
<span>{{ item.type }}</span>
|
||||
</div>
|
||||
<el-tag :type="item.status === '运行中' ? 'success' : 'warning'">{{ item.status }}</el-tag>
|
||||
</div>
|
||||
<div class="institution-metrics">
|
||||
<div><strong>{{ item.teachers }}</strong><span>带教老师</span></div>
|
||||
<div><strong>{{ item.students }}</strong><span>学生</span></div>
|
||||
<div><strong>{{ item.activeRate }}%</strong><span>活跃率</span></div>
|
||||
</div>
|
||||
<el-progress :percentage="item.activeRate" />
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { institutions } from '@/mock/dashboard'
|
||||
</script>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<section class="login-visual">
|
||||
<div class="visual-grid" />
|
||||
<div class="login-brand">
|
||||
<div class="brand-logo">MediAI</div>
|
||||
<h1>医疗AI平台管理系统</h1>
|
||||
<p>智能化医疗教学与病例分析平台</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-list">
|
||||
<div v-for="item in features" :key="item.title" class="feature-item">
|
||||
<el-icon><component :is="item.icon" /></el-icon>
|
||||
<div>
|
||||
<strong>{{ item.title }}</strong>
|
||||
<span>{{ item.desc }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="login-card">
|
||||
<div class="login-card-head">
|
||||
<h2>欢迎回来</h2>
|
||||
<p>请登录您的管理员账号</p>
|
||||
</div>
|
||||
|
||||
<el-form ref="formRef" :model="form" :rules="rules" size="large" @keyup.enter="handleLogin">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="form.username" :prefix-icon="User" placeholder="请输入账号" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="form.password" :prefix-icon="Lock" placeholder="请输入密码" show-password type="password" />
|
||||
</el-form-item>
|
||||
<div class="login-options">
|
||||
<el-checkbox v-model="form.remember">记住我</el-checkbox>
|
||||
<el-button link type="primary">忘记密码?</el-button>
|
||||
</div>
|
||||
<el-button :loading="loading" class="login-submit" type="primary" @click="handleLogin">登录</el-button>
|
||||
</el-form>
|
||||
|
||||
<div class="login-divider"><span>其他登录方式</span></div>
|
||||
<div class="login-methods">
|
||||
<el-button :icon="Iphone">扫码登录</el-button>
|
||||
<el-button :icon="Key">SSO登录</el-button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Collection, DataAnalysis, FirstAidKit, Iphone, Key, Lock, OfficeBuilding, User } from '@element-plus/icons-vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const formRef = ref<FormInstance>()
|
||||
const loading = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
remember: true
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const features = [
|
||||
{ title: '智能病例训练', desc: 'AI驱动的沉浸式病例问诊训练', icon: FirstAidKit },
|
||||
{ title: '数据驱动决策', desc: '全方位数据分析与能力评估', icon: DataAnalysis },
|
||||
{ title: '多机构管理', desc: '支持多医院、多科室统一管理', icon: OfficeBuilding },
|
||||
{ title: '内容协同运营', desc: '病例、任务、评分规则统一维护', icon: Collection }
|
||||
]
|
||||
|
||||
async function handleLogin() {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
|
||||
window.setTimeout(() => {
|
||||
appStore.login(form.username)
|
||||
loading.value = false
|
||||
router.push((route.query.redirect as string) || '/')
|
||||
}, 420)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div class="page-stack">
|
||||
<section class="page-toolbar">
|
||||
<div>
|
||||
<span class="eyebrow">{{ moduleConfig.group }}</span>
|
||||
<h1>{{ moduleConfig.title }}</h1>
|
||||
<p>{{ moduleConfig.description }}</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button :icon="Download" v-if="moduleConfig.actions.includes('export')">导出</el-button>
|
||||
<el-button :icon="Upload" v-if="moduleConfig.actions.includes('import')">导入</el-button>
|
||||
<el-button :icon="Plus" type="primary" v-if="moduleConfig.actions.includes('create')">{{ moduleConfig.primaryAction }}</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="moduleConfig.kind === 'analysis'" class="dashboard-grid">
|
||||
<ChartPanel :title="`${moduleConfig.title}趋势`" subtitle="按周汇总核心指标" :option="lineOption" />
|
||||
<ChartPanel title="结构分布" subtitle="按业务维度拆解" :option="pieOption" />
|
||||
</section>
|
||||
|
||||
<section v-if="moduleConfig.kind === 'ai'" class="ai-workbench">
|
||||
<div class="data-section">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<h2>生成配置</h2>
|
||||
<p>选择科室、难度和内容类型,生成后进入审核流程。</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-form label-position="top">
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="科室">
|
||||
<el-select model-value="心内科">
|
||||
<el-option label="心内科" value="心内科" />
|
||||
<el-option label="儿科" value="儿科" />
|
||||
<el-option label="神经内科" value="神经内科" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="难度">
|
||||
<el-select model-value="中级">
|
||||
<el-option label="初级" value="初级" />
|
||||
<el-option label="中级" value="中级" />
|
||||
<el-option label="高级" value="高级" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="生成数量">
|
||||
<el-input-number :model-value="3" :min="1" :max="10" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="补充描述">
|
||||
<el-input type="textarea" :rows="4" placeholder="输入病例背景、考察重点或评分要求" />
|
||||
</el-form-item>
|
||||
<el-button :icon="Cpu" type="primary">开始生成</el-button>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="data-section">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<h2>AI任务队列</h2>
|
||||
<p>展示生成、审核和发布状态。</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="aiRows">
|
||||
<el-table-column prop="name" label="任务名称" min-width="190" />
|
||||
<el-table-column prop="type" label="类型" width="110" />
|
||||
<el-table-column prop="score" label="质量分" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '已发布' ? 'success' : row.status === '待审核' ? 'warning' : 'info'">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-else-if="moduleConfig.kind === 'profile'" class="profile-layout">
|
||||
<div class="data-section profile-summary">
|
||||
<el-avatar :size="76">陈</el-avatar>
|
||||
<h2>陈静</h2>
|
||||
<p>复旦大学中山医院 | 进阶学习者</p>
|
||||
<div class="profile-stats">
|
||||
<div><strong>156</strong><span>训练次数</span></div>
|
||||
<div><strong>82.5</strong><span>平均得分</span></div>
|
||||
<div><strong>45天</strong><span>学习天数</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<ChartPanel title="能力雷达图" subtitle="问诊、诊断、沟通、处置综合评估" :option="radarOption" />
|
||||
</section>
|
||||
|
||||
<section v-else-if="moduleConfig.kind === 'config'" class="settings-grid">
|
||||
<el-card shadow="never">
|
||||
<template #header>基础设置</template>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="平台名称">
|
||||
<el-input model-value="MediAI 医疗AI平台" />
|
||||
</el-form-item>
|
||||
<el-form-item label="AI病例生成">
|
||||
<el-switch :model-value="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户注册">
|
||||
<el-switch :model-value="true" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card shadow="never">
|
||||
<template #header>安全审计</template>
|
||||
<el-table :data="auditRows">
|
||||
<el-table-column prop="time" label="时间" width="150" />
|
||||
<el-table-column prop="user" label="用户" width="100" />
|
||||
<el-table-column prop="action" label="操作" />
|
||||
<el-table-column prop="result" label="结果" width="90" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<section v-else class="data-section">
|
||||
<div class="filter-bar module-filter">
|
||||
<el-input v-model="keyword" :prefix-icon="Search" clearable :placeholder="`搜索${moduleConfig.title}`" />
|
||||
<el-select v-model="status" clearable placeholder="状态">
|
||||
<el-option label="正常" value="正常" />
|
||||
<el-option label="待审核" value="待审核" />
|
||||
<el-option label="已发布" value="已发布" />
|
||||
</el-select>
|
||||
<el-button :icon="Refresh">重置</el-button>
|
||||
</div>
|
||||
<el-table :data="tableRows" row-key="id">
|
||||
<el-table-column prop="id" label="编号" width="150" />
|
||||
<el-table-column prop="name" :label="moduleConfig.tableTitle" min-width="220" />
|
||||
<el-table-column prop="category" label="分类/机构" min-width="150" />
|
||||
<el-table-column prop="owner" label="负责人" width="120" />
|
||||
<el-table-column prop="metric" label="关键指标" width="130" />
|
||||
<el-table-column prop="status" label="状态" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="tagType(row.status)">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default>
|
||||
<el-button link type="primary">详情</el-button>
|
||||
<el-button link type="primary">编辑</el-button>
|
||||
<el-button link type="danger">停用</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { Cpu, Download, Plus, Refresh, Search, Upload } from '@element-plus/icons-vue'
|
||||
import ChartPanel from '@/components/ChartPanel.vue'
|
||||
import { pageTitles } from '@/mock/navigation'
|
||||
|
||||
type ModuleKind = 'table' | 'analysis' | 'ai' | 'profile' | 'config'
|
||||
|
||||
interface ModuleConfig {
|
||||
title: string
|
||||
group: string
|
||||
description: string
|
||||
kind: ModuleKind
|
||||
tableTitle: string
|
||||
primaryAction: string
|
||||
actions: Array<'create' | 'import' | 'export'>
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
const keyword = ref('')
|
||||
const status = ref('')
|
||||
|
||||
const page = computed(() => String(route.params.page || 'dashboard'))
|
||||
const moduleConfig = computed<ModuleConfig>(() => resolveModuleConfig(page.value))
|
||||
|
||||
const tableRows = computed(() => createRows(moduleConfig.value.title).filter(row => {
|
||||
const matchKeyword = !keyword.value || row.name.includes(keyword.value) || row.id.includes(keyword.value)
|
||||
const matchStatus = !status.value || row.status === status.value
|
||||
return matchKeyword && matchStatus
|
||||
}))
|
||||
|
||||
const aiRows = [
|
||||
{ name: '急性胸痛问诊病例生成', type: '剧本病例', score: 95, status: '已发布' },
|
||||
{ name: '儿童发热互动病例', type: '互动病例', score: 91, status: '待审核' },
|
||||
{ name: '卒中早筛训练包', type: '专题内容', score: 88, status: '生成中' }
|
||||
]
|
||||
|
||||
const auditRows = [
|
||||
{ time: '06-03 13:42', user: '张管理员', action: '更新 AI 评分策略', result: '成功' },
|
||||
{ time: '06-03 11:08', user: '王内容', action: '提交病例审核', result: '成功' },
|
||||
{ time: '06-02 19:26', user: '李医生', action: '导出训练报告', result: '成功' }
|
||||
]
|
||||
|
||||
const lineOption = computed<EChartsOption>(() => ({
|
||||
color: ['#2563eb', '#0f766e'],
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { left: 36, right: 20, top: 34, bottom: 28 },
|
||||
xAxis: { type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
||||
yAxis: { type: 'value', splitLine: { lineStyle: { color: '#eef2f7' } } },
|
||||
series: [
|
||||
{ name: '访问量', type: 'line', smooth: true, data: [180, 260, 230, 340, 420, 390, 480] },
|
||||
{ name: '完成量', type: 'bar', barWidth: 18, data: [120, 180, 156, 260, 310, 286, 350], itemStyle: { borderRadius: [5, 5, 0, 0] } }
|
||||
]
|
||||
}))
|
||||
|
||||
const pieOption = computed<EChartsOption>(() => ({
|
||||
tooltip: { trigger: 'item' },
|
||||
color: ['#2563eb', '#0f766e', '#f59e0b', '#7c3aed'],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['46%', '72%'],
|
||||
data: [
|
||||
{ name: '心内科', value: 32 },
|
||||
{ name: '儿科', value: 24 },
|
||||
{ name: '神经内科', value: 18 },
|
||||
{ name: '其他', value: 26 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
const radarOption = computed<EChartsOption>(() => ({
|
||||
color: ['#2563eb'],
|
||||
radar: {
|
||||
radius: 94,
|
||||
indicator: [
|
||||
{ name: '问诊完整度', max: 100 },
|
||||
{ name: '诊断推理', max: 100 },
|
||||
{ name: '沟通表达', max: 100 },
|
||||
{ name: '检查选择', max: 100 },
|
||||
{ name: '处置方案', max: 100 }
|
||||
]
|
||||
},
|
||||
series: [{ type: 'radar', areaStyle: { color: 'rgba(37, 99, 235, 0.16)' }, data: [{ value: [92, 86, 78, 82, 88] }] }]
|
||||
}))
|
||||
|
||||
function resolveModuleConfig(currentPage: string): ModuleConfig {
|
||||
const title = pageTitles[currentPage] || currentPage
|
||||
const kind = inferKind(currentPage)
|
||||
const group = inferGroup(currentPage)
|
||||
|
||||
return {
|
||||
title,
|
||||
group,
|
||||
kind,
|
||||
description: createDescription(title, kind),
|
||||
tableTitle: inferTableTitle(currentPage),
|
||||
primaryAction: inferPrimaryAction(currentPage),
|
||||
actions: kind === 'analysis' || kind === 'profile' ? ['export'] : kind === 'config' ? [] : ['create', 'import', 'export']
|
||||
}
|
||||
}
|
||||
|
||||
function inferKind(currentPage: string): ModuleKind {
|
||||
if (currentPage.includes('dashboard') || currentPage.includes('data') || currentPage.includes('stats') || currentPage.includes('analysis') || currentPage.includes('trend') || currentPage.includes('retention')) return 'analysis'
|
||||
if (currentPage.includes('ai')) return 'ai'
|
||||
if (currentPage.includes('ability') || currentPage.includes('growth') || currentPage.includes('ranking') || currentPage.includes('leaderboard')) return 'profile'
|
||||
if (currentPage.includes('config') || currentPage.includes('log') || currentPage.includes('permission')) return 'config'
|
||||
return 'table'
|
||||
}
|
||||
|
||||
function inferGroup(currentPage: string) {
|
||||
if (currentPage.includes('user') || currentPage.includes('doctor') || currentPage.includes('student') || currentPage.includes('teacher')) return '人员与权限'
|
||||
if (currentPage.includes('case') || currentPage.includes('tag') || currentPage.includes('category')) return '内容管理'
|
||||
if (currentPage.includes('training') || currentPage.includes('dialog') || currentPage.includes('task')) return '训练教学'
|
||||
if (currentPage.includes('ability') || currentPage.includes('growth') || currentPage.includes('ranking')) return '能力评估'
|
||||
if (currentPage.includes('data') || currentPage.includes('stats') || currentPage.includes('analysis')) return '数据分析'
|
||||
return '平台管理'
|
||||
}
|
||||
|
||||
function createDescription(title: string, kind: ModuleKind) {
|
||||
const map: Record<ModuleKind, string> = {
|
||||
table: `维护${title}相关数据,支持查询、导入导出、启停和详情查看。`,
|
||||
analysis: `查看${title}相关趋势、结构分布和关键指标,辅助运营决策。`,
|
||||
ai: `通过 AI 辅助生成、审核和优化医疗教学内容。`,
|
||||
profile: `从多维指标评估学生能力,沉淀成长路径和推荐方案。`,
|
||||
config: `配置平台参数、安全策略、权限规则和审计记录。`
|
||||
}
|
||||
return map[kind]
|
||||
}
|
||||
|
||||
function inferTableTitle(currentPage: string) {
|
||||
if (currentPage.includes('case')) return '病例名称'
|
||||
if (currentPage.includes('hospital')) return '医院名称'
|
||||
if (currentPage.includes('department') || currentPage.includes('dept')) return '科室名称'
|
||||
if (currentPage.includes('training')) return '训练任务'
|
||||
if (currentPage.includes('student')) return '学生姓名'
|
||||
if (currentPage.includes('doctor') || currentPage.includes('teacher')) return '医生姓名'
|
||||
return '名称'
|
||||
}
|
||||
|
||||
function inferPrimaryAction(currentPage: string) {
|
||||
if (currentPage.includes('case')) return '新增病例'
|
||||
if (currentPage.includes('task')) return '分配任务'
|
||||
if (currentPage.includes('hospital')) return '新增医院'
|
||||
if (currentPage.includes('department')) return '新增科室'
|
||||
return '新增'
|
||||
}
|
||||
|
||||
function createRows(title: string) {
|
||||
return [
|
||||
{ id: 'M-202606-001', name: `${title}核心数据维护`, category: '方正中心医院', owner: '张管理员', metric: '1,286', status: '正常' },
|
||||
{ id: 'M-202606-002', name: `${title}待审核任务`, category: '心内科', owner: '李医生', metric: '86%', status: '待审核' },
|
||||
{ id: 'M-202606-003', name: `${title}专题内容包`, category: '教学运营部', owner: '王内容', metric: '342', status: '已发布' },
|
||||
{ id: 'M-202606-004', name: `${title}试点项目`, category: '南城社区医院', owner: '陈医生', metric: '73%', status: '正常' }
|
||||
]
|
||||
}
|
||||
|
||||
function tagType(value: string) {
|
||||
if (value === '正常' || value === '已发布') return 'success'
|
||||
if (value === '待审核') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="page-stack">
|
||||
<section class="page-toolbar">
|
||||
<div>
|
||||
<h1>系统配置</h1>
|
||||
<p>配置平台参数、AI 评分策略、登录安全和通知规则。</p>
|
||||
</div>
|
||||
<el-button type="primary">保存配置</el-button>
|
||||
</section>
|
||||
|
||||
<section class="settings-grid">
|
||||
<el-card shadow="never">
|
||||
<template #header>AI 评分策略</template>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="评分模型">
|
||||
<el-select model-value="clinical-eval-v2">
|
||||
<el-option label="Clinical Eval v2" value="clinical-eval-v2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="通过阈值">
|
||||
<el-slider :model-value="82" />
|
||||
</el-form-item>
|
||||
<el-form-item label="自动生成改进建议">
|
||||
<el-switch :model-value="true" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>安全设置</template>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="登录验证码">
|
||||
<el-switch :model-value="false" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码有效期">
|
||||
<el-input-number :model-value="90" :min="30" :max="365" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SSO 登录">
|
||||
<el-switch :model-value="true" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="page-stack">
|
||||
<section class="page-toolbar">
|
||||
<div>
|
||||
<h1>训练管理</h1>
|
||||
<p>配置教学任务、训练对象、评分规则和完成进度。</p>
|
||||
</div>
|
||||
<el-button :icon="Plus" type="primary">发布任务</el-button>
|
||||
</section>
|
||||
|
||||
<section class="kanban-grid">
|
||||
<article v-for="group in taskGroups" :key="group.title" class="kanban-column">
|
||||
<div class="kanban-title">
|
||||
<h2>{{ group.title }}</h2>
|
||||
<el-tag>{{ group.items.length }}</el-tag>
|
||||
</div>
|
||||
<div v-for="task in group.items" :key="task.name" class="task-card">
|
||||
<strong>{{ task.name }}</strong>
|
||||
<span>{{ task.scope }}</span>
|
||||
<el-progress :percentage="task.progress" :status="task.progress > 90 ? 'success' : undefined" />
|
||||
<div class="task-meta">
|
||||
<span>{{ task.owner }}</span>
|
||||
<span>{{ task.due }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
|
||||
const taskGroups = [
|
||||
{
|
||||
title: '进行中',
|
||||
items: [
|
||||
{ name: '急性胸痛问诊训练', scope: '心内科 2026 级规培', progress: 76, owner: '李医生', due: '06-12 截止' },
|
||||
{ name: '糖尿病随访沟通训练', scope: '全科医学班', progress: 64, owner: '王医生', due: '06-18 截止' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '待审核',
|
||||
items: [
|
||||
{ name: '卒中识别专项训练', scope: '神经内科轮转', progress: 35, owner: '赵医生', due: '待发布' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '已完成',
|
||||
items: [
|
||||
{ name: '儿童发热初诊流程', scope: '儿科见习', progress: 98, owner: '陈医生', due: '已归档' },
|
||||
{ name: '呼吸困难鉴别诊断', scope: '急诊科', progress: 93, owner: '刘医生', due: '已归档' }
|
||||
]
|
||||
}
|
||||
]
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="page-stack">
|
||||
<section class="page-toolbar">
|
||||
<div>
|
||||
<h1>用户权限</h1>
|
||||
<p>维护管理员、内容运营、带教医生与学生账号。</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button :icon="Upload">导入用户</el-button>
|
||||
<el-button :icon="Plus" type="primary">新增用户</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="data-section">
|
||||
<el-table :data="users">
|
||||
<el-table-column prop="name" label="姓名" min-width="120" />
|
||||
<el-table-column prop="account" label="账号" min-width="140" />
|
||||
<el-table-column prop="role" label="角色" width="140" />
|
||||
<el-table-column prop="org" label="所属机构" min-width="180" />
|
||||
<el-table-column prop="lastLogin" label="最近登录" width="160" />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.enabled" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default>
|
||||
<el-button link type="primary">授权</el-button>
|
||||
<el-button link type="danger">禁用</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { Plus, Upload } from '@element-plus/icons-vue'
|
||||
|
||||
const users = reactive([
|
||||
{ name: '张管理员', account: 'admin', role: '超级管理员', org: '平台运营中心', lastLogin: '2026-06-03 13:40', enabled: true },
|
||||
{ name: '李医生', account: 'li.teacher', role: '带教医生', org: '方正中心医院', lastLogin: '2026-06-03 10:21', enabled: true },
|
||||
{ name: '王内容', account: 'wang.ops', role: '内容管理员', org: '内容运营部', lastLogin: '2026-06-02 18:09', enabled: true },
|
||||
{ name: '陈院管', account: 'chen.hospital', role: '医院管理员', org: '华东医学院', lastLogin: '2026-06-01 09:15', enabled: false }
|
||||
])
|
||||
</script>
|
||||
Reference in New Issue
Block a user