From 4a1fb3bc5650ef9cfd0e7eaac30787bad93568f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A4=A9=E9=AA=84?= <5307576@qq.com> Date: Wed, 17 Jun 2026 15:11:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=96=B0=E5=A2=9E=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=85=A5=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/institutions.ts | 53 +++ src/api/stats.ts | 4 + src/api/teacherStudentRelations.ts | 115 +++++- src/mock/navigation.ts | 8 +- src/router/index.ts | 1 + src/views/HospitalSettingsView.vue | 415 ++++++++++++++++++++++ src/views/InstitutionsView.vue | 239 ++++++++++++- src/views/TeacherStudentRelationsView.vue | 216 ++++++++++- 8 files changed, 1033 insertions(+), 18 deletions(-) create mode 100644 src/views/HospitalSettingsView.vue diff --git a/src/api/institutions.ts b/src/api/institutions.ts index 2c0bc6c3..52eb5b4f 100644 --- a/src/api/institutions.ts +++ b/src/api/institutions.ts @@ -32,6 +32,7 @@ export interface InstitutionPayload { level?: string province?: string city?: string + banner_url?: string } export interface CreateInstitutionPayload extends InstitutionPayload { @@ -53,6 +54,18 @@ export interface DisableInstitutionParams { id: number } +export interface UploadInstitutionBannerParams { + token: string + id: number + file: File +} + +export interface UploadInstitutionBannerResult { + message: string + bannerUrl: string + raw: unknown +} + export interface ImportInstitutionsParams { token: string file: File @@ -102,6 +115,25 @@ function getString(record: Record, key: string): string { return '' } +function getBannerUrlFromResponse(data: unknown): string { + if (!data || typeof data !== 'object') { + return '' + } + + const record = data as Record + const bannerUrl = record.banner_url || record.bannerUrl + if (typeof bannerUrl === 'string') { + return bannerUrl + } + + const nested = record.data || record.result || record.payload + if (nested && typeof nested === 'object') { + return getBannerUrlFromResponse(nested) + } + + return '' +} + function normalizeInstitution(item: unknown): InstitutionListItem { const record = item && typeof item === 'object' ? (item as Record) : {} const id = record.id @@ -291,6 +323,27 @@ export async function disableInstitution(params: DisableInstitutionParams): Prom return parseMutationResponse(response, '停用机构失败') } +export async function uploadInstitutionBanner(params: UploadInstitutionBannerParams): Promise { + const formData = new FormData() + formData.append('file', params.file) + + const response = await fetch(`/server/api/cms/institutions/${params.id}/banner/`, { + method: 'POST', + headers: { + Accept: 'application/json', + Authorization: createAuthorization(params.token) + }, + body: formData + }) + const data = await parseMutationResponse(response, '上传机构背景图失败') + + return { + message: getMessageFromResponse(data), + bannerUrl: getBannerUrlFromResponse(data), + raw: data + } +} + export async function importInstitutions(params: ImportInstitutionsParams): Promise { const formData = new FormData() formData.append('file', params.file) diff --git a/src/api/stats.ts b/src/api/stats.ts index 9479e7ff..5c83a745 100644 --- a/src/api/stats.ts +++ b/src/api/stats.ts @@ -101,6 +101,10 @@ export interface HospitalOverview { name?: string logo?: string level?: string + province?: string + city?: string + banner_url?: string + bannerUrl?: string cooperation_days?: number | null } summary: { diff --git a/src/api/teacherStudentRelations.ts b/src/api/teacherStudentRelations.ts index 17a2cb6d..31378a94 100644 --- a/src/api/teacherStudentRelations.ts +++ b/src/api/teacherStudentRelations.ts @@ -26,22 +26,29 @@ export interface TeacherStudentRelationListResult { total: number } +export interface TeacherStudentRelationUserOption { + id: string + name: string + phone: string +} + export interface TeacherStudentRelationPayload { + teacher_name?: string teacher_phone?: string + student_name?: string student_phone?: string relation_type?: string status?: 0 | 1 } export interface CreateTeacherStudentRelationPayload extends TeacherStudentRelationPayload { + teacher_name: string teacher_phone: string + student_name: string student_phone: string } -export interface UpdateTeacherStudentRelationPayload extends TeacherStudentRelationPayload { - teacher_phone: string - student_phone: string -} +export interface UpdateTeacherStudentRelationPayload extends TeacherStudentRelationPayload {} export interface CreateTeacherStudentRelationParams { token: string @@ -188,6 +195,60 @@ function getResults(data: unknown): unknown[] { return [] } +function getOptionResults(data: unknown, keys: string[]): unknown[] { + if (Array.isArray(data)) { + return data + } + if (!data || typeof data !== 'object') { + return [] + } + + const record = data as Record + for (const key of keys) { + const value = record[key] + if (Array.isArray(value)) { + return value + } + } + if (record.data && typeof record.data === 'object') { + return getOptionResults(record.data, keys) + } + + return [] +} + +function normalizeRelationUserOption(item: unknown): TeacherStudentRelationUserOption | null { + if (typeof item === 'string' || typeof item === 'number') { + const phone = String(item) + return { id: phone, name: '', phone } + } + if (!item || typeof item !== 'object') { + return null + } + + const record = item as Record + const phone = getString(record, [ + 'phone', + 'mobile', + 'username', + 'account', + 'teacher_phone', + 'teacherPhone', + 'student_phone', + 'studentPhone', + 'value' + ]) + if (!phone) { + return null + } + + return { + id: getString(record, ['id', 'user_id', 'userId', 'teacher_id', 'teacherId', 'student_id', 'studentId'], phone), + name: getString(record, ['real_name', 'realName', 'name', 'label', 'nickname'], ''), + phone + } +} + function createRelationQuery(params: Partial, includePage = true) { const query = new URLSearchParams() if (params.teacher?.trim()) { @@ -209,6 +270,34 @@ function createRelationQuery(params: Partial, return query } +async function fetchRelationUserOptions(url: string, token: string, listKeys: string[], fallbackMessage: string) { + 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) + } + + const seen = new Set() + return getOptionResults(data, listKeys) + .map(normalizeRelationUserOption) + .filter((item): item is TeacherStudentRelationUserOption => { + if (!item || seen.has(item.phone)) { + return false + } + seen.add(item.phone) + return true + }) +} + async function parseMutationResponse(response: Response, fallbackMessage: string): Promise { const text = await response.text() const data = parseResponseText(text) @@ -282,6 +371,24 @@ export async function fetchTeacherStudentRelations( } } +export async function fetchTeacherStudentRelationDoctors(token: string): Promise { + return fetchRelationUserOptions( + '/server/api/cms/teacher-student-relations/doctors/', + token, + ['results', 'list', 'rows', 'items', 'records', 'doctors', 'teachers'], + '获取带教医生列表失败' + ) +} + +export async function fetchTeacherStudentRelationStudents(token: string): Promise { + return fetchRelationUserOptions( + '/server/api/cms/teacher-student-relations/students/', + token, + ['results', 'list', 'rows', 'items', 'records', 'students'], + '获取学生列表失败' + ) +} + export async function createTeacherStudentRelation(params: CreateTeacherStudentRelationParams): Promise { const response = await fetch('/server/api/cms/teacher-student-relations/', { method: 'POST', diff --git a/src/mock/navigation.ts b/src/mock/navigation.ts index dbe2596a..b3e20fd2 100644 --- a/src/mock/navigation.ts +++ b/src/mock/navigation.ts @@ -78,7 +78,13 @@ export const roleMenus: Record = { }, ], 'hospital-admin': [ - { title: '概览', items: [{ page: 'hospital-dashboard', icon: OfficeBuilding, title: '医院驾驶舱' }] }, + { + title: '概览', + items: [ + { page: 'hospital-dashboard', icon: OfficeBuilding, title: '医院驾驶舱' }, + { page: 'hospital-settings', icon: Setting, title: '本院配置' } + ] + }, { title: '人员管理', items: [ diff --git a/src/router/index.ts b/src/router/index.ts index 1cf4c19a..538dc002 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -40,6 +40,7 @@ const routes: RouteRecordRaw[] = [ { path: 'module/dept-analysis', name: 'HospitalDeptAnalysis', component: () => import('@/views/HospitalStatsModuleView.vue'), meta: { title: '科室分析' } }, { path: 'module/ability-trend', name: 'HospitalAbilityTrend', component: () => import('@/views/HospitalStatsModuleView.vue'), meta: { title: '能力趋势' } }, { path: 'module/student-ranking', name: 'HospitalStudentRanking', component: () => import('@/views/HospitalStatsModuleView.vue'), meta: { title: '学生排行' } }, + { path: 'module/hospital-settings', name: 'HospitalSettings', component: () => import('@/views/HospitalSettingsView.vue'), meta: { title: '本院配置' } }, { path: 'module/content-dashboard', name: 'ContentDashboard', component: () => import('@/views/ContentDashboardView.vue'), meta: { title: '内容概览' } }, { path: 'module/content-stats', name: 'ContentStats', component: () => import('@/views/ContentStatsView.vue'), meta: { title: '内容统计' } }, { path: 'module/teacher-dashboard', name: 'TeacherDashboard', component: () => import('@/views/TeacherDashboardView.vue'), meta: { title: '教学概览' } }, diff --git a/src/views/HospitalSettingsView.vue b/src/views/HospitalSettingsView.vue new file mode 100644 index 00000000..3541ae8b --- /dev/null +++ b/src/views/HospitalSettingsView.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/src/views/InstitutionsView.vue b/src/views/InstitutionsView.vue index 91e83b44..441fd91c 100644 --- a/src/views/InstitutionsView.vue +++ b/src/views/InstitutionsView.vue @@ -46,6 +46,12 @@ {{ formatRegion(row.province, row.city) }} + + +