diff --git a/src/api/auth.ts b/src/api/auth.ts index 82232394..fc73966c 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,4 +1,4 @@ -export type LoginRole = 'super_admin' | 'doctor' +export type LoginRole = 'super_admin' | 'doctor' | 'hospital_admin' | 'content_admin' export interface LoginPayload { account: string diff --git a/src/api/stats.ts b/src/api/stats.ts new file mode 100644 index 00000000..9479e7ff --- /dev/null +++ b/src/api/stats.ts @@ -0,0 +1,649 @@ +export interface KpiOverview { + institution_count?: number | null + mau_institution?: number | null + user_total?: number | null + mau_user?: number | null +} + +export interface PlatformCoreOverview { + train_new_month?: number | null + train_new_mom?: number | null + train_total?: number | null + complete_rate?: number | null + avg_score?: number | null +} + +export interface TrendOverview { + months: string[] + train_counts: number[] + active_users?: number[] +} + +export interface UserComposeItem { + role: string + count: number +} + +export interface HourlyAverageItem { + hour: number + avg: number +} + +export interface InstitutionDistributionItem { + id: number + institution: string + users: number + active: number +} + +export interface HospitalTrainRankItem { + id: number + institution: string + count: number +} + +export interface HospitalScoreRankItem { + id: number + institution: string + avg_score: number +} + +export interface CaseTypeCountItem { + case_type: string + count: number +} + +export interface CaseTypeUsageRateItem { + case_type: string + rate: number +} + +export interface CaseUsageItem { + case_id: number + title: string + count: number +} + +export interface CasePassRateItem { + case_id: number + title: string + pass_rate: number + total: number +} + +export interface PlatformCaseAssetOverview { + total?: number | null + new_month?: number | null + new_mom?: number | null + type_dist: CaseTypeCountItem[] + type_usage_rate: CaseTypeUsageRateItem[] + top_used: CaseUsageItem[] + low_pass: CasePassRateItem[] +} + +export interface PlatformOverview { + kpi: KpiOverview + core: PlatformCoreOverview + trend: TrendOverview + user_compose: UserComposeItem[] + hourly_7d: HourlyAverageItem[] + institution_dist: InstitutionDistributionItem[] + hospital_rank: { + by_train: HospitalTrainRankItem[] + by_avg_score: HospitalScoreRankItem[] + } + case_asset: PlatformCaseAssetOverview +} + +export interface HospitalOverview { + profile: { + institution_id?: number | null + name?: string + logo?: string + level?: string + cooperation_days?: number | null + } + summary: { + dept_count?: number | null + doctor_count?: number | null + student_count?: number | null + train_total?: number | null + complete_rate?: number | null + avg_score?: number | null + train_months: string[] + train_monthly: number[] + } + dept_rank: Array<{ + id: number + department: string + case_count: number + train_count: number + effective_train: number + active_users: number + avg_score: number + }> + case_asset: { + total?: number | null + new_month?: number | null + top_used: CaseUsageItem[] + pass_high: CasePassRateItem[] + pass_low: CasePassRateItem[] + } + competency: { + student_avg?: number | null + platform_avg?: number | null + radar: CompetencyDimension[] + radar_platform: CompetencyDimension[] + } +} + +export interface ContentOverview { + summary: { + case_total?: number | null + pending_publish?: number | null + case_new_month?: number | null + case_new_mom?: number | null + usage_rate?: number | null + train_total?: number | null + train_mom?: number | null + } + dist: { + type_dist: CaseTypeCountItem[] + type_train: Record + dept_dist: Array<{ + id: number + department: string + case_count: number + used_count: number + train_count: number + }> + difficulty_dist: Array<{ + difficulty: string + case_count: number + train_count: number + }> + } + warning: CasePassRateItem[] + hot: CaseUsageItem[] +} + +export interface TeachingStudentOverviewItem { + id: number + real_name: string + username: string + department: string + train_total: number + complete_rate: number + avg_score: number + weak_dimensions: string[] + most_trained_type: string + last_trained_at: string + pending_tasks: number | null +} + +export interface CompetencyDimension { + dimension: string + score: number +} + +export interface TeachingOverview { + students: TeachingStudentOverviewItem[] + overview: { + student_count?: number | null + radar: CompetencyDimension[] + students_avg?: number | null + institution_avg?: number | null + train_months: string[] + train_monthly: number[] + task_summary?: unknown + } +} + +export interface TrainingRecordListParams { + token: string + scope: 'platform' | 'teacher' + search?: string + user_id?: string + case_id?: string + status?: string + training_mode?: string + case_type?: string + institution?: string + start_date?: string + end_date?: string + min_score?: string + max_score?: string + page?: number +} + +export interface TrainingRecordItem { + id: string + userId: string + userName: string + username: string + phone: string + caseId: string + caseTitle: string + status: string + trainingMode: string + caseType: string + institution: string + score: number | null + createdAt: string + completedAt: string + duration: string + raw: unknown +} + +export interface TrainingRecordListResult { + records: TrainingRecordItem[] + total: number +} + +export interface StudentRankingItem { + id: string + name: string + username: string + department: string + value: number | null + score: number | null + trainCount: number | null + completed: number | null + completeRate: number | null + raw: unknown +} + +export interface StudentRankingResult { + students: StudentRankingItem[] + total: number +} + +export interface StudentCompetency { + profile: { + id: string + name: string + username: string + department: string + } + summary: { + trainTotal: number | null + completeRate: number | null + avgScore: number | null + lastTrainedAt: string + } + radar: CompetencyDimension[] + trend: { + months: string[] + trainCounts: number[] + scores: number[] + } + weakDimensions: CompetencyDimension[] + raw: unknown +} + +const listKeys = ['results', 'list', 'rows', 'items', 'records', 'students', 'data'] +const totalKeys = ['count', 'total', 'total_count', 'totalCount'] + +function createAuthorization(token: string) { + return /^Bearer\s+/i.test(token) ? token : `Bearer ${token}` +} + +function parseResponseText(text: string): unknown { + if (!text) { + return null + } + + try { + return JSON.parse(text) + } catch { + return null + } +} + +function getMessageFromResponse(data: unknown): string { + if (!data || typeof data !== 'object') { + return '' + } + + const record = data as Record + const message = record.message || record.msg || record.detail + if (typeof message === 'string') { + return message + } + + return getMessageFromResponse(record.data) +} + +function unwrapData(data: unknown): unknown { + if (!data || typeof data !== 'object' || Array.isArray(data)) { + return data + } + + const record = data as Record + const nested = record.data || record.result || record.payload + if (nested && typeof nested === 'object') { + const metaKeys = ['message', 'msg', 'code', 'success'] + const dataOnlyKeys = Object.keys(record).filter(key => !metaKeys.includes(key)) + if (dataOnlyKeys.length === 1 || ('data' in record && !('kpi' in record) && !('summary' in record))) { + return unwrapData(nested) + } + } + + return data +} + +async function requestJson(url: string, token: string, fallbackMessage: string): Promise { + const response = await fetch(url, { + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: createAuthorization(token) + } + }) + const text = await response.text() + const data = parseResponseText(text) + + if (!response.ok) { + const message = getMessageFromResponse(data) || fallbackMessage + throw new Error(message) + } + + return unwrapData(data) as T +} + +function getRecord(value: unknown): Record { + return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : {} +} + +function getFirst(record: Record, keys: string[]): unknown { + for (const key of keys) { + const value = record[key] + if (value !== undefined && value !== null) { + return value + } + } + + return undefined +} + +function getString(record: Record, keys: string[], fallback = ''): string { + const value = getFirst(record, keys) + if (typeof value === 'string') { + return value + } + if (typeof value === 'number') { + return String(value) + } + + return fallback +} + +function getNumber(record: Record, keys: string[]): number | null { + const value = getFirst(record, keys) + if (typeof value === 'number' && Number.isFinite(value)) { + return value + } + if (typeof value === 'string' && value.trim() && Number.isFinite(Number(value))) { + return Number(value) + } + + return null +} + +function getTotal(record: Record, fallback: number): number { + for (const key of totalKeys) { + const value = record[key] + if (typeof value === 'number') { + return value + } + if (typeof value === 'string' && Number.isFinite(Number(value))) { + return Number(value) + } + } + + return fallback +} + +function findListPayload(data: unknown): { items: unknown[]; total: number } { + if (Array.isArray(data)) { + return { items: data, total: data.length } + } + if (!data || typeof data !== 'object') { + return { items: [], total: 0 } + } + + const record = data as Record + for (const key of listKeys) { + const value = record[key] + if (Array.isArray(value)) { + return { items: value, total: getTotal(record, value.length) } + } + } + + const nested = record.data || record.result || record.payload + if (nested && typeof nested === 'object') { + const payload = findListPayload(nested) + return { + items: payload.items, + total: payload.total || getTotal(record, payload.items.length) + } + } + + return { items: [], total: getTotal(record, 0) } +} + +function createQuery(params: Record) { + const query = new URLSearchParams() + Object.entries(params).forEach(([key, value]) => { + if (value === undefined || value === null || value === '') { + return + } + if (typeof value === 'string' && !value.trim()) { + return + } + query.set(key, String(value).trim()) + }) + + return query +} + +function getNestedString(record: Record, nestedKeys: string[], valueKeys: string[], fallback = '') { + for (const key of nestedKeys) { + const nested = getRecord(record[key]) + const value = getString(nested, valueKeys) + if (value) { + return value + } + } + + return fallback +} + +function getNestedNumber(record: Record, nestedKeys: string[], valueKeys: string[]) { + for (const key of nestedKeys) { + const nested = getRecord(record[key]) + const value = getNumber(nested, valueKeys) + if (value !== null) { + return value + } + } + + return null +} + +function normalizeTrainingRecord(item: unknown): TrainingRecordItem { + const record = getRecord(item) + const user = getRecord(getFirst(record, ['user', 'student', 'trainee'])) + const caseRecord = getRecord(getFirst(record, ['case', 'case_info', 'training_case'])) + + return { + id: getString(record, ['id', 'record_id', 'recordId', 'uuid']), + userId: getString(record, ['user_id', 'userId', 'student_id', 'studentId']) || getString(user, ['id', 'user_id']), + userName: getString(record, ['user_name', 'real_name', 'realName', 'student_name', 'studentName']) || getString(user, ['real_name', 'realName', 'name']), + username: getString(record, ['username', 'account']) || getString(user, ['username', 'account']), + phone: getString(record, ['phone', 'mobile']) || getString(user, ['phone', 'mobile', 'username']), + caseId: getString(record, ['case_id', 'caseId']) || getString(caseRecord, ['id', 'case_id']), + caseTitle: getString(record, ['case_title', 'caseTitle', 'title']) || getString(caseRecord, ['title', 'name']), + status: getString(record, ['status', 'state']), + trainingMode: getString(record, ['training_mode', 'trainingMode', 'mode']), + caseType: getString(record, ['case_type', 'caseType']) || getString(caseRecord, ['case_type', 'caseType']), + institution: getString(record, ['institution', 'institution_name', 'institutionName', 'hospital', 'hospital_name', 'hospitalName']) || + getNestedString(user, ['institution', 'hospital'], ['name', 'institution', 'institution_name']), + score: getNumber(record, ['score', 'final_score', 'finalScore', 'total_score', 'totalScore', 'normalized_score', 'normalizedScore', 'avg_score']), + createdAt: getString(record, ['created_at', 'createdAt', 'start_time', 'startTime', 'trained_at', 'trainedAt']), + completedAt: getString(record, ['completed_at', 'completedAt', 'end_time', 'endTime', 'finished_at', 'finishedAt']), + duration: getString(record, ['duration', 'duration_text', 'durationText', 'elapsed', 'elapsed_seconds', 'elapsedSeconds']), + raw: item + } +} + +function normalizeRankingItem(item: unknown, dimension: string): StudentRankingItem { + const record = getRecord(item) + const user = getRecord(getFirst(record, ['user', 'student'])) + const dimensions = getRecord(getFirst(record, ['dimensions', 'dimension_scores', 'scores'])) + const dimensionValue = getNumber(record, [dimension]) ?? getNumber(dimensions, [dimension]) + const score = getNumber(record, ['score', 'avg_score', 'avgScore', 'students_avg']) + const trainCount = getNumber(record, ['train_count', 'trainCount', 'train_total', 'trainTotal']) + const completed = getNumber(record, ['completed', 'completed_count', 'completedCount', 'effective_train']) + const completeRate = getNumber(record, ['complete_rate', 'completeRate', 'completed_rate']) + const fallbackValue = dimension === 'train_count' + ? trainCount + : dimension === 'completed' + ? completed ?? completeRate + : score + + return { + id: getString(record, ['id', 'user_id', 'userId', 'student_id', 'studentId']) || getString(user, ['id', 'user_id']), + name: getString(record, ['real_name', 'realName', 'name', 'student_name', 'studentName']) || getString(user, ['real_name', 'realName', 'name']), + username: getString(record, ['username', 'account']) || getString(user, ['username', 'account']), + department: getString(record, ['department', 'department_name', 'departmentName']) || + getNestedString(user, ['department'], ['name', 'department']), + value: getNumber(record, ['value', 'rank_value', 'rankValue']) ?? dimensionValue ?? fallbackValue, + score, + trainCount, + completed, + completeRate, + raw: item + } +} + +function normalizeDimensionList(value: unknown): CompetencyDimension[] { + if (Array.isArray(value)) { + return value + .map(item => { + const record = getRecord(item) + const dimension = getString(record, ['dimension', 'name', 'label']) + const score = getNumber(record, ['score', 'value', 'rate']) + return dimension && score !== null ? { dimension, score } : null + }) + .filter((item): item is CompetencyDimension => Boolean(item)) + } + + const record = getRecord(value) + return Object.entries(record) + .map(([dimension, score]) => { + const numericScore = typeof score === 'number' ? score : typeof score === 'string' && Number.isFinite(Number(score)) ? Number(score) : null + return numericScore !== null ? { dimension, score: numericScore } : null + }) + .filter((item): item is CompetencyDimension => Boolean(item)) +} + +function firstDimensionList(...values: unknown[]): CompetencyDimension[] { + for (const value of values) { + const list = normalizeDimensionList(value) + if (list.length) { + return list + } + } + + return [] +} + +function normalizeCompetency(data: unknown, id: string | number): StudentCompetency { + const root = getRecord(unwrapData(data)) + const student = getRecord(getFirst(root, ['student', 'profile', 'user'])) + const summary = getRecord(getFirst(root, ['summary', 'overview'])) + const competency = getRecord(root.competency) + const trend = getRecord(root.trend) + const radar = firstDimensionList(root.radar, competency.radar, root.dimension_scores, root.dimensions, summary.radar) + const explicitWeak = firstDimensionList(root.weak_dimensions, summary.weak_dimensions) + const weakDimensions = explicitWeak.length ? explicitWeak : radar.filter(item => item.score < 60) + + return { + profile: { + id: getString(root, ['id', 'student_id', 'studentId', 'user_id', 'userId'], String(id)) || getString(student, ['id', 'user_id'], String(id)), + name: getString(root, ['real_name', 'realName', 'name', 'student_name', 'studentName']) || getString(student, ['real_name', 'realName', 'name']), + username: getString(root, ['username', 'account']) || getString(student, ['username', 'account']), + department: getString(root, ['department', 'department_name', 'departmentName']) || getString(student, ['department', 'department_name', 'departmentName']) + }, + summary: { + trainTotal: getNumber(summary, ['train_total', 'trainTotal']) ?? getNumber(root, ['train_total', 'trainTotal']), + completeRate: getNumber(summary, ['complete_rate', 'completeRate']) ?? getNumber(root, ['complete_rate', 'completeRate']), + avgScore: getNumber(summary, ['avg_score', 'avgScore', 'student_avg']) ?? getNumber(root, ['avg_score', 'avgScore', 'student_avg']), + lastTrainedAt: getString(summary, ['last_trained_at', 'lastTrainedAt']) || getString(root, ['last_trained_at', 'lastTrainedAt']) + }, + radar, + trend: { + months: normalizeStringArray(getFirst(trend, ['months', 'train_months']) ?? getFirst(summary, ['months', 'train_months']) ?? root.train_months), + trainCounts: normalizeNumberArray(getFirst(trend, ['train_counts', 'train_monthly']) ?? getFirst(summary, ['train_counts', 'train_monthly']) ?? root.train_monthly), + scores: normalizeNumberArray(getFirst(trend, ['scores', 'avg_scores', 'score_monthly']) ?? getFirst(summary, ['scores', 'avg_scores', 'score_monthly'])) + }, + weakDimensions, + raw: data + } +} + +function normalizeStringArray(value: unknown): string[] { + return Array.isArray(value) ? value.map(item => String(item)) : [] +} + +function normalizeNumberArray(value: unknown): number[] { + return Array.isArray(value) + ? value.map(item => (typeof item === 'number' ? item : Number(item))).filter(item => Number.isFinite(item)) + : [] +} + +export async function fetchPlatformOverview(token: string): Promise { + return requestJson('/server/api/cms/stats/overview/', token, '获取平台概览失败') +} + +export async function fetchHospitalOverview(token: string): Promise { + return requestJson('/server/api/cms/stats/hospital/overview/', token, '获取医院概览失败') +} + +export async function fetchContentOverview(token: string): Promise { + return requestJson('/server/api/cms/stats/content/overview/', token, '获取内容概览失败') +} + +export async function fetchTeachingOverview(token: string): Promise { + return requestJson('/server/api/cms/stats/teaching/overview/', token, '获取教学概览失败') +} + +export async function fetchTrainingRecords(params: TrainingRecordListParams): Promise { + const { token, scope, ...queryParams } = params + const query = createQuery(queryParams) + const baseUrl = scope === 'teacher' ? '/server/api/cms/students/training-records/' : '/server/api/cms/training-records/' + const data = await requestJson(`${baseUrl}${query.toString() ? `?${query.toString()}` : ''}`, token, '获取训练记录失败') + const payload = findListPayload(data) + + return { + records: payload.items.map(normalizeTrainingRecord), + total: payload.total + } +} + +export async function fetchStudentCompetency(token: string, id: string | number): Promise { + const data = await requestJson(`/server/api/cms/students/${id}/competency/`, token, '获取学生能力画像失败') + return normalizeCompetency(data, id) +} + +export async function fetchStudentRanking(token: string, dimension = 'score'): Promise { + const query = createQuery({ dimension }) + const data = await requestJson(`/server/api/cms/students/ranking/?${query.toString()}`, token, '获取学生排行榜失败') + const payload = findListPayload(data) + + return { + students: payload.items.map(item => normalizeRankingItem(item, dimension)), + total: payload.total + } +} diff --git a/src/assets/styles/main.scss b/src/assets/styles/main.scss index 71ac6c9e..d0d3f923 100644 --- a/src/assets/styles/main.scss +++ b/src/assets/styles/main.scss @@ -198,8 +198,7 @@ p { .brand-copy, .nav-item span, - .user-meta, - .role-select { + .user-meta { display: none; } @@ -359,10 +358,6 @@ p { } } -.role-select { - width: 100%; -} - .main-panel { min-width: 0; } @@ -779,6 +774,18 @@ p { grid-template-columns: minmax(260px, 1fr) 150px 96px 96px; } +.training-record-filter { + grid-template-columns: minmax(220px, 1fr) 110px 110px 140px 140px 140px 140px 260px 100px 100px 96px 96px; +} + +.teacher-training-record-filter { + grid-template-columns: minmax(220px, 1fr) 110px 110px 140px 140px 140px 260px 100px 100px 96px 96px; +} + +.ranking-kpis { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + .table-subtext { display: block; margin-top: 4px; diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue index dfa6867e..07149468 100644 --- a/src/layouts/AdminLayout.vue +++ b/src/layouts/AdminLayout.vue @@ -35,9 +35,6 @@ {{ appStore.roleLabel }} - - - @@ -89,19 +86,13 @@ import { computed, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import { ArrowDown, Bell, Fold, Moon, QuestionFilled, Search, Sunny } from '@element-plus/icons-vue' -import { roleOptions } from '@/mock/dashboard' -import { getFirstPage, getPagePath, pageTitles, roleMenus } from '@/mock/navigation' +import { getPagePath, pageTitles, roleMenus } from '@/mock/navigation' import { useAppStore } from '@/stores/app' -import type { RoleKey } from '@/types' const route = useRoute() const router = useRouter() const appStore = useAppStore() const keyword = ref('') -const role = computed({ - get: () => appStore.user.role, - set: value => appStore.switchRole(value) -}) const visibleSections = computed(() => roleMenus[appStore.user.role]) const pageTitle = computed(() => { @@ -112,11 +103,6 @@ const pageTitle = computed(() => { return String(route.meta.title || '数据驾驶舱') }) -function handleRoleChange(value: RoleKey) { - appStore.switchRole(value) - router.push(getPagePath(getFirstPage(value))) -} - function handleCommand(command: string) { if (command === 'logout') { appStore.logout() diff --git a/src/router/index.ts b/src/router/index.ts index ea0d23fd..5ced4d03 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -15,7 +15,13 @@ const routes: RouteRecordRaw[] = [ children: [ { path: 'dashboard', name: 'Dashboard', component: () => import('@/views/DashboardView.vue'), meta: { title: '数据驾驶舱' } }, { path: 'cases', name: 'Cases', component: () => import('@/views/CasesView.vue'), meta: { title: '病例中心' } }, - { path: 'training', name: 'Training', component: () => import('@/views/TrainingView.vue'), meta: { title: '训练管理' } }, + { path: 'training', name: 'Training', component: () => import('@/views/TrainingView.vue'), meta: { title: '训练记录' } }, + { path: 'platform/ability-profile', name: 'PlatformAbilityProfile', component: () => import('@/views/PlatformAbilityView.vue'), meta: { title: '能力画像' } }, + { path: 'platform/ability-radar', name: 'PlatformAbilityRadar', component: () => import('@/views/PlatformAbilityView.vue'), meta: { title: '雷达图分析' } }, + { path: 'teacher/training-records', name: 'TeacherTrainingRecords', component: () => import('@/views/TrainingView.vue'), meta: { title: '教学训练记录' } }, + { path: 'teacher/student-ability', name: 'StudentAbility', component: () => import('@/views/StudentAbilityView.vue'), meta: { title: '能力画像' } }, + { path: 'teacher/student-ranking', name: 'StudentRanking', component: () => import('@/views/StudentRankingView.vue'), meta: { title: '学生排行榜' } }, + { path: 'teacher/growth-path', name: 'TeacherGrowthPath', component: () => import('@/views/TeacherGrowthPathView.vue'), meta: { title: '成长轨迹' } }, { path: 'institutions', name: 'Institutions', component: () => import('@/views/InstitutionsView.vue'), meta: { title: '医院管理' } }, { path: 'departments', name: 'Departments', component: () => import('@/views/DepartmentsView.vue'), meta: { title: '科室管理' } }, { path: 'users', name: 'Users', component: () => import('@/views/UsersView.vue'), meta: { title: '用户列表' } }, @@ -42,6 +48,13 @@ const routes: RouteRecordRaw[] = [ { path: 'module/my-students', redirect: '/my-students' }, { path: 'module/student-list', redirect: '/users/students' }, { path: 'module/teacher-list', redirect: '/teacher-student-relations' }, + { path: 'module/training-list', redirect: '/training' }, + { path: 'module/ability-profile', redirect: '/platform/ability-profile' }, + { path: 'module/ability-radar', redirect: '/platform/ability-radar' }, + { path: 'module/training-record', redirect: '/teacher/training-records' }, + { path: 'module/student-ability', redirect: '/teacher/student-ability' }, + { path: 'module/leaderboard', redirect: '/teacher/student-ranking' }, + { path: 'module/growth-path', redirect: '/teacher/growth-path' }, { path: 'module/user-list', redirect: '/users' }, { path: 'module/knowledge-base', name: 'KnowledgeBase', component: () => import('@/views/KnowledgeBaseView.vue'), meta: { title: '知识库管理' } }, { path: 'module/:page', name: 'Module', component: () => import('@/views/ModuleView.vue'), meta: { title: '业务模块' } } diff --git a/src/stores/app.ts b/src/stores/app.ts index 5fa90286..4eb0dfe2 100644 --- a/src/stores/app.ts +++ b/src/stores/app.ts @@ -13,7 +13,9 @@ const roleProfiles: Record> = const loginRoleMap: Record = { super_admin: 'super-admin', - doctor: 'teacher' + doctor: 'teacher', + hospital_admin: 'hospital-admin', + content_admin: 'content-admin' } function getStoredRole(): RoleKey { diff --git a/src/views/ContentDashboardView.vue b/src/views/ContentDashboardView.vue index af33527e..6964f366 100644 --- a/src/views/ContentDashboardView.vue +++ b/src/views/ContentDashboardView.vue @@ -1,5 +1,5 @@