From 3fdf9b6ed2662a4662b54a16c915d045e8d91b25 Mon Sep 17 00:00:00 2001 From: Ghost <106998207@qq.com> Date: Tue, 31 Mar 2026 10:56:28 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E7=AE=A1=E7=90=86=E7=AB=AF=E4=B8=8E?= =?UTF-8?q?=20API=20=E6=89=B9=E9=87=8F=E6=9B=B4=E6=96=B0=EF=BC=88=E6=B5=8B?= =?UTF-8?q?=E8=AF=84=E6=81=A2=E5=A4=8D=E3=80=81=E7=9C=8B=E6=9D=BF=E3=80=81?= =?UTF-8?q?=E5=88=86=E9=94=80=E4=B8=8E=E7=94=A8=E6=88=B7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、修复了用户详情/测评结果与人脸结果解析展示问题;管理端订单、分销及超管用户相关接口与展示异常。 2、新增了 testResultParse 统一解析工具、mbti_test_results 恢复 SQL 及生成脚本;补充 API 路由与 Dashboard/用户侧能力。 3、优化了企业/超管数据看板与分销结算逻辑;精简超管 Settings 与布局代码;完善 faceResultDetail;小程序 app.js 小幅同步。 Made-with: Cursor --- admin/src/components/UserDetailDialog.vue | 2 +- admin/src/layouts/SuperAdminLayout.vue | 38 -- admin/src/utils/faceResultDetail.ts | 75 ++- admin/src/utils/testResultParse.ts | 17 + admin/src/views/admin/Dashboard.vue | 100 +++- admin/src/views/admin/Orders.vue | 22 +- admin/src/views/admin/Users.vue | 72 ++- admin/src/views/superadmin/Settings.vue | 115 +---- admin/src/views/superadmin/Users.vue | 72 ++- api/app/controller/admin/AppUser.php | 83 ++- api/app/controller/admin/Dashboard.php | 335 +++++++++--- api/app/controller/admin/Distribution.php | 225 ++++++-- api/app/controller/admin/Order.php | 15 +- api/app/controller/superadmin/AppUser.php | 482 +++++++++++------- .../_gen_restore_mbti_test_results.py | 38 ++ .../restore_mbti_test_results_1_145.sql | 155 ++++++ api/route/api.php | 2 + miniprogram/app.js | 4 +- 18 files changed, 1306 insertions(+), 546 deletions(-) create mode 100644 admin/src/utils/testResultParse.ts create mode 100644 api/database/migrations/_gen_restore_mbti_test_results.py create mode 100644 api/database/migrations/restore_mbti_test_results_1_145.sql diff --git a/admin/src/components/UserDetailDialog.vue b/admin/src/components/UserDetailDialog.vue index 78bea9d..f62979d 100644 --- a/admin/src/components/UserDetailDialog.vue +++ b/admin/src/components/UserDetailDialog.vue @@ -153,7 +153,7 @@ diff --git a/admin/src/layouts/SuperAdminLayout.vue b/admin/src/layouts/SuperAdminLayout.vue index 2c9bcbb..8062f6c 100644 --- a/admin/src/layouts/SuperAdminLayout.vue +++ b/admin/src/layouts/SuperAdminLayout.vue @@ -71,18 +71,6 @@
-
-
- - 核心职责:配置与监督各企业使用的 - 普通管理后台 - (用户运营、订单、分销等由企业管理员在日常后台完成)。 - - - 进入管理后台 - -
-
@@ -358,30 +346,4 @@ const handleLogout = async () => { padding-left: 240px; } } - -.mission-strip { - background: linear-gradient(90deg, #fef2f2 0%, #fff7ed 100%); - border-bottom: 1px solid #fecaca; -} - -.mission-inner { - max-width: 1400px; - margin: 0 auto; - padding: 10px 24px; - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.mission-text { - font-size: 13px; - color: #44403c; - line-height: 1.5; - - strong { - color: #991b1b; - } -} diff --git a/admin/src/utils/faceResultDetail.ts b/admin/src/utils/faceResultDetail.ts index 837537a..190ae9a 100644 --- a/admin/src/utils/faceResultDetail.ts +++ b/admin/src/utils/faceResultDetail.ts @@ -40,6 +40,73 @@ export interface FaceDetailView { careers: string[] } +function collectPhotoUrls(parsed: Record): string[] { + const out: string[] = [] + const pushUrl = (s: string) => { + const t = s.trim() + if (t && (t.startsWith('http://') || t.startsWith('https://'))) out.push(t) + } + const pushArr = (v: unknown) => { + if (!Array.isArray(v)) return + for (const x of v) { + if (typeof x === 'string') pushUrl(x) + } + } + pushArr(parsed.photoUrls) + pushArr(parsed.photos) + pushArr(parsed.imageUrls) + for (const k of ['photoUrl', 'imageUrl', 'faceImageUrl'] as const) { + const u = parsed[k] + if (typeof u === 'string') pushUrl(u) + } + return [...new Set(out)] +} + +function isPlaceholderTitle(s: string): boolean { + return !s || s === '—' || s === '-' || s === '–' || s === '--' +} + +function normalizeMbtiBlock(parsed: Record): { type?: string; title?: string } | null { + const m = parsed.mbti + if (m && typeof m === 'object' && !Array.isArray(m)) { + const o = m as { type?: string; title?: string } + const type = o.type != null ? String(o.type).trim() : '' + let title = o.title != null ? String(o.title).trim() : '' + if (isPlaceholderTitle(title)) title = '' + if (!type) return null + return title ? { type, title } : { type } + } + const typeOnly = + (typeof m === 'string' && m.trim() ? m.trim() : '') || + String(parsed.mbtiType ?? parsed.mbti_type ?? '').trim() + if (!typeOnly) return null + let titleRaw = String(parsed.mbtiTitle ?? parsed.title ?? '').trim() + if (isPlaceholderTitle(titleRaw)) titleRaw = '' + return titleRaw ? { type: typeOnly, title: titleRaw } : { type: typeOnly } +} + +function normalizeDiscBlock(parsed: Record): { primary?: string; secondary?: string } | null { + const d = parsed.disc + if (d && typeof d === 'object' && !Array.isArray(d)) { + return d as { primary?: string; secondary?: string } + } + if (typeof d === 'string' && d.trim()) { + return { primary: d.trim() } + } + return null +} + +function normalizePdpBlock(parsed: Record): { primary?: string; secondary?: string } | null { + const p = parsed.pdp + if (p && typeof p === 'object' && !Array.isArray(p)) { + return p as { primary?: string; secondary?: string } + } + if (typeof p === 'string' && p.trim()) { + return { primary: p.trim() } + } + return null +} + function parseMaybeJsonObject(raw: unknown): Record | null { if (raw && typeof raw === 'object' && !Array.isArray(raw)) { return raw as Record @@ -113,10 +180,10 @@ export function buildFaceDetailFromParsed(parsed: Record | null | u Array.isArray(v) ? (v as unknown[]).map((x) => String(x)).filter(Boolean) : [] return { - photos: Array.isArray(parsed.photoUrls) ? (parsed.photoUrls as string[]) : [], - mbti: parsed.mbti && typeof parsed.mbti === 'object' ? parsed.mbti : null, - disc: parsed.disc && typeof parsed.disc === 'object' ? parsed.disc : null, - pdp: parsed.pdp && typeof parsed.pdp === 'object' ? parsed.pdp : null, + photos: collectPhotoUrls(parsed), + mbti: normalizeMbtiBlock(parsed), + disc: normalizeDiscBlock(parsed), + pdp: normalizePdpBlock(parsed), overview: String(parsed.overview ?? ''), personalitySummary: String(parsed.personalitySummary ?? ''), faceAnalysisText, diff --git a/admin/src/utils/testResultParse.ts b/admin/src/utils/testResultParse.ts new file mode 100644 index 0000000..a7d3f52 --- /dev/null +++ b/admin/src/utils/testResultParse.ts @@ -0,0 +1,17 @@ +/** + * 统一解析 test_results 单条的 result / resultData(字符串 JSON 或已解析对象) + */ +export function parseTestResultPayload(raw: unknown): Record | null { + if (raw == null || raw === '') return null + if (typeof raw === 'object' && !Array.isArray(raw)) { + return raw as Record + } + if (typeof raw !== 'string') return null + try { + const data = JSON.parse(raw) as unknown + if (!data || typeof data !== 'object' || Array.isArray(data)) return null + return data as Record + } catch { + return null + } +} diff --git a/admin/src/views/admin/Dashboard.vue b/admin/src/views/admin/Dashboard.vue index 34c6b7e..a36094b 100644 --- a/admin/src/views/admin/Dashboard.vue +++ b/admin/src/views/admin/Dashboard.vue @@ -15,6 +15,18 @@ +
+
+
企业余额
+ + + 企业充值 + +
+
+ +
+
@@ -75,19 +87,19 @@
-

测试 Top 10

+

测试 Top 20

按完成次数 · 本企业口径

全部用户
-
+
@@ -134,7 +146,7 @@