diff --git a/index.html b/index.html
index 46d9818c..80948d5f 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
- MediAI - 医疗AI平台管理系统
+ 新方正集团
diff --git a/src/api/cases.ts b/src/api/cases.ts
index 32d89bb3..5977e418 100644
--- a/src/api/cases.ts
+++ b/src/api/cases.ts
@@ -49,6 +49,7 @@ export interface CaseExamItemPayload {
item_code: string
item_name?: string
item_type?: string
+ result_text?: string
}
export interface CreateCaseDraftPayload {
@@ -111,9 +112,17 @@ export interface AiGenerateCaseResult {
raw: unknown
}
+export interface ImportCasePdfResult {
+ parseId: string
+ caseType: DraftCaseType
+ data: Record
+ raw: unknown
+}
+
export interface ImportCasePdfParams {
token: string
file: File
+ case_type: DraftCaseType
}
export interface UpdateCaseRelationsPayload {
@@ -133,6 +142,12 @@ export interface CaseActionParams {
id: string | number
}
+export interface SubmitCasePayload extends CreateCaseDraftPayload {}
+
+export interface SubmitCaseParams extends CaseActionParams {
+ payload?: SubmitCasePayload
+}
+
const listKeys = ['results', 'list', 'rows', 'items', 'records', 'cases']
const totalKeys = ['count', 'total', 'total_count', 'totalCount']
@@ -231,6 +246,10 @@ function getStringList(record: Record, keys: string[]): string[
return []
}
+function normalizeDraftCaseType(value: string, fallback: DraftCaseType = 'traditional'): DraftCaseType {
+ return value === 'teaching' ? 'teaching' : fallback
+}
+
function normalizePublishStatus(value: unknown): CasePublishStatus | null {
if (value === 0 || value === 1 || value === 2) {
return value
@@ -384,6 +403,22 @@ function normalizeAiGenerateResult(data: unknown): AiGenerateCaseResult {
}
}
+function normalizeImportCasePdfResult(data: unknown, fallbackCaseType: DraftCaseType): ImportCasePdfResult {
+ const root = getRecord(data)
+ const payload = root.parse_id || root.parseId || root.case_type || root.caseType || root.title
+ ? root
+ : getRecord(root.data)
+ const nestedData = getRecord(payload.data)
+ const generatedData = Object.keys(nestedData).length ? nestedData : payload
+
+ return {
+ parseId: getString(payload, ['parse_id', 'parseId']),
+ caseType: normalizeDraftCaseType(getString(payload, ['case_type', 'caseType']), fallbackCaseType),
+ data: generatedData,
+ raw: data
+ }
+}
+
function createCaseQuery(params: Partial) {
const query = new URLSearchParams()
if (params.search?.trim()) {
@@ -489,9 +524,10 @@ export async function generateCaseWithAi(params: AiGenerateCaseParams): Promise<
return normalizeAiGenerateResult(data)
}
-export async function importCasePdf(params: ImportCasePdfParams): Promise {
+export async function importCasePdf(params: ImportCasePdfParams): Promise {
const formData = new FormData()
- formData.append('file', params.file)
+ formData.append('files', params.file)
+ formData.append('case_type', params.case_type)
const response = await fetch('/server/api/cms/cases/import-pdf/', {
method: 'POST',
@@ -502,7 +538,8 @@ export async function importCasePdf(params: ImportCasePdfParams): Promise {
@@ -519,13 +556,15 @@ export async function updateCaseRelations(params: UpdateCaseRelationsParams): Pr
return parseMutationResponse(response, '编辑病例关联失败')
}
-export async function submitCase(params: CaseActionParams): Promise {
+export async function submitCase(params: SubmitCaseParams): Promise {
const response = await fetch(`/server/api/cms/cases/${params.id}/submit/`, {
method: 'POST',
headers: {
Accept: 'application/json',
- Authorization: createAuthorization(params.token)
- }
+ Authorization: createAuthorization(params.token),
+ ...(params.payload ? { 'Content-Type': 'application/json' } : {})
+ },
+ ...(params.payload ? { body: JSON.stringify(params.payload) } : {})
})
return parseMutationResponse(response, '提交病例失败')
diff --git a/src/api/users.ts b/src/api/users.ts
index 5a1b8363..0e455157 100644
--- a/src/api/users.ts
+++ b/src/api/users.ts
@@ -394,7 +394,7 @@ export async function resetUserPassword(params: ResetUserPasswordParams): Promis
export async function importUsers(params: ImportUsersParams): Promise {
const formData = new FormData()
- formData.append('file', params.file)
+ formData.append('files', params.file)
const response = await fetch('/server/api/cms/users/import/', {
method: 'POST',
diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png
new file mode 100644
index 00000000..bbad25ea
Binary files /dev/null and b/src/assets/images/logo.png differ
diff --git a/src/assets/styles/main.scss b/src/assets/styles/main.scss
index d0d3f923..beec6d5d 100644
--- a/src/assets/styles/main.scss
+++ b/src/assets/styles/main.scss
@@ -90,13 +90,21 @@ p {
display: inline-flex;
align-items: center;
justify-content: center;
+ gap: 10px;
height: 42px;
- padding: 0 16px;
+ padding: 0 16px 0 8px;
border: 1px solid rgb(255 255 255 / 38%);
border-radius: 8px;
font-size: 18px;
font-weight: 800;
background: rgb(255 255 255 / 12%);
+
+ img {
+ width: 30px;
+ height: 30px;
+ border-radius: 6px;
+ object-fit: cover;
+ }
}
.login-brand h1 {
@@ -194,17 +202,46 @@ p {
transition: grid-template-columns 0.22s ease;
&.collapsed {
- grid-template-columns: 82px 1fr;
+ grid-template-columns: 88px 1fr;
.brand-copy,
+ .nav-section-title,
.nav-item span,
.user-meta {
display: none;
}
- .sidebar-header,
+ .sidebar-header {
+ display: grid;
+ grid-template-rows: 38px 26px;
+ align-content: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 0;
+ }
+
.nav-item {
justify-content: center;
+ padding: 0;
+ }
+
+ .nav-list {
+ padding: 16px 18px;
+ }
+
+ .sidebar-toggle {
+ position: static;
+ margin: 0 auto;
+ width: 26px;
+ height: 26px;
+ min-height: 26px;
+ color: #c7d2fe;
+ background: rgb(255 255 255 / 12%);
+ }
+
+ .user-card {
+ justify-content: center;
+ margin-bottom: 0;
}
.sidebar-footer {
@@ -235,6 +272,7 @@ p {
}
.sidebar-header {
+ position: relative;
display: flex;
align-items: center;
gap: 12px;
@@ -250,13 +288,20 @@ p {
justify-content: center;
width: 38px;
height: 38px;
+ overflow: hidden;
border-radius: 8px;
- color: #fff;
- font-weight: 800;
- background: linear-gradient(135deg, var(--primary), var(--teal));
+ background: #fff;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
}
.brand-copy {
+ min-width: 0;
+
strong,
span {
display: block;
@@ -264,7 +309,8 @@ p {
strong {
color: #fff;
- font-size: 18px;
+ font-size: 17px;
+ white-space: nowrap;
}
span {
@@ -274,6 +320,20 @@ p {
}
}
+.sidebar-toggle {
+ margin-left: auto;
+ color: #dbeafe;
+ border-color: rgb(255 255 255 / 22%);
+ background: rgb(255 255 255 / 8%);
+
+ &:hover,
+ &:focus {
+ color: #fff;
+ border-color: rgb(255 255 255 / 38%);
+ background: rgb(37 99 235 / 34%);
+ }
+}
+
.sidebar-scroll {
flex: 1;
}
@@ -307,6 +367,7 @@ p {
transition: background 0.16s ease, color 0.16s ease;
.el-icon {
+ flex: 0 0 auto;
font-size: 18px;
}
@@ -801,6 +862,7 @@ p {
}
.case-create-form,
+.case-import-form,
.case-relations-form {
.el-select,
.el-input-number {
@@ -844,7 +906,7 @@ p {
}
.exam-item-row {
- grid-template-columns: minmax(150px, 0.8fr) minmax(160px, 1fr) minmax(120px, 0.8fr) 36px;
+ grid-template-columns: minmax(130px, 0.8fr) minmax(140px, 1fr) minmax(110px, 0.7fr) minmax(150px, 1fr) 36px;
}
.case-empty-line {
@@ -862,6 +924,18 @@ p {
.case-detail-drawer {
display: grid;
gap: 16px;
+ min-height: 100%;
+}
+
+.case-detail-form {
+ align-content: start;
+}
+
+.drawer-form-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+ padding-top: 8px;
}
.kanban-grid {
@@ -990,6 +1064,16 @@ p {
gap: 16px;
}
+.ai-result-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.ai-draft-form {
+ min-width: 0;
+}
+
.ai-result-kpis {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue
index 07149468..91222a58 100644
--- a/src/layouts/AdminLayout.vue
+++ b/src/layouts/AdminLayout.vue
@@ -2,11 +2,16 @@
@@ -388,6 +554,7 @@ import {
submitCase,
updateCaseRelations,
type CaseExamItemPayload,
+ type ImportCasePdfResult,
type CaseListItem,
type CasePublishStatus,
type CaseScoringRulePayload,
@@ -411,6 +578,7 @@ interface ExamItemForm {
item_code: string
item_name: string
item_type: string
+ result_text: string
}
interface CaseDraftForm {
@@ -443,13 +611,16 @@ const savingCase = ref(false)
const importing = ref(false)
const savingRelations = ref(false)
const detailLoading = ref(false)
+const submittingDetail = ref(false)
const caseDialogVisible = ref(false)
const importDialogVisible = ref(false)
const relationsDialogVisible = ref(false)
const detailDrawerVisible = ref(false)
const caseFormRef = ref()
+const detailFormRef = ref()
const importUploadRef = ref()
const importFile = ref(null)
+const importCaseType = ref('traditional')
const importFileList = ref([])
const cases = ref([])
const relationsCase = ref(null)
@@ -505,6 +676,30 @@ const caseForm = reactive({
exam_items: []
})
+const detailForm = reactive({
+ title: '',
+ case_type: 'traditional',
+ institution_id: undefined,
+ department_name: '',
+ difficulty: '',
+ chief_complaint: '',
+ description: '',
+ patient_age: undefined,
+ patient_gender: '',
+ tags: '',
+ icd_codes: '',
+ estimated_minutes: undefined,
+ osce_enabled: false,
+ traditional_standard_diagnosis: '',
+ traditional_standard_treatment: '',
+ traditional_guideline_reference: '',
+ teaching_learning_objectives: '',
+ teaching_key_points: '',
+ teaching_reference_answer: '',
+ scoring_rules: [{ dimension: '', score_weight: 1, ai_auto_score: true, scoring_standard: '' }],
+ exam_items: []
+})
+
const relationsForm = reactive<{
institutionMode: RelationInstitutionMode
institution_id?: number
@@ -610,30 +805,41 @@ function openCreateDialog() {
}
function resetCaseForm() {
- caseForm.title = ''
- caseForm.case_type = 'traditional'
- caseForm.institution_id = undefined
- caseForm.department_name = ''
- caseForm.difficulty = ''
- caseForm.chief_complaint = ''
- caseForm.description = ''
- caseForm.patient_age = undefined
- caseForm.patient_gender = ''
- caseForm.tags = ''
- caseForm.icd_codes = ''
- caseForm.estimated_minutes = undefined
- caseForm.osce_enabled = false
- caseForm.traditional_standard_diagnosis = ''
- caseForm.traditional_standard_treatment = ''
- caseForm.traditional_guideline_reference = ''
- caseForm.teaching_learning_objectives = ''
- caseForm.teaching_key_points = ''
- caseForm.teaching_reference_answer = ''
- caseForm.scoring_rules = [{ dimension: '', score_weight: 1, ai_auto_score: true, scoring_standard: '' }]
- caseForm.exam_items = []
+ resetDraftForm(caseForm)
caseFormRef.value?.clearValidate()
}
+function resetDetailForm() {
+ detailCase.value = null
+ caseDetail.value = null
+ resetDraftForm(detailForm)
+ detailFormRef.value?.clearValidate()
+}
+
+function resetDraftForm(form: CaseDraftForm) {
+ form.title = ''
+ form.case_type = 'traditional'
+ form.institution_id = undefined
+ form.department_name = ''
+ form.difficulty = ''
+ form.chief_complaint = ''
+ form.description = ''
+ form.patient_age = undefined
+ form.patient_gender = ''
+ form.tags = ''
+ form.icd_codes = ''
+ form.estimated_minutes = undefined
+ form.osce_enabled = false
+ form.traditional_standard_diagnosis = ''
+ form.traditional_standard_treatment = ''
+ form.traditional_guideline_reference = ''
+ form.teaching_learning_objectives = ''
+ form.teaching_key_points = ''
+ form.teaching_reference_answer = ''
+ form.scoring_rules = [{ dimension: '', score_weight: 1, ai_auto_score: true, scoring_standard: '' }]
+ form.exam_items = []
+}
+
function addScoringRule() {
caseForm.scoring_rules.push({ dimension: '', score_weight: 1, ai_auto_score: true, scoring_standard: '' })
}
@@ -647,13 +853,33 @@ function removeScoringRule(index: number) {
}
function addExamItem() {
- caseForm.exam_items.push({ item_code: '', item_name: '', item_type: '' })
+ caseForm.exam_items.push({ item_code: '', item_name: '', item_type: '', result_text: '' })
}
function removeExamItem(index: number) {
caseForm.exam_items.splice(index, 1)
}
+function addDetailScoringRule() {
+ detailForm.scoring_rules.push({ dimension: '', score_weight: 1, ai_auto_score: true, scoring_standard: '' })
+}
+
+function removeDetailScoringRule(index: number) {
+ if (detailForm.scoring_rules.length <= 1) {
+ return
+ }
+
+ detailForm.scoring_rules.splice(index, 1)
+}
+
+function addDetailExamItem() {
+ detailForm.exam_items.push({ item_code: '', item_name: '', item_type: '', result_text: '' })
+}
+
+function removeDetailExamItem(index: number) {
+ detailForm.exam_items.splice(index, 1)
+}
+
async function submitCaseForm() {
if (!appStore.token) {
ElMessage.warning('缺少登录信息,请重新登录')
@@ -691,60 +917,64 @@ async function submitCaseForm() {
}
function buildCreatePayload(): CreateCaseDraftPayload {
- const structure = buildCaseStructure()
- const scoringRules = normalizeScoringRules()
- const examItems = normalizeExamItems()
+ return buildDraftPayload(caseForm)
+}
+
+function buildDraftPayload(form: CaseDraftForm): CreateCaseDraftPayload {
+ const structure = buildCaseStructure(form)
+ const scoringRules = normalizeScoringRules(form)
+ const examItems = normalizeExamItems(form)
const payload: CreateCaseDraftPayload = {
- title: caseForm.title.trim(),
- case_type: caseForm.case_type,
+ title: form.title.trim(),
+ case_type: form.case_type,
scoring_rules: scoringRules
}
- payload[caseForm.case_type] = structure
- if (canManageInstitution.value && caseForm.institution_id) payload.institution_id = caseForm.institution_id
- if (caseForm.department_name.trim()) payload.department_name = caseForm.department_name.trim()
- if (caseForm.difficulty.trim()) payload.difficulty = caseForm.difficulty.trim()
- if (caseForm.chief_complaint.trim()) payload.chief_complaint = caseForm.chief_complaint.trim()
- if (caseForm.description.trim()) payload.description = caseForm.description.trim()
- if (caseForm.patient_age !== undefined) payload.patient_age = caseForm.patient_age
- if (caseForm.patient_gender) payload.patient_gender = caseForm.patient_gender
- if (caseForm.tags.trim()) {
- payload.tags = isContentAdmin.value ? caseForm.tags.trim() : parseTextList(caseForm.tags)
+ payload[form.case_type] = structure
+ if (canManageInstitution.value && form.institution_id) payload.institution_id = form.institution_id
+ if (form.department_name.trim()) payload.department_name = form.department_name.trim()
+ if (form.difficulty.trim()) payload.difficulty = form.difficulty.trim()
+ if (form.chief_complaint.trim()) payload.chief_complaint = form.chief_complaint.trim()
+ if (form.description.trim()) payload.description = form.description.trim()
+ if (form.patient_age !== undefined) payload.patient_age = form.patient_age
+ if (form.patient_gender) payload.patient_gender = form.patient_gender
+ if (form.tags.trim()) {
+ payload.tags = isContentAdmin.value ? form.tags.trim() : parseTextList(form.tags)
}
- if (parseTextList(caseForm.icd_codes).length) payload.icd_codes = parseTextList(caseForm.icd_codes)
- if (caseForm.estimated_minutes !== undefined) payload.estimated_minutes = caseForm.estimated_minutes
- if (caseForm.osce_enabled) payload.osce_enabled = caseForm.osce_enabled
+ if (parseTextList(form.icd_codes).length) payload.icd_codes = parseTextList(form.icd_codes)
+ if (form.estimated_minutes !== undefined) payload.estimated_minutes = form.estimated_minutes
+ if (form.osce_enabled) payload.osce_enabled = form.osce_enabled
if (examItems.length) payload.exam_items = examItems
return payload
}
-function buildCaseStructure(): Record {
- if (caseForm.case_type === 'teaching') {
- if (!caseForm.teaching_learning_objectives.trim()) {
+function buildCaseStructure(form: CaseDraftForm): Record {
+ if (form.case_type === 'teaching') {
+ if (!form.teaching_learning_objectives.trim()) {
throw new Error('请输入教学目标')
}
return {
- learning_objectives: caseForm.teaching_learning_objectives.trim(),
- ...(caseForm.teaching_key_points.trim() ? { key_points: caseForm.teaching_key_points.trim() } : {}),
- ...(caseForm.teaching_reference_answer.trim() ? { reference_answer: caseForm.teaching_reference_answer.trim() } : {})
+ learning_objectives: form.teaching_learning_objectives.trim(),
+ ...(form.teaching_key_points.trim() ? { key_points: form.teaching_key_points.trim() } : {}),
+ ...(form.teaching_reference_answer.trim() ? { reference_answer: form.teaching_reference_answer.trim() } : {})
}
}
- if (!caseForm.traditional_standard_diagnosis.trim()) {
+ if (!form.traditional_standard_diagnosis.trim()) {
throw new Error('请输入标准诊断')
}
return {
- standard_diagnosis: caseForm.traditional_standard_diagnosis.trim(),
- ...(caseForm.traditional_standard_treatment.trim() ? { standard_treatment: caseForm.traditional_standard_treatment.trim() } : {}),
- ...(caseForm.traditional_guideline_reference.trim() ? { guideline_reference: caseForm.traditional_guideline_reference.trim() } : {})
+ standard_diagnosis: form.traditional_standard_diagnosis.trim(),
+ ...(form.traditional_standard_treatment.trim() ? { standard_treatment: form.traditional_standard_treatment.trim() } : {}),
+ ...(form.traditional_guideline_reference.trim() ? { guideline_reference: form.traditional_guideline_reference.trim() } : {})
}
}
-function normalizeScoringRules(): CaseScoringRulePayload[] {
- const rules = caseForm.scoring_rules
+function normalizeScoringRules(form: CaseDraftForm): CaseScoringRulePayload[] {
+ const rules = form.scoring_rules
.map(rule => ({
dimension: rule.dimension.trim(),
score_weight: Number(rule.score_weight),
@@ -774,14 +1004,15 @@ function normalizeScoringRules(): CaseScoringRulePayload[] {
}))
}
-function normalizeExamItems(): CaseExamItemPayload[] {
- const items = caseForm.exam_items
+function normalizeExamItems(form: CaseDraftForm): CaseExamItemPayload[] {
+ const items = form.exam_items
.map(item => ({
item_code: item.item_code.trim(),
item_name: item.item_name.trim(),
- item_type: item.item_type.trim()
+ item_type: item.item_type.trim(),
+ result_text: item.result_text.trim()
}))
- .filter(item => item.item_code || item.item_name || item.item_type)
+ .filter(item => item.item_code || item.item_name || item.item_type || item.result_text)
const codes = new Set()
for (const item of items) {
@@ -797,7 +1028,8 @@ function normalizeExamItems(): CaseExamItemPayload[] {
return items.map(item => ({
item_code: item.item_code,
...(item.item_name ? { item_name: item.item_name } : {}),
- ...(item.item_type ? { item_type: item.item_type } : {})
+ ...(item.item_type ? { item_type: item.item_type } : {}),
+ ...(item.result_text ? { result_text: item.result_text } : {})
}))
}
@@ -807,6 +1039,7 @@ function openImportDialog() {
function resetImportFile() {
importFile.value = null
+ importCaseType.value = 'traditional'
importFileList.value = []
importUploadRef.value?.clearFiles()
}
@@ -842,14 +1075,15 @@ async function submitImportPdf() {
try {
importing.value = true
- await importCasePdf({
+ const result = await importCasePdf({
token: appStore.token,
- file: importFile.value
+ file: importFile.value,
+ case_type: importCaseType.value
})
- ElMessage.success('PDF 病例导入完成')
+ fillCaseFormFromImportedPdf(result)
importDialogVisible.value = false
- pagination.page = 1
- await loadCases()
+ caseDialogVisible.value = true
+ ElMessage.success('PDF 病例导入完成,请确认后保存草稿')
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '导入 PDF 病例失败')
} finally {
@@ -1001,6 +1235,7 @@ async function openDetailDrawer(row: CaseListItem) {
token: appStore.token,
id: row.id
})
+ fillDetailForm(row, caseDetail.value)
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '获取病例详情失败')
} finally {
@@ -1008,6 +1243,42 @@ async function openDetailDrawer(row: CaseListItem) {
}
}
+async function submitDetailForm() {
+ if (!appStore.token || !detailCase.value) {
+ ElMessage.warning('缺少登录信息,请重新登录')
+ return
+ }
+
+ const isValid = await detailFormRef.value?.validate().catch(() => false)
+ if (!isValid) {
+ return
+ }
+
+ let payload: CreateCaseDraftPayload
+ try {
+ payload = buildDraftPayload(detailForm)
+ } catch (error) {
+ ElMessage.warning(error instanceof Error ? error.message : '请检查病例表单')
+ return
+ }
+
+ try {
+ submittingDetail.value = true
+ await submitCase({
+ token: appStore.token,
+ id: detailCase.value.id,
+ payload
+ })
+ ElMessage.success('病例已提交')
+ detailDrawerVisible.value = false
+ await loadCases()
+ } catch (error) {
+ ElMessage.error(error instanceof Error ? error.message : '提交病例失败')
+ } finally {
+ submittingDetail.value = false
+ }
+}
+
function parseTextList(value: string): string[] {
return value
.split(/[,\n,;;]/)
@@ -1015,6 +1286,205 @@ function parseTextList(value: string): string[] {
.filter(Boolean)
}
+function fillCaseFormFromImportedPdf(result: ImportCasePdfResult) {
+ resetDraftForm(caseForm)
+
+ const record = getImportRecord(result.data)
+ const caseType = normalizeImportCaseType(getImportString(record, ['case_type', 'caseType']), result.caseType)
+ const traditional = getImportRecord(getImportFirst(record, ['traditional']))
+ const teaching = getImportRecord(getImportFirst(record, ['teaching']))
+ const scoringRules = getImportScoringRules(record)
+ const examItems = getImportExamItems(record)
+
+ caseForm.case_type = caseType
+ caseForm.title = getImportString(record, ['title', 'name', 'case_title', 'caseTitle'])
+ caseForm.department_name = getImportString(record, ['department_name', 'departmentName'])
+ caseForm.difficulty = getImportString(record, ['difficulty'])
+ caseForm.chief_complaint = getImportString(record, ['chief_complaint', 'chiefComplaint'])
+ caseForm.description = getImportString(record, ['description', 'summary', 'content'])
+ caseForm.patient_age = getImportNumber(record, ['patient_age', 'patientAge']) ?? undefined
+ caseForm.patient_gender = normalizeImportGender(getImportString(record, ['patient_gender', 'patientGender']))
+ caseForm.tags = getImportStringList(record, ['tags', 'tag_list', 'tagList']).join(', ')
+ caseForm.icd_codes = getImportStringList(record, ['icd_codes', 'icdCodes', 'icd_list', 'icdList']).join(', ')
+ caseForm.estimated_minutes = getImportNumber(record, ['estimated_minutes', 'estimatedMinutes']) ?? undefined
+ caseForm.osce_enabled = getImportBoolean(record, ['osce_enabled', 'osceEnabled'])
+
+ caseForm.traditional_standard_diagnosis = getImportString(traditional, ['standard_diagnosis', 'standardDiagnosis'])
+ caseForm.traditional_standard_treatment = getImportString(traditional, ['standard_treatment', 'standardTreatment'])
+ caseForm.traditional_guideline_reference = getImportString(traditional, ['guideline_reference', 'guidelineReference'])
+ caseForm.teaching_learning_objectives = getImportString(teaching, ['learning_objectives', 'learningObjectives'])
+ caseForm.teaching_key_points = getImportString(teaching, ['key_points', 'keyPoints'])
+ caseForm.teaching_reference_answer = getImportString(teaching, ['reference_answer', 'referenceAnswer'])
+ caseForm.scoring_rules = scoringRules.length
+ ? scoringRules
+ : [{ dimension: '', score_weight: 1, ai_auto_score: true, scoring_standard: '' }]
+ caseForm.exam_items = examItems
+ caseFormRef.value?.clearValidate()
+}
+
+function fillDetailForm(row: CaseListItem, fullData: unknown) {
+ resetDraftForm(detailForm)
+
+ const record = getDetailRecord(fullData)
+ const traditional = getImportRecord(getImportFirst(record, ['traditional']))
+ const teaching = getImportRecord(getImportFirst(record, ['teaching']))
+ const scoringRules = getImportScoringRules(record)
+ const examItems = getImportExamItems(record)
+
+ detailForm.title = getImportString(record, ['title', 'name', 'case_title', 'caseTitle'], row.title)
+ detailForm.case_type = normalizeImportCaseType(getImportString(record, ['case_type', 'caseType'], row.caseType), normalizeImportCaseType(row.caseType, 'traditional'))
+ detailForm.institution_id = getImportNumber(record, ['institution_id', 'institutionId']) ?? undefined
+ detailForm.department_name = getImportString(record, ['department_name', 'departmentName'], row.departmentName)
+ detailForm.difficulty = getImportString(record, ['difficulty'], row.difficulty)
+ detailForm.chief_complaint = getImportString(record, ['chief_complaint', 'chiefComplaint'], row.chiefComplaint)
+ detailForm.description = getImportString(record, ['description', 'summary', 'content'])
+ detailForm.patient_age = getImportNumber(record, ['patient_age', 'patientAge']) ?? undefined
+ detailForm.patient_gender = normalizeImportGender(getImportString(record, ['patient_gender', 'patientGender']))
+ detailForm.tags = getImportStringList(record, ['tags', 'tag_list', 'tagList']).join(', ')
+ detailForm.icd_codes = getImportStringList(record, ['icd_codes', 'icdCodes', 'icd_list', 'icdList']).join(', ')
+ detailForm.estimated_minutes = getImportNumber(record, ['estimated_minutes', 'estimatedMinutes']) ?? row.estimatedMinutes ?? undefined
+ detailForm.osce_enabled = getImportBoolean(record, ['osce_enabled', 'osceEnabled'])
+ detailForm.traditional_standard_diagnosis = getImportString(traditional, ['standard_diagnosis', 'standardDiagnosis'])
+ detailForm.traditional_standard_treatment = getImportString(traditional, ['standard_treatment', 'standardTreatment'])
+ detailForm.traditional_guideline_reference = getImportString(traditional, ['guideline_reference', 'guidelineReference'])
+ detailForm.teaching_learning_objectives = getImportString(teaching, ['learning_objectives', 'learningObjectives'])
+ detailForm.teaching_key_points = getImportString(teaching, ['key_points', 'keyPoints'])
+ detailForm.teaching_reference_answer = getImportString(teaching, ['reference_answer', 'referenceAnswer'])
+ detailForm.scoring_rules = scoringRules.length
+ ? scoringRules
+ : [{ dimension: '', score_weight: 1, ai_auto_score: true, scoring_standard: '' }]
+ detailForm.exam_items = examItems
+ detailFormRef.value?.clearValidate()
+}
+
+function getImportRecord(value: unknown): Record {
+ return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : {}
+}
+
+function getImportFirst(record: Record, keys: string[]): unknown {
+ for (const key of keys) {
+ if (record[key] !== undefined && record[key] !== null) {
+ return record[key]
+ }
+ }
+
+ return undefined
+}
+
+function getImportString(record: Record, keys: string[], fallback = ''): string {
+ for (const key of keys) {
+ const value = record[key]
+ if (typeof value === 'string' && value.trim()) {
+ return value.trim()
+ }
+ if (typeof value === 'number') {
+ return String(value)
+ }
+ }
+
+ return fallback
+}
+
+function getImportNumber(record: Record, keys: string[]): number | null {
+ const value = getImportFirst(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 getImportBoolean(record: Record, keys: string[]) {
+ const value = getImportFirst(record, keys)
+ if (typeof value === 'boolean') {
+ return value
+ }
+ if (typeof value === 'number') {
+ return value === 1
+ }
+ if (typeof value === 'string') {
+ const normalized = value.trim().toLowerCase()
+ return ['true', '1', 'yes', 'y', 'on', '开启', '启用', '是'].includes(normalized)
+ }
+
+ return false
+}
+
+function getImportStringList(record: Record, keys: string[]): string[] {
+ const value = getImportFirst(record, keys)
+ if (Array.isArray(value)) {
+ return value
+ .map(item => {
+ if (typeof item === 'string' || typeof item === 'number') {
+ return String(item).trim()
+ }
+ const itemRecord = getImportRecord(item)
+ return getImportString(itemRecord, ['name', 'title', 'code', 'value'])
+ })
+ .filter(Boolean)
+ }
+
+ if (typeof value === 'string') {
+ return parseTextList(value)
+ }
+
+ return []
+}
+
+function getImportScoringRules(record: Record): ScoringRuleForm[] {
+ const raw = getImportFirst(record, ['scoring_rules', 'scoringRules'])
+ if (!Array.isArray(raw)) {
+ return []
+ }
+
+ return raw
+ .map(item => {
+ const itemRecord = getImportRecord(item)
+ const scoreWeight = getImportNumber(itemRecord, ['score_weight', 'scoreWeight', 'weight'])
+ const rawAutoScore = getImportFirst(itemRecord, ['ai_auto_score', 'aiAutoScore'])
+ return {
+ dimension: getImportString(itemRecord, ['dimension', 'name']),
+ score_weight: scoreWeight ?? 1,
+ ai_auto_score: rawAutoScore === undefined ? true : getImportBoolean(itemRecord, ['ai_auto_score', 'aiAutoScore']),
+ scoring_standard: getImportString(itemRecord, ['scoring_standard', 'scoringStandard', 'description'])
+ }
+ })
+ .filter(item => item.dimension || item.scoring_standard)
+}
+
+function getImportExamItems(record: Record): ExamItemForm[] {
+ const raw = getImportFirst(record, ['exam_items', 'examItems'])
+ if (!Array.isArray(raw)) {
+ return []
+ }
+
+ return raw
+ .map(item => {
+ const itemRecord = getImportRecord(item)
+ return {
+ item_code: getImportString(itemRecord, ['item_code', 'itemCode', 'code']),
+ item_name: getImportString(itemRecord, ['item_name', 'itemName', 'name']),
+ item_type: getImportString(itemRecord, ['item_type', 'itemType', 'type']),
+ result_text: getImportString(itemRecord, ['result_text', 'resultText'])
+ }
+ })
+ .filter(item => item.item_code || item.item_name || item.item_type || item.result_text)
+}
+
+function normalizeImportCaseType(value: string, fallback: DraftCaseType): DraftCaseType {
+ return value === 'teaching' ? 'teaching' : fallback
+}
+
+function normalizeImportGender(value: string) {
+ if (value === '男') return 'male'
+ if (value === '女') return 'female'
+ if (value === '未知') return 'unknown'
+ return ['male', 'female', 'unknown'].includes(value) ? value : ''
+}
+
function createDetailDisplay(row: CaseListItem | null, fullData: unknown) {
const record = getDetailRecord(fullData)
const title = getDetailString(record, ['title', 'name']) || row?.title || '-'
diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue
index 655a2bf3..5f0a401e 100644
--- a/src/views/LoginView.vue
+++ b/src/views/LoginView.vue
@@ -3,8 +3,11 @@
-
MediAI
-
医疗AI平台管理系统
+
+
![新方正集团]()
+
新方正集团
+
+
新方正集团管理系统
智能化医疗教学与病例分析平台
@@ -50,6 +53,7 @@ import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { Collection, DataAnalysis, FirstAidKit, Lock, OfficeBuilding, User, UserFilled } from '@element-plus/icons-vue'
import { login, type LoginRole } from '@/api/auth'
+import logoUrl from '@/assets/images/logo.png'
import { getFirstPage, getPagePath } from '@/mock/navigation'
import { useAppStore } from '@/stores/app'
diff --git a/src/views/ModuleView.vue b/src/views/ModuleView.vue
index 8e0967a4..64d8aff9 100644
--- a/src/views/ModuleView.vue
+++ b/src/views/ModuleView.vue
@@ -98,7 +98,7 @@
基础设置
-
+