feat: scaffold mediai admin

This commit is contained in:
王天骄
2026-06-03 15:21:07 +08:00
parent 7da12b0c07
commit 875bf1f098
665 changed files with 4345 additions and 73920 deletions
+76
View File
@@ -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>
+109
View File
@@ -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>
+34
View File
@@ -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>
+92
View File
@@ -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>
+320
View File
@@ -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>
+45
View File
@@ -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>
+56
View File
@@ -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>
+47
View File
@@ -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>