diff --git a/src/api/user/index.ts b/src/api/user/index.ts index 74fb0b9..2fcf516 100644 --- a/src/api/user/index.ts +++ b/src/api/user/index.ts @@ -17,7 +17,7 @@ export function getUserarchive(data): AxiosPromise { }); } //获取用户档案详情接口 -export function getUserarchiveDetail(data): AxiosPromise { +export function getUserarchiveDetail(data: any): AxiosPromise { return request({ url: '/admin/v1/user/detail', method: 'post', @@ -49,4 +49,12 @@ export function userDetectionOperation(data: any): AxiosPromise { method: 'post', data }); +} +//用户检测-分析列表详情接口 +export function getUserDetectionDetail(data: any): AxiosPromise { + return request({ + url: '/admin/v1/scan/detail', + method: 'post', + data + }); } \ No newline at end of file diff --git a/src/components/User/UserArchiveDetailDialog.vue b/src/components/User/UserArchiveDetailDialog.vue index 6839ec4..32d5330 100644 --- a/src/components/User/UserArchiveDetailDialog.vue +++ b/src/components/User/UserArchiveDetailDialog.vue @@ -4,6 +4,7 @@
+
@@ -20,6 +21,7 @@
+
@@ -30,14 +32,15 @@
- 分析模式 + 模式图 {{ activeEntry.label }}
@@ -70,6 +73,7 @@ import { computed, ref, watch } from 'vue'; import { getUserarchiveDetail } from '@/api/user'; import { ElMessage } from 'element-plus'; +import { buildImageEntries, type ImageEntry, type StateAngles } from '@/utils/userArchiveImages'; const CONTENT_LABELS: Record = { brightness: '亮度', @@ -79,19 +83,6 @@ const CONTENT_LABELS: Record = { yanmin: '炎敏', }; -const IMAGE_LABELS: Record = { - standard: '标准', - brown_area: '棕区', - intersection: '交叉偏振', - parallel: '平行偏振', - red_zone: '红区图', - red_zone_heat: '红区热力', - redness: '红血丝', - sunlight: '日光图', - ultraviolet: '紫外线', - uv_spot: 'UV斑', -}; - interface Props { visible: boolean; archiveId: number; @@ -100,15 +91,6 @@ interface Props { const props = defineProps(); defineEmits<{ 'update:visible': [val: boolean] }>(); -type StateAngles = { left: string; front: string; right: string }; - -interface ImageEntry { - key: string; - label: string; - angles: StateAngles; - thumbUrl: string; -} - const loading = ref(false); const detail = ref(null); const imageEntries = ref([]); @@ -120,10 +102,6 @@ const progressColors = [ { color: '#67c23a', percentage: 100 }, ]; -function normUrl(u: unknown): string { - return u && String(u).trim() ? String(u).trim() : ''; -} - const activeEntry = computed(() => imageEntries.value.find((e) => e.key === activeImageKey.value) ); @@ -162,178 +140,6 @@ const metricRows = computed(() => { }); }); -function isFlatStateUrlMap(obj: unknown): obj is Record { - if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false; - const vals = Object.values(obj); - if (!vals.length) return false; - return vals.every((v) => typeof v === 'string' || v == null || v === ''); -} - -const THUMB_ORDER = [ - 'standard', 'brown_area', 'intersection', 'parallel', - 'red_zone', 'red_zone_heat', 'redness', 'sunlight', 'ultraviolet', 'uv_spot', -]; - -function mergeAngles(a: StateAngles, b: StateAngles): StateAngles { - return { - left: a.left || b.left, - front: a.front || b.front, - right: a.right || b.right, - }; -} - -function mergeAngleMaps(a: Map, b: Map): Map { - const keys = new Set([...a.keys(), ...b.keys()]); - const out = new Map(); - for (const k of keys) { - const merged = mergeAngles( - a.get(k) ?? { left: '', front: '', right: '' }, - b.get(k) ?? { left: '', front: '', right: '' } - ); - if (merged.left || merged.front || merged.right) out.set(k, merged); - } - return out; -} - -function buildFromImagesFrontAndImagesNested(d: any): ImageEntry[] { - const imgs = d?.images; - const imx = d?.imagex; - const thumbA = imgs?.front; - const thumbB = imx?.front; - const tripleA = imgs?.images; - const tripleB = imx?.images; - - const thumbFlatA = thumbA && typeof thumbA === 'object' && !Array.isArray(thumbA) && isFlatStateUrlMap(thumbA); - const thumbFlatB = thumbB && typeof thumbB === 'object' && !Array.isArray(thumbB) && isFlatStateUrlMap(thumbB); - const hasTripleA = tripleA && typeof tripleA === 'object' && !Array.isArray(tripleA); - const hasTripleB = tripleB && typeof tripleB === 'object' && !Array.isArray(tripleB); - - if (!thumbFlatA && !thumbFlatB && !hasTripleA && !hasTripleB) return []; - - const keys = new Set(); - if (thumbFlatA) Object.keys(thumbA as object).forEach((k) => keys.add(k)); - if (thumbFlatB) Object.keys(thumbB as object).forEach((k) => keys.add(k)); - if (hasTripleA) Object.keys(tripleA as object).forEach((k) => keys.add(k)); - if (hasTripleB) Object.keys(tripleB as object).forEach((k) => keys.add(k)); - - const thumbFor = (key: string) => - normUrl(thumbFlatA ? (thumbA as Record)[key] : '') || - normUrl(thumbFlatB ? (thumbB as Record)[key] : ''); - - const tripleFor = (key: string): StateAngles => { - const pick = (root: unknown) => { - if (!root || typeof root !== 'object' || Array.isArray(root)) return { left: '', front: '', right: '' }; - const tri = (root as Record)[key]; - if (!tri || typeof tri !== 'object' || Array.isArray(tri)) return { left: '', front: '', right: '' }; - const o = tri as Record; - return { left: normUrl(o.left), front: normUrl(o.front), right: normUrl(o.right) }; - }; - return mergeAngles(pick(tripleA), pick(tripleB)); - }; - - const entries: ImageEntry[] = []; - for (const key of keys) { - const thumbUrl = thumbFor(key); - const tri = tripleFor(key); - const angles: StateAngles = { left: tri.left, front: tri.front || thumbUrl, right: tri.right }; - if (!thumbUrl && !angles.left && !angles.front && !angles.right) continue; - entries.push({ key, label: IMAGE_LABELS[key] || key, angles, thumbUrl: thumbUrl || tri.front || tri.left || tri.right }); - } - return entries.length ? sortImageEntries(entries) : []; -} - -function sortImageEntries(entries: ImageEntry[]): ImageEntry[] { - const byKey = new Map(entries.map((e) => [e.key, e])); - const known: ImageEntry[] = []; - const used = new Set(); - for (const key of THUMB_ORDER) { - const e = byKey.get(key); - if (e) { used.add(key); known.push(e); } - } - const rest = entries.filter((e) => !used.has(e.key)).sort((a, b) => a.key.localeCompare(b.key)); - return [...known, ...rest]; -} - -function buildFromAngleRootMapsMap(d: any): Map { - const pick = (angle: 'left' | 'front' | 'right', stateKey: string) => - normUrl(d?.images?.[angle]?.[stateKey]) || normUrl(d?.imagex?.[angle]?.[stateKey]); - const frontObj = d?.images?.front ?? d?.imagex?.front; - const leftObj = d?.images?.left ?? d?.imagex?.left; - const rightObj = d?.images?.right ?? d?.imagex?.right; - - const keys = new Set(); - [leftObj, rightObj].forEach((m) => { - if (m && typeof m === 'object' && !Array.isArray(m)) Object.keys(m as object).forEach((k) => keys.add(k)); - }); - if (frontObj && typeof frontObj === 'object' && !Array.isArray(frontObj) && !isFlatStateUrlMap(frontObj)) { - Object.keys(frontObj as object).forEach((k) => keys.add(k)); - } - - const map = new Map(); - for (const key of keys) { - const angles: StateAngles = { left: pick('left', key), front: pick('front', key), right: pick('right', key) }; - if (angles.left || angles.front || angles.right) map.set(key, angles); - } - return map; -} - -function buildFromStateNestedMapsMap(d: any): Map { - const roots = [d?.images, d?.imagex].filter((x) => x && typeof x === 'object' && !Array.isArray(x)) as Record[]; - const byKey = new Map(); - - function mergeTriplet(stateKey: string, left: string, front: string, right: string) { - if (!left && !front && !right) return; - const prev = byKey.get(stateKey) ?? { left: '', front: '', right: '' }; - byKey.set(stateKey, { left: prev.left || left, front: prev.front || front, right: prev.right || right }); - } - - for (const imgRoot of roots) { - for (const [key, val] of Object.entries(imgRoot)) { - if (['left', 'front', 'right'].includes(key)) continue; - if (key === 'images' && val && typeof val === 'object' && !Array.isArray(val)) { - for (const [stateKey, tri] of Object.entries(val as Record)) { - if (!tri || typeof tri !== 'object' || Array.isArray(tri)) continue; - const o = tri as Record; - mergeTriplet(stateKey, normUrl(o.left), normUrl(o.front), normUrl(o.right)); - } - continue; - } - if (!val || typeof val !== 'object' || Array.isArray(val)) continue; - const o = val as Record; - mergeTriplet(key, normUrl(o.left), normUrl(o.front), normUrl(o.right)); - } - } - return byKey; -} - -function mapToImageEntries(map: Map): ImageEntry[] { - const entries: ImageEntry[] = []; - for (const [key, angles] of map) { - if (!angles.left && !angles.front && !angles.right) continue; - entries.push({ key, label: IMAGE_LABELS[key] || key, angles, thumbUrl: angles.front || angles.left || angles.right }); - } - return sortImageEntries(entries); -} - -function buildImageEntries(d: any): ImageEntry[] { - if (!d || typeof d !== 'object') return []; - const primary = buildFromImagesFrontAndImagesNested(d); - if (primary.length) return primary; - const fromAngles = buildFromAngleRootMapsMap(d); - const fromNested = buildFromStateNestedMapsMap(d); - const merged = mergeAngleMaps(fromAngles, fromNested); - if (merged.size) return mapToImageEntries(merged); - const flat = (d?.images?.front ?? d?.imagex?.front) as Record | undefined; - if (!flat || typeof flat !== 'object' || !isFlatStateUrlMap(flat)) return []; - return Object.entries(flat) - .map(([key, url]) => { - const u = normUrl(url); - if (!u) return null; - return { key, label: IMAGE_LABELS[key] || key, angles: { left: '', front: u, right: '' } as StateAngles, thumbUrl: u } as ImageEntry; - }) - .filter((x): x is ImageEntry => x != null); -} - function syncDisplayAngles() { const a = activeAngles.value; centerLeft.value = a.left; @@ -401,17 +207,11 @@ watch( diff --git a/src/utils/userArchiveImages.ts b/src/utils/userArchiveImages.ts new file mode 100644 index 0000000..9bc1e38 --- /dev/null +++ b/src/utils/userArchiveImages.ts @@ -0,0 +1,253 @@ +/** + * 用户档案 / 检测详情里的 images 结构解析(与弹窗详情共用) + */ + +export type StateAngles = { left: string; front: string; right: string }; + +export interface ImageEntry { + key: string; + label: string; + angles: StateAngles; + thumbUrl: string; +} + +const IMAGE_LABELS: Record = { + standard: '标准', + brown_area: '棕区', + intersection: '交叉偏振', + parallel: '平行偏振', + red_zone: '红区图', + red_zone_heat: '红区热力', + redness: '红血丝', + sunlight: '日光图', + ultraviolet: '紫外线', + uv_spot: 'UV斑', +}; + +const THUMB_ORDER = [ + 'standard', + 'brown_area', + 'intersection', + 'parallel', + 'red_zone', + 'red_zone_heat', + 'redness', + 'sunlight', + 'ultraviolet', + 'uv_spot', +]; + +function normUrl(u: unknown): string { + return u && String(u).trim() ? String(u).trim() : ''; +} + +function isFlatStateUrlMap(obj: unknown): obj is Record { + if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false; + const vals = Object.values(obj); + if (!vals.length) return false; + return vals.every((v) => typeof v === 'string' || v == null || v === ''); +} + +function mergeAngles(a: StateAngles, b: StateAngles): StateAngles { + return { + left: a.left || b.left, + front: a.front || b.front, + right: a.right || b.right, + }; +} + +function mergeAngleMaps(a: Map, b: Map): Map { + const keys = new Set([...a.keys(), ...b.keys()]); + const out = new Map(); + for (const k of keys) { + const merged = mergeAngles( + a.get(k) ?? { left: '', front: '', right: '' }, + b.get(k) ?? { left: '', front: '', right: '' } + ); + if (merged.left || merged.front || merged.right) out.set(k, merged); + } + return out; +} + +function sortImageEntries(entries: ImageEntry[]): ImageEntry[] { + const byKey = new Map(entries.map((e) => [e.key, e])); + const known: ImageEntry[] = []; + const used = new Set(); + for (const key of THUMB_ORDER) { + const e = byKey.get(key); + if (e) { + used.add(key); + known.push(e); + } + } + const rest = entries.filter((e) => !used.has(e.key)).sort((a, b) => a.key.localeCompare(b.key)); + return [...known, ...rest]; +} + +function buildFromImagesFrontAndImagesNested(d: any): ImageEntry[] { + const imgs = d?.images; + const imx = d?.imagex; + const thumbA = imgs?.front; + const thumbB = imx?.front; + const tripleA = imgs?.images; + const tripleB = imx?.images; + + const thumbFlatA = thumbA && typeof thumbA === 'object' && !Array.isArray(thumbA) && isFlatStateUrlMap(thumbA); + const thumbFlatB = thumbB && typeof thumbB === 'object' && !Array.isArray(thumbB) && isFlatStateUrlMap(thumbB); + const hasTripleA = tripleA && typeof tripleA === 'object' && !Array.isArray(tripleA); + const hasTripleB = tripleB && typeof tripleB === 'object' && !Array.isArray(tripleB); + + if (!thumbFlatA && !thumbFlatB && !hasTripleA && !hasTripleB) return []; + + const keys = new Set(); + if (thumbFlatA) Object.keys(thumbA as object).forEach((k) => keys.add(k)); + if (thumbFlatB) Object.keys(thumbB as object).forEach((k) => keys.add(k)); + if (hasTripleA) Object.keys(tripleA as object).forEach((k) => keys.add(k)); + if (hasTripleB) Object.keys(tripleB as object).forEach((k) => keys.add(k)); + + const thumbFor = (key: string) => + normUrl(thumbFlatA ? (thumbA as Record)[key] : '') || + normUrl(thumbFlatB ? (thumbB as Record)[key] : ''); + + const tripleFor = (key: string): StateAngles => { + const pick = (root: unknown) => { + if (!root || typeof root !== 'object' || Array.isArray(root)) return { left: '', front: '', right: '' }; + const tri = (root as Record)[key]; + if (!tri || typeof tri !== 'object' || Array.isArray(tri)) return { left: '', front: '', right: '' }; + const o = tri as Record; + return { left: normUrl(o.left), front: normUrl(o.front), right: normUrl(o.right) }; + }; + return mergeAngles(pick(tripleA), pick(tripleB)); + }; + + const entries: ImageEntry[] = []; + for (const key of keys) { + const thumbUrl = thumbFor(key); + const tri = tripleFor(key); + const angles: StateAngles = { left: tri.left, front: tri.front || thumbUrl, right: tri.right }; + if (!thumbUrl && !angles.left && !angles.front && !angles.right) continue; + entries.push({ + key, + label: IMAGE_LABELS[key] || key, + angles, + thumbUrl: thumbUrl || tri.front || tri.left || tri.right, + }); + } + return entries.length ? sortImageEntries(entries) : []; +} + +function buildFromAngleRootMapsMap(d: any): Map { + const pick = (angle: 'left' | 'front' | 'right', stateKey: string) => + normUrl(d?.images?.[angle]?.[stateKey]) || normUrl(d?.imagex?.[angle]?.[stateKey]); + const frontObj = d?.images?.front ?? d?.imagex?.front; + const leftObj = d?.images?.left ?? d?.imagex?.left; + const rightObj = d?.images?.right ?? d?.imagex?.right; + + const keys = new Set(); + [leftObj, rightObj].forEach((m) => { + if (m && typeof m === 'object' && !Array.isArray(m)) Object.keys(m as object).forEach((k) => keys.add(k)); + }); + if (frontObj && typeof frontObj === 'object' && !Array.isArray(frontObj) && !isFlatStateUrlMap(frontObj)) { + Object.keys(frontObj as object).forEach((k) => keys.add(k)); + } + + const map = new Map(); + for (const key of keys) { + const angles: StateAngles = { left: pick('left', key), front: pick('front', key), right: pick('right', key) }; + if (angles.left || angles.front || angles.right) map.set(key, angles); + } + return map; +} + +function buildFromStateNestedMapsMap(d: any): Map { + const roots = [d?.images, d?.imagex].filter((x) => x && typeof x === 'object' && !Array.isArray(x)) as Record< + string, + unknown + >[]; + const byKey = new Map(); + + function mergeTriplet(stateKey: string, left: string, front: string, right: string) { + if (!left && !front && !right) return; + const prev = byKey.get(stateKey) ?? { left: '', front: '', right: '' }; + byKey.set(stateKey, { + left: prev.left || left, + front: prev.front || front, + right: prev.right || right, + }); + } + + for (const imgRoot of roots) { + for (const [key, val] of Object.entries(imgRoot)) { + if (['left', 'front', 'right'].includes(key)) continue; + if (key === 'images' && val && typeof val === 'object' && !Array.isArray(val)) { + for (const [stateKey, tri] of Object.entries(val as Record)) { + if (!tri || typeof tri !== 'object' || Array.isArray(tri)) continue; + const o = tri as Record; + mergeTriplet(stateKey, normUrl(o.left), normUrl(o.front), normUrl(o.right)); + } + continue; + } + if (!val || typeof val !== 'object' || Array.isArray(val)) continue; + const o = val as Record; + mergeTriplet(key, normUrl(o.left), normUrl(o.front), normUrl(o.right)); + } + } + return byKey; +} + +function mapToImageEntries(map: Map): ImageEntry[] { + const entries: ImageEntry[] = []; + for (const [key, angles] of map) { + if (!angles.left && !angles.front && !angles.right) continue; + entries.push({ + key, + label: IMAGE_LABELS[key] || key, + angles, + thumbUrl: angles.front || angles.left || angles.right, + }); + } + return sortImageEntries(entries); +} + +/** 从单条档案/检测记录解析可切换的分析模式列表 */ +export function buildImageEntries(d: any): ImageEntry[] { + if (!d || typeof d !== 'object') return []; + const primary = buildFromImagesFrontAndImagesNested(d); + if (primary.length) return primary; + const fromAngles = buildFromAngleRootMapsMap(d); + const fromNested = buildFromStateNestedMapsMap(d); + const merged = mergeAngleMaps(fromAngles, fromNested); + if (merged.size) return mapToImageEntries(merged); + const flat = (d?.images?.front ?? d?.imagex?.front) as Record | undefined; + if (!flat || typeof flat !== 'object' || !isFlatStateUrlMap(flat)) return []; + return Object.entries(flat) + .map(([key, url]) => { + const u = normUrl(url); + if (!u) return null; + return { + key, + label: IMAGE_LABELS[key] || key, + angles: { left: '', front: u, right: '' } as StateAngles, + thumbUrl: u, + } as ImageEntry; + }) + .filter((x): x is ImageEntry => x != null); +} + +/** 合并多条解析结果里的模式 key,顺序与缩略条一致 */ +export function orderedImageKeys(...entryLists: ImageEntry[][]): string[] { + const set = new Set(); + for (const list of entryLists) { + for (const e of list) set.add(e.key); + } + const known: string[] = []; + const used = new Set(); + for (const k of THUMB_ORDER) { + if (set.has(k)) { + known.push(k); + used.add(k); + } + } + const rest = [...set].filter((k) => !used.has(k)).sort((a, b) => a.localeCompare(b)); + return [...known, ...rest]; +} diff --git a/src/views/user/archiveDetail.vue b/src/views/user/archiveDetail.vue new file mode 100644 index 0000000..6124ac8 --- /dev/null +++ b/src/views/user/archiveDetail.vue @@ -0,0 +1,849 @@ + + + + + diff --git a/src/views/user/userAnalysis.vue b/src/views/user/userAnalysis.vue index 662bf92..d080941 100644 --- a/src/views/user/userAnalysis.vue +++ b/src/views/user/userAnalysis.vue @@ -45,7 +45,8 @@