From 7108f28280fdce2db96e5da215e236fca15aec85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A1=E8=8B=A5?= Date: Fri, 17 Apr 2026 19:46:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=8F=E7=A8=8B=E5=BA=8F=E4=B8=8E?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=AB=AF=E8=BF=AD=E4=BB=A3=EF=BC=88=E7=A5=9E?= =?UTF-8?q?=E4=BB=99AI=E3=80=81=E4=BA=86=E8=A7=A3=E8=87=AA=E5=B7=B1CRM?= =?UTF-8?q?=E3=80=81AI=E6=B5=8B=E8=AF=95=E5=85=A5=E5=8F=A3=E3=80=81?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E4=B8=8E=E5=88=86=E6=B6=A6=E7=AD=89=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- .gitignore | 3 + README.md | 44 +- admin/package.json | 71 +- admin/src/assets/admin-theme.css | 1207 +++++++++++++++++ admin/src/components/UserDetailDialog.vue | 107 ++ .../components/superadmin/SaPageHeader.vue | 24 + admin/src/components/superadmin/SaTabs.vue | 58 + admin/src/layouts/AdminLayout.vue | 4 +- admin/src/layouts/SuperAdminLayout.vue | 154 ++- admin/src/main.ts | 61 +- admin/src/router/index.ts | 28 +- admin/src/utils/request.ts | 10 +- admin/src/views/admin/Dashboard.vue | 239 +++- admin/src/views/admin/Distribution.vue | 161 ++- admin/src/views/admin/OrderTimelineDrawer.vue | 329 +++++ admin/src/views/admin/Orders.vue | 167 ++- admin/src/views/admin/Users.vue | 327 +++-- admin/src/views/superadmin/CommerceHub.vue | 118 +- admin/src/views/superadmin/Distribution.vue | 143 +- .../superadmin/DistributionStandalone.vue | 118 +- admin/src/views/superadmin/EnterpriseHub.vue | 137 +- admin/src/views/superadmin/MpAnalytics.vue | 274 +++- admin/src/views/superadmin/MpTabBar.vue | 351 +++++ admin/src/views/superadmin/OpsHub.vue | 43 +- admin/src/views/superadmin/Overview.vue | 24 +- admin/src/views/superadmin/ProfitRules.vue | 199 +++ admin/src/views/superadmin/SoulArticles.vue | 740 ++++++++++ admin/vite.config.ts | 7 +- api/.env.local.merge.example | 10 + api/app/common/AnalyticsEventLabels.php | 132 ++ .../common/service/AiBalanceAlertService.php | 128 ++ api/app/common/service/AiCallService.php | 623 +++++++++ .../service/AiChatArticleDisplayService.php | 116 ++ api/app/common/service/AiReportService.php | 206 +++ .../service/FeishuLeadWebhookService.php | 101 +- .../service/OutboundPushHookService.php | 73 +- .../common/service/ProfitSharingService.php | 259 ++++ api/app/common/service/SoulArticleService.php | 441 ++++++ api/app/common/service/UserJourneyService.php | 152 +++ api/app/controller/admin/AppUser.php | 87 ++ api/app/controller/admin/Finance.php | 32 +- api/app/controller/admin/Order.php | 65 + .../admin/concern/ExtractsTestResults.php | 103 ++ api/app/controller/api/AiChat.php | 503 +++++++ api/app/controller/api/AiReport.php | 84 ++ api/app/controller/api/CrmReport.php | 93 +- api/app/controller/api/MpConfig.php | 58 + api/app/controller/api/Payment.php | 54 + api/app/controller/superadmin/AiMonitor.php | 29 + api/app/controller/superadmin/Analytics.php | 303 ++++- api/app/controller/superadmin/MpTabBar.php | 127 ++ api/app/controller/superadmin/ProfitRule.php | 79 ++ api/app/controller/superadmin/SoulArticle.php | 232 ++++ api/app/model/AiConversation.php | 29 + api/app/model/AiMessage.php | 23 + api/app/model/AiProvider.php | 219 +-- api/app/model/AiReport.php | 12 + api/app/model/Enterprise.php | 2 + api/app/model/ProfitSharingRule.php | 16 + api/app/model/SoulArticle.php | 30 + .../add_ai_chat_and_soul_articles.sql | 80 ++ api/database/migrations/add_cold_face.sql | 12 + .../add_mp_analytics_events_user_index.sql | 4 + .../add_tabbar_and_ai_profit_sharing.sql | 95 ++ .../add_tabbar_iconurl_and_soul_autosync.sql | 24 + api/route/api.php | 49 + douyin-miniprogram/images/default-avatar.png | Bin 70 -> 101819 bytes douyin-miniprogram/pages/index/result.js | 13 + douyin-miniprogram/pages/index/result.ttml | 29 +- douyin-miniprogram/pages/index/result.ttss | 98 +- douyin-miniprogram/pages/profile/index.js | 1 + douyin-miniprogram/pages/profile/index.ttml | 24 +- douyin-miniprogram/pages/profile/index.ttss | 15 + douyin-miniprogram/pages/purchase/index.js | 54 +- douyin-miniprogram/pages/purchase/index.ttml | 5 - douyin-miniprogram/utils/questionBank.js | 24 +- miniprogram/.env.example | 4 + miniprogram/app.js | 101 +- miniprogram/app.json | 14 +- miniprogram/app.wxss | 369 ++--- .../components/result-section-nav/index.js | 23 + .../components/result-section-nav/index.json | 4 + .../components/result-section-nav/index.wxml | 17 + .../components/result-section-nav/index.wxss | 67 + miniprogram/custom-tab-bar/index.js | 112 +- miniprogram/custom-tab-bar/index.wxml | 64 +- miniprogram/custom-tab-bar/index.wxss | 94 +- miniprogram/images/default-avatar.png | Bin 70 -> 101819 bytes miniprogram/images/shenxian-ai-logo.png | Bin 0 -> 181915 bytes miniprogram/images/shenxian-oldman-circle.png | Bin 0 -> 101819 bytes miniprogram/pages/ai-chat/history.js | 59 + miniprogram/pages/ai-chat/history.json | 7 + miniprogram/pages/ai-chat/history.wxml | 14 + miniprogram/pages/ai-chat/history.wxss | 39 + miniprogram/pages/ai-chat/index.js | 396 ++++++ miniprogram/pages/ai-chat/index.json | 7 + miniprogram/pages/ai-chat/index.wxml | 113 ++ miniprogram/pages/ai-chat/index.wxss | 507 +++++++ miniprogram/pages/ai-chat/report.js | 194 +++ miniprogram/pages/ai-chat/report.json | 7 + miniprogram/pages/ai-chat/report.wxml | 58 + miniprogram/pages/ai-chat/report.wxss | 112 ++ miniprogram/pages/ai-test/index.js | 62 +- miniprogram/pages/ai-test/index.json | 5 +- miniprogram/pages/ai-test/index.wxml | 30 +- miniprogram/pages/ai-test/index.wxss | 102 +- miniprogram/pages/index/camera.wxml | 2 +- miniprogram/pages/index/index.js | 15 +- miniprogram/pages/index/index.wxml | 2 +- miniprogram/pages/index/index.wxss | 4 +- miniprogram/pages/index/result.js | 24 + miniprogram/pages/index/result.json | 14 +- miniprogram/pages/index/result.wxml | 56 +- miniprogram/pages/index/result.wxss | 264 +++- miniprogram/pages/match-job/index.js | 264 ++++ miniprogram/pages/match-job/index.json | 7 + miniprogram/pages/match-job/index.wxml | 150 ++ miniprogram/pages/match-job/index.wxss | 356 +++++ miniprogram/pages/profile/index.js | 96 +- miniprogram/pages/profile/index.wxml | 229 ++-- miniprogram/pages/profile/index.wxss | 370 +++++ miniprogram/pages/promo/index.js | 568 ++++---- miniprogram/pages/promo/index.wxss | 84 ++ miniprogram/pages/purchase/index.js | 106 +- miniprogram/pages/purchase/index.wxml | 27 +- miniprogram/pages/purchase/index.wxss | 79 ++ miniprogram/pages/result/disc.js | 170 ++- miniprogram/pages/result/disc.json | 16 +- miniprogram/pages/result/disc.wxml | 265 +++- miniprogram/pages/result/disc.wxss | 42 +- miniprogram/pages/result/mbti.js | 212 ++- miniprogram/pages/result/mbti.json | 16 +- miniprogram/pages/result/mbti.wxml | 320 ++++- miniprogram/pages/result/mbti.wxss | 34 +- miniprogram/pages/result/pdp.js | 170 ++- miniprogram/pages/result/pdp.json | 16 +- miniprogram/pages/result/pdp.wxml | 284 +++- miniprogram/pages/result/pdp.wxss | 42 +- miniprogram/pages/result/sbti.js | 171 ++- miniprogram/pages/result/sbti.json | 6 +- miniprogram/pages/result/sbti.wxml | 196 ++- miniprogram/pages/result/sbti.wxss | 213 ++- miniprogram/pages/test-select/index.js | 33 +- miniprogram/pages/test-select/index.wxml | 26 +- miniprogram/pages/test-select/index.wxss | 8 + miniprogram/pages/test/mbti.js | 51 +- miniprogram/pages/test/mbti.wxml | 11 + miniprogram/pages/test/mbti.wxss | 68 + miniprogram/pages/webview/index.js | 31 + miniprogram/pages/webview/index.json | 6 + miniprogram/pages/webview/index.wxml | 4 + miniprogram/pages/webview/index.wxss | 6 + miniprogram/styles/result-page-dashboard.wxss | 834 ++++++++++++ miniprogram/utils/discInsights.js | 69 + miniprogram/utils/mbtiInsights.js | 124 ++ miniprogram/utils/mbtiLocalQuestions.js | 269 ++++ miniprogram/utils/pdpInsights.js | 67 + miniprogram/utils/questionBank.js | 57 +- miniprogram/utils/request.js | 258 ++-- miniprogram/utils/resultJourneyState.js | 88 ++ miniprogram/utils/resultProfileGate.js | 58 + miniprogram/utils/resultSectionScrollSync.js | 36 + miniprogram/utils/sbtiDisplay.js | 91 ++ scripts/apply_bt_download_env.py | 63 + ...backfill_ent_enterprise_dashboard_mysql.py | 379 ++++++ scripts/deploy_mbti_bt_api.py | 313 +++++ scripts/deploy_mbti_ssh.sh | 46 + scripts/dev_health.sh | 34 + scripts/dev_start.sh | 101 ++ scripts/dev_start_daemon.sh | 142 ++ scripts/dev_stop.sh | 37 + scripts/mysql_mirror_krimp_wechat_users.py | 150 ++ scripts/reshuffle_ent_test_labels_mysql.py | 204 +++ ...seed_ent5_orders_and_distribution_mysql.py | 401 ++++++ 开发文档/.obsidian/app.json | 1 + 开发文档/.obsidian/appearance.json | 1 + 开发文档/.obsidian/core-plugins.json | 33 + 开发文档/.obsidian/workspace.json | 237 ++++ 开发文档/00704fd6ec91ff23b8abbc18337c852d.jpg | Bin 0 -> 3012062 bytes .../1、需求/修改/MBTI闭环流程图-迭代优化.png | Bin 0 -> 898160 bytes 开发文档/1、需求/修改/ai功能202600417 -6.md | 7 + .../1、需求/修改/ai功能202600417 -6.plan.md | 246 ++++ .../1、需求/修改/ai功能202600417 -7.plan.md | 8 + .../1、需求/修改/推广模块202600416 -1.plan.md | 140 ++ .../1、需求/修改/管理后台20260417-plan-1.md | 19 + .../1、需求/修改/管理后台20260417.plan.md | 5 + .../1、需求/修改/网站全站修改202600417 -5.plan.md | 205 +++ .../1、需求/修改/超级管理后台20260417-exec.md | 95 ++ .../1、需求/修改/超级管理后台20260417-plan-1 1.md | 33 + .../全站全链路测试与缺陷_20260327.md | 12 +- .../1、需求/{修改 => 已完成}/小程序20260327 -2.md | 0 .../1、需求/{修改 => 已完成}/小程序20260327.md | 0 .../{修改 => 已完成}/管理后台20260327-1.md | 0 .../1、需求/已完成/网站全站修改202600417 -1.md | 42 + .../已完成/网站全站修改202600417 -1.plan.md | 157 +++ .../已完成/网站全站修改202600417 -2.plan.md | 151 +++ .../1、需求/已完成/网站全站修改202600417 -3.qa.md | 156 +++ .../1、需求/已完成/网站全站修改202600417 -4.md | 9 + .../1、需求/已完成/网站全站修改202600417 -5.md | 26 + 开发文档/2f31e4e827dd900c2e855088596d5519.png | Bin 0 -> 839980 bytes 开发文档/8、部署/微信小程序上传凭证.md | 21 + 开发文档/965e283925b460355a5dbb5b45263985.png | Bin 0 -> 667518 bytes 开发文档/MBTI王-全项目架构图.png | Bin 0 -> 1349064 bytes 开发文档/Pasted image 20260416161438.png | Bin 0 -> 309522 bytes 开发文档/Pasted image 20260416161656.png | Bin 0 -> 413618 bytes 开发文档/Pasted image 20260416161946.png | Bin 0 -> 421051 bytes 开发文档/Pasted image 20260416162054.png | Bin 0 -> 206741 bytes 开发文档/Pasted image 20260416162142.png | Bin 0 -> 528885 bytes 开发文档/Pasted image 20260417110250.png | Bin 0 -> 1173431 bytes 开发文档/Pasted image 20260417110259.png | Bin 0 -> 981782 bytes 开发文档/Pasted image 20260417111944.png | Bin 0 -> 628737 bytes 开发文档/Pasted image 20260417112053.png | Bin 0 -> 414485 bytes 开发文档/Pasted image 20260417112434.png | Bin 0 -> 529995 bytes 开发文档/Pasted image 20260417112616.png | Bin 0 -> 325076 bytes 开发文档/Pasted image 20260417112633.png | Bin 0 -> 491409 bytes 开发文档/Pasted image 20260417112854.png | Bin 0 -> 293021 bytes 开发文档/Pasted image 20260417113332.png | Bin 0 -> 475291 bytes 开发文档/Pasted image 20260417114129.png | Bin 0 -> 1238247 bytes 开发文档/Pasted image 20260417120633.png | Bin 0 -> 848924 bytes 开发文档/Pasted image 20260417120722.png | Bin 0 -> 626780 bytes 开发文档/Pasted image 20260417120805.png | Bin 0 -> 291758 bytes 开发文档/Pasted image 20260417120826.png | Bin 0 -> 345843 bytes 开发文档/Pasted image 20260417120959.png | Bin 0 -> 457650 bytes 开发文档/Pasted image 20260417152722.png | Bin 0 -> 635564 bytes 开发文档/Pasted image 20260417152833.png | Bin 0 -> 298167 bytes 开发文档/Pasted image 20260417153100.png | Bin 0 -> 390563 bytes 开发文档/Pasted image 20260417165731.png | Bin 0 -> 161829 bytes 开发文档/Pasted image 20260417170150.png | Bin 0 -> 176662 bytes 开发文档/Pasted image 20260417170217.png | Bin 0 -> 195836 bytes 开发文档/Pasted image 20260417170249.png | Bin 0 -> 195836 bytes 开发文档/Pasted image 20260417170546.png | Bin 0 -> 128511 bytes 开发文档/Pasted image 20260417170725.png | Bin 0 -> 161237 bytes 开发文档/Pasted image 20260417170754.png | Bin 0 -> 170685 bytes 开发文档/Pasted image 20260417180044.png | Bin 0 -> 365009 bytes 开发文档/Pasted image 20260417180545.png | Bin 0 -> 123668 bytes 开发文档/Pasted image 20260417181742.png | Bin 0 -> 89585 bytes 开发文档/Pasted image 20260417181917.png | Bin 0 -> 321561 bytes 开发文档/e6bbeefa58d7c3e2d420c986d4ec745f.jpg | Bin 0 -> 268198 bytes 开发文档/未命名.md | 0 239 files changed, 21061 insertions(+), 2175 deletions(-) create mode 100644 admin/src/assets/admin-theme.css create mode 100644 admin/src/components/superadmin/SaPageHeader.vue create mode 100644 admin/src/components/superadmin/SaTabs.vue create mode 100644 admin/src/views/admin/OrderTimelineDrawer.vue create mode 100644 admin/src/views/superadmin/MpTabBar.vue create mode 100644 admin/src/views/superadmin/ProfitRules.vue create mode 100644 admin/src/views/superadmin/SoulArticles.vue create mode 100644 api/.env.local.merge.example create mode 100644 api/app/common/AnalyticsEventLabels.php create mode 100644 api/app/common/service/AiBalanceAlertService.php create mode 100644 api/app/common/service/AiCallService.php create mode 100644 api/app/common/service/AiChatArticleDisplayService.php create mode 100644 api/app/common/service/AiReportService.php create mode 100644 api/app/common/service/ProfitSharingService.php create mode 100644 api/app/common/service/SoulArticleService.php create mode 100644 api/app/common/service/UserJourneyService.php create mode 100644 api/app/controller/api/AiChat.php create mode 100644 api/app/controller/api/AiReport.php create mode 100644 api/app/controller/api/MpConfig.php create mode 100644 api/app/controller/superadmin/AiMonitor.php create mode 100644 api/app/controller/superadmin/MpTabBar.php create mode 100644 api/app/controller/superadmin/ProfitRule.php create mode 100644 api/app/controller/superadmin/SoulArticle.php create mode 100644 api/app/model/AiConversation.php create mode 100644 api/app/model/AiMessage.php create mode 100644 api/app/model/AiReport.php create mode 100644 api/app/model/ProfitSharingRule.php create mode 100644 api/app/model/SoulArticle.php create mode 100644 api/database/migrations/add_ai_chat_and_soul_articles.sql create mode 100644 api/database/migrations/add_cold_face.sql create mode 100644 api/database/migrations/add_mp_analytics_events_user_index.sql create mode 100644 api/database/migrations/add_tabbar_and_ai_profit_sharing.sql create mode 100644 api/database/migrations/add_tabbar_iconurl_and_soul_autosync.sql create mode 100644 miniprogram/.env.example create mode 100644 miniprogram/components/result-section-nav/index.js create mode 100644 miniprogram/components/result-section-nav/index.json create mode 100644 miniprogram/components/result-section-nav/index.wxml create mode 100644 miniprogram/components/result-section-nav/index.wxss create mode 100644 miniprogram/images/shenxian-ai-logo.png create mode 100644 miniprogram/images/shenxian-oldman-circle.png create mode 100644 miniprogram/pages/ai-chat/history.js create mode 100644 miniprogram/pages/ai-chat/history.json create mode 100644 miniprogram/pages/ai-chat/history.wxml create mode 100644 miniprogram/pages/ai-chat/history.wxss create mode 100644 miniprogram/pages/ai-chat/index.js create mode 100644 miniprogram/pages/ai-chat/index.json create mode 100644 miniprogram/pages/ai-chat/index.wxml create mode 100644 miniprogram/pages/ai-chat/index.wxss create mode 100644 miniprogram/pages/ai-chat/report.js create mode 100644 miniprogram/pages/ai-chat/report.json create mode 100644 miniprogram/pages/ai-chat/report.wxml create mode 100644 miniprogram/pages/ai-chat/report.wxss create mode 100644 miniprogram/pages/match-job/index.js create mode 100644 miniprogram/pages/match-job/index.json create mode 100644 miniprogram/pages/match-job/index.wxml create mode 100644 miniprogram/pages/match-job/index.wxss create mode 100644 miniprogram/pages/webview/index.js create mode 100644 miniprogram/pages/webview/index.json create mode 100644 miniprogram/pages/webview/index.wxml create mode 100644 miniprogram/pages/webview/index.wxss create mode 100644 miniprogram/styles/result-page-dashboard.wxss create mode 100644 miniprogram/utils/discInsights.js create mode 100644 miniprogram/utils/mbtiInsights.js create mode 100644 miniprogram/utils/mbtiLocalQuestions.js create mode 100644 miniprogram/utils/pdpInsights.js create mode 100644 miniprogram/utils/resultJourneyState.js create mode 100644 miniprogram/utils/resultProfileGate.js create mode 100644 miniprogram/utils/resultSectionScrollSync.js create mode 100644 miniprogram/utils/sbtiDisplay.js create mode 100644 scripts/apply_bt_download_env.py create mode 100644 scripts/backfill_ent_enterprise_dashboard_mysql.py create mode 100644 scripts/deploy_mbti_bt_api.py create mode 100755 scripts/deploy_mbti_ssh.sh create mode 100644 scripts/dev_health.sh create mode 100755 scripts/dev_start.sh create mode 100644 scripts/dev_start_daemon.sh create mode 100755 scripts/dev_stop.sh create mode 100644 scripts/mysql_mirror_krimp_wechat_users.py create mode 100644 scripts/reshuffle_ent_test_labels_mysql.py create mode 100644 scripts/seed_ent5_orders_and_distribution_mysql.py create mode 100644 开发文档/.obsidian/app.json create mode 100644 开发文档/.obsidian/appearance.json create mode 100644 开发文档/.obsidian/core-plugins.json create mode 100644 开发文档/.obsidian/workspace.json create mode 100644 开发文档/00704fd6ec91ff23b8abbc18337c852d.jpg create mode 100644 开发文档/1、需求/修改/MBTI闭环流程图-迭代优化.png create mode 100644 开发文档/1、需求/修改/ai功能202600417 -6.md create mode 100644 开发文档/1、需求/修改/ai功能202600417 -6.plan.md create mode 100644 开发文档/1、需求/修改/ai功能202600417 -7.plan.md create mode 100644 开发文档/1、需求/修改/推广模块202600416 -1.plan.md create mode 100644 开发文档/1、需求/修改/管理后台20260417-plan-1.md create mode 100644 开发文档/1、需求/修改/管理后台20260417.plan.md create mode 100644 开发文档/1、需求/修改/网站全站修改202600417 -5.plan.md create mode 100644 开发文档/1、需求/修改/超级管理后台20260417-exec.md create mode 100644 开发文档/1、需求/修改/超级管理后台20260417-plan-1 1.md rename 开发文档/1、需求/{修改 => 已完成}/全站全链路测试与缺陷_20260327.md (88%) rename 开发文档/1、需求/{修改 => 已完成}/小程序20260327 -2.md (100%) rename 开发文档/1、需求/{修改 => 已完成}/小程序20260327.md (100%) rename 开发文档/1、需求/{修改 => 已完成}/管理后台20260327-1.md (100%) create mode 100644 开发文档/1、需求/已完成/网站全站修改202600417 -1.md create mode 100644 开发文档/1、需求/已完成/网站全站修改202600417 -1.plan.md create mode 100644 开发文档/1、需求/已完成/网站全站修改202600417 -2.plan.md create mode 100644 开发文档/1、需求/已完成/网站全站修改202600417 -3.qa.md create mode 100644 开发文档/1、需求/已完成/网站全站修改202600417 -4.md create mode 100644 开发文档/1、需求/已完成/网站全站修改202600417 -5.md create mode 100644 开发文档/2f31e4e827dd900c2e855088596d5519.png create mode 100644 开发文档/8、部署/微信小程序上传凭证.md create mode 100644 开发文档/965e283925b460355a5dbb5b45263985.png create mode 100644 开发文档/MBTI王-全项目架构图.png create mode 100644 开发文档/Pasted image 20260416161438.png create mode 100644 开发文档/Pasted image 20260416161656.png create mode 100644 开发文档/Pasted image 20260416161946.png create mode 100644 开发文档/Pasted image 20260416162054.png create mode 100644 开发文档/Pasted image 20260416162142.png create mode 100644 开发文档/Pasted image 20260417110250.png create mode 100644 开发文档/Pasted image 20260417110259.png create mode 100644 开发文档/Pasted image 20260417111944.png create mode 100644 开发文档/Pasted image 20260417112053.png create mode 100644 开发文档/Pasted image 20260417112434.png create mode 100644 开发文档/Pasted image 20260417112616.png create mode 100644 开发文档/Pasted image 20260417112633.png create mode 100644 开发文档/Pasted image 20260417112854.png create mode 100644 开发文档/Pasted image 20260417113332.png create mode 100644 开发文档/Pasted image 20260417114129.png create mode 100644 开发文档/Pasted image 20260417120633.png create mode 100644 开发文档/Pasted image 20260417120722.png create mode 100644 开发文档/Pasted image 20260417120805.png create mode 100644 开发文档/Pasted image 20260417120826.png create mode 100644 开发文档/Pasted image 20260417120959.png create mode 100644 开发文档/Pasted image 20260417152722.png create mode 100644 开发文档/Pasted image 20260417152833.png create mode 100644 开发文档/Pasted image 20260417153100.png create mode 100644 开发文档/Pasted image 20260417165731.png create mode 100644 开发文档/Pasted image 20260417170150.png create mode 100644 开发文档/Pasted image 20260417170217.png create mode 100644 开发文档/Pasted image 20260417170249.png create mode 100644 开发文档/Pasted image 20260417170546.png create mode 100644 开发文档/Pasted image 20260417170725.png create mode 100644 开发文档/Pasted image 20260417170754.png create mode 100644 开发文档/Pasted image 20260417180044.png create mode 100644 开发文档/Pasted image 20260417180545.png create mode 100644 开发文档/Pasted image 20260417181742.png create mode 100644 开发文档/Pasted image 20260417181917.png create mode 100644 开发文档/e6bbeefa58d7c3e2d420c986d4ec745f.jpg create mode 100644 开发文档/未命名.md diff --git a/.gitignore b/.gitignore index 06b9d18..3e2a535 100644 --- a/.gitignore +++ b/.gitignore @@ -11,12 +11,15 @@ build/ .env .env.local .env.*.local +.env.local.merge +.env.bak.* # 日志与运行时 *.log api/runtime/log/ api/runtime/cache/ api/runtime/temp/ +api/runtime/dev-*.pid # IDE .idea/ diff --git a/README.md b/README.md index 382adb6..ab4d312 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,44 @@ git push origin main ## 本地开发(管理后台 + API) -1. 启动 API(需 PHP 8+,示例用 Homebrew 的 8.4): +### 推荐 A:后台常驻(关终端 / 关 Cursor 仍可用) + +在仓库根目录执行(自动释放 **8787**、**5173**,起 PHP + Vite,**nohup 后台**,并做接口自检): + +```bash +bash scripts/dev_stop.sh # 可选:先停旧进程 +bash scripts/dev_start_daemon.sh +# 或 admin 目录:npm run dev:daemon +``` + +浏览器:**http://127.0.0.1:5173/admin/login** +日志:`api/runtime/php-dev-server.log`、`api/runtime/vite-dev-server.log` +自检:`bash scripts/dev_health.sh`(或 `npm run dev:health`) +停止:`bash scripts/dev_stop.sh` + +### 推荐 B:前台一键(调试用,Ctrl+C 即停) + +```bash +bash scripts/dev_start.sh +# 或 admin 目录:npm run dev:all +``` + +(请保持 `admin/.env.development` 里 `VITE_API_BASE_URL` **留空**,走 Vite 代理。) + +可选环境变量:`MBTI_PHP_BIN`、`MBTI_API_PORT`、`MBTI_ADMIN_PORT`、`MBTI_API_CURL_TIMEOUT`(守护进程等 API 的 curl 秒数,默认 120)。 +前端开发环境 axios 默认 **180s** 超时(云库慢);可在 `admin/.env.development` 设 `VITE_REQUEST_TIMEOUT_MS`。 +Vite 代理默认 **180s**(`VITE_PROXY_TIMEOUT_MS` 可改)。 + +### 手动分步(与旧流程一致) + +1. 启动 API(需 PHP 8+): ```bash cd api/public /usr/local/opt/php@8.4/bin/php -S 127.0.0.1:8787 router.php ``` -2. 启动管理后台(`admin/.env.development` 已默认走代理,无需改 hosts): +2. 启动管理后台: ```bash cd admin @@ -46,5 +76,13 @@ npm install npm run dev ``` -浏览器打开 `http://localhost:5173` ,接口会经 Vite 转发到 `http://127.0.0.1:8787`。若 API 端口不是 8787,可执行 +接口经 Vite 转发到 `http://127.0.0.1:8787`。若 API 端口不是 8787,可执行 `VITE_DEV_API_PROXY=http://127.0.0.1:端口 npm run dev`。 + +### 从 kr 宝塔同步 `.env` + +1. 在宝塔站点目录下载或复制线上 `api/.env` 到本机(任意路径)。 +2. 执行:`python3 scripts/apply_bt_download_env.py /你下载的路径/.env` + - 会自动备份当前 `api/.env` 为 `api/.env.bak.<时间戳>`。 +3. 若宝塔里数据库是 `127.0.0.1`(指服务器本机),本机需隧道或改连云库:可复制 `api/.env.local.merge.example` 为 `api/.env.local.merge`,按注释写好覆盖项,再重新执行第 2 步(会追加合并)。 +4. **管理端**请保持 `admin/.env.development` 里 `VITE_API_BASE_URL` **留空**,否则浏览器会直连 `mbti.com` 等域名,易出现 **Network Error**。 diff --git a/admin/package.json b/admin/package.json index 19eda8f..ed294d1 100644 --- a/admin/package.json +++ b/admin/package.json @@ -1,34 +1,37 @@ -{ - "name": "mbti-admin", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" - }, - "dependencies": { - "@element-plus/icons-vue": "^2.3.1", - "axios": "^1.6.7", - "dayjs": "^1.11.10", - "echarts": "^5.6.0", - "element-plus": "^2.6.2", - "pinia": "^2.1.7", - "vue": "^3.4.21", - "vue-echarts": "^6.6.9", - "vue-router": "^4.3.0", - "vue3-draggable-resizable": "^1.6.5" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.0.4", - "@vue/tsconfig": "^0.5.1", - "sass": "^1.71.1", - "typescript": "~5.4.0", - "unplugin-auto-import": "^0.17.5", - "unplugin-vue-components": "^0.26.0", - "vite": "^5.1.6", - "vue-tsc": "^2.0.6" - } -} +{ + "name": "mbti-admin", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "dev:all": "bash ../scripts/dev_start.sh", + "dev:daemon": "bash ../scripts/dev_start_daemon.sh", + "dev:health": "bash ../scripts/dev_health.sh", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "axios": "^1.6.7", + "dayjs": "^1.11.10", + "echarts": "^5.6.0", + "element-plus": "^2.6.2", + "pinia": "^2.1.7", + "vue": "^3.4.21", + "vue-echarts": "^6.6.9", + "vue-router": "^4.3.0", + "vue3-draggable-resizable": "^1.6.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "@vue/tsconfig": "^0.5.1", + "sass": "^1.71.1", + "typescript": "~5.4.0", + "unplugin-auto-import": "^0.17.5", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.1.6", + "vue-tsc": "^2.0.6" + } +} diff --git a/admin/src/assets/admin-theme.css b/admin/src/assets/admin-theme.css new file mode 100644 index 0000000..f4e4812 --- /dev/null +++ b/admin/src/assets/admin-theme.css @@ -0,0 +1,1207 @@ +/* 企业后台 /admin/* 统一浅色柔和主题(不影响 /superadmin) */ + +.admin-layout { + --admin-primary: #4F46E5; + --admin-primary-soft: #EEF2FF; + --admin-primary-hover: #4338CA; + --admin-success: #10B981; + --admin-success-soft: #ECFDF5; + --admin-warning: #F59E0B; + --admin-warning-soft: #FFFBEB; + --admin-danger: #EF4444; + --admin-danger-soft: #FEF2F2; + --admin-info: #64748B; + --admin-info-soft: #F1F5F9; + --admin-bg: #F8FAFC; + --admin-card-bg: #FFFFFF; + --admin-text: #0F172A; + --admin-text-sub: #64748B; + --admin-border: #E2E8F0; + --admin-radius: 12px; + --admin-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04); +} + +.admin-layout .layout-main { + background-color: var(--admin-bg); +} + +/* 通用卡片骨架 */ +.admin-layout .admin-card, +.admin-layout .content-card { + background: var(--admin-card-bg); + border-radius: var(--admin-radius); + box-shadow: var(--admin-shadow); + border: 1px solid var(--admin-border); + padding: 20px; +} + +/* KPI 卡片网格 */ +.admin-layout .kpi-row { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 16px; +} +@media (max-width: 1200px) { + .admin-layout .kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} +@media (max-width: 640px) { + .admin-layout .kpi-row { grid-template-columns: 1fr; } +} + +.admin-layout .kpi-card { + background: var(--admin-card-bg); + border-radius: var(--admin-radius); + box-shadow: var(--admin-shadow); + border: 1px solid var(--admin-border); + padding: 18px 20px; + display: flex; + flex-direction: column; + gap: 8px; + position: relative; + overflow: hidden; +} +.admin-layout .kpi-card .kpi-label { + font-size: 13px; + color: var(--admin-text-sub); + font-weight: 500; +} +.admin-layout .kpi-card .kpi-value { + font-size: 26px; + font-weight: 700; + color: var(--admin-text); + letter-spacing: -0.02em; + line-height: 1.1; +} +.admin-layout .kpi-card .kpi-foot { + font-size: 12px; + color: var(--admin-text-sub); + display: flex; + align-items: center; + justify-content: space-between; +} +.admin-layout .kpi-card .kpi-icon { + position: absolute; + right: 14px; + top: 16px; + width: 36px; + height: 36px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; +} +.admin-layout .kpi-card.kpi-primary .kpi-icon { background: var(--admin-primary-soft); color: var(--admin-primary); } +.admin-layout .kpi-card.kpi-success .kpi-icon { background: var(--admin-success-soft); color: var(--admin-success); } +.admin-layout .kpi-card.kpi-warning .kpi-icon { background: var(--admin-warning-soft); color: var(--admin-warning); } +.admin-layout .kpi-card.kpi-danger .kpi-icon { background: var(--admin-danger-soft); color: var(--admin-danger); } +.admin-layout .kpi-card.kpi-info .kpi-icon { background: var(--admin-info-soft); color: var(--admin-info); } + +/* 页面头部 */ +.admin-layout .admin-page { + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} +.admin-layout .admin-page-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} +.admin-layout .admin-page-header .page-title { + font-size: 20px; + font-weight: 700; + color: var(--admin-text); +} +.admin-layout .admin-page-header .page-subtitle { + font-size: 13px; + color: var(--admin-text-sub); + margin-top: 4px; +} + +/* 表格轻量化 */ +.admin-layout .el-table { + --el-table-border-color: var(--admin-border); + --el-table-header-bg-color: #F8FAFC; + --el-table-row-hover-bg-color: #F8FAFC; + background: transparent; + color: var(--admin-text); +} +.admin-layout .el-table th.el-table__cell { + background-color: #F8FAFC !important; + color: var(--admin-text-sub) !important; + font-weight: 600; + font-size: 12.5px; +} +.admin-layout .el-table td.el-table__cell { + padding: 10px 0; +} + +/* 状态 badge */ +.admin-layout .status-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 2px 10px; + font-size: 12px; + border-radius: 999px; + font-weight: 500; + line-height: 1.5; + border: 1px solid transparent; +} +.admin-layout .status-badge::before { + content: ''; + width: 6px; height: 6px; + border-radius: 50%; + background: currentColor; +} +.admin-layout .status-badge.status-pending { background: #F1F5F9; color: #475569; } +.admin-layout .status-badge.status-paid { background: #EEF2FF; color: #4F46E5; } +.admin-layout .status-badge.status-completed { background: #ECFDF5; color: #059669; } +.admin-layout .status-badge.status-refunded { background: #FFF7ED; color: #C2410C; } +.admin-layout .status-badge.status-cancelled { background: #E2E8F0; color: #334155; } +.admin-layout .status-badge.status-failed { background: #FEF2F2; color: #DC2626; } +.admin-layout .status-badge.status-frozen { background: #FFFBEB; color: #B45309; } + +/* 冷脸标签 */ +.admin-layout .coldface-tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 1px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 500; + line-height: 1.6; + border: 1px solid transparent; +} +.admin-layout .coldface-tag.coldface-cold { background: #EFF6FF; color: #1D4ED8; border-color: #DBEAFE; } +.admin-layout .coldface-tag.coldface-neutral { background: #F1F5F9; color: #475569; border-color: #E2E8F0; } +.admin-layout .coldface-tag.coldface-warm { background: #FFF7ED; color: #C2410C; border-color: #FED7AA; } + +/* 用户行首列 */ +.admin-layout .user-cell { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; +} +.admin-layout .user-cell .user-avatar { + width: 36px; height: 36px; + border-radius: 10px; + background: var(--admin-primary-soft); + color: var(--admin-primary); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; + overflow: hidden; + flex-shrink: 0; +} +.admin-layout .user-cell .user-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} +.admin-layout .user-cell .user-meta { + display: flex; + flex-direction: column; + min-width: 0; + gap: 3px; +} +.admin-layout .user-cell .user-name { + font-size: 14px; + font-weight: 600; + color: var(--admin-text); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; +} +.admin-layout .user-cell .user-sub { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--admin-text-sub); +} + +/* 主按钮 */ +.admin-layout .el-button--primary { + --el-button-bg-color: var(--admin-primary); + --el-button-border-color: var(--admin-primary); + --el-button-hover-bg-color: var(--admin-primary-hover); + --el-button-hover-border-color: var(--admin-primary-hover); +} + +/* 侧栏高亮替换成新主色 */ +.admin-layout .layout-sidebar .sidebar-nav .nav-item.active { + background-color: var(--admin-primary-soft) !important; + color: var(--admin-primary) !important; +} +.admin-layout .layout-sidebar .sidebar-nav .nav-item.active::before { + background-color: var(--admin-primary) !important; +} +.admin-layout .layout-sidebar .sidebar-nav .nav-item.active .nav-icon { + color: var(--admin-primary) !important; +} +.admin-layout .layout-header .logo-icon { + background-color: var(--admin-primary) !important; +} + +/* 筛选条 */ +.admin-layout .admin-filter-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; +} + +/* 空状态 */ +.admin-layout .empty-hint { + color: var(--admin-text-sub); + font-size: 12px; +} + +/* ========================================================================== + 超级管理后台 /superadmin/* 统一主题 + 与 /admin 同色系(靛紫)但主色更深,加强「平台最高权限」视觉层级 + logo 保留红色徽章作为超管标识 + ========================================================================== */ +.superadmin-layout { + --sa-primary: #1E40AF; + --sa-primary-soft: #E0E7FF; + --sa-primary-hover: #1E3A8A; + --sa-primary-deep: #1E3A8A; + --sa-accent: #D97706; + --sa-accent-soft: #FEF3C7; + --sa-success: #10B981; + --sa-success-soft: #ECFDF5; + --sa-warning: #F59E0B; + --sa-warning-soft: #FFFBEB; + --sa-info: #64748B; + --sa-info-soft: #F1F5F9; + --sa-bg: #F8FAFC; + --sa-bg-sub: #F1F5F9; + --sa-card-bg: #FFFFFF; + --sa-text: #0F172A; + --sa-text-sub: #64748B; + --sa-text-muted: #94A3B8; + --sa-border: #E2E8F0; + --sa-border-soft: #F1F5F9; + --sa-radius: 12px; + --sa-radius-sm: 8px; + --sa-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04); + --sa-shadow-hover: 0 2px 4px rgba(15, 23, 42, 0.06), 0 8px 24px rgba(15, 23, 42, 0.08); +} + +.superadmin-layout .layout-main { + background-color: var(--sa-bg); +} + +/* 统一页壳 */ +.superadmin-layout .sa-page { + padding: 24px; + min-height: 100%; + display: flex; + flex-direction: column; + gap: 20px; +} +@media (max-width: 768px) { + .superadmin-layout .sa-page { padding: 16px; gap: 16px; } +} + +/* 页头 */ +.superadmin-layout .sa-page-head { + display: flex; + flex-direction: column; + gap: 6px; +} +.superadmin-layout .sa-page-head .sa-title { + margin: 0; + font-size: 22px; + font-weight: 700; + color: var(--sa-text); + letter-spacing: -0.01em; + display: flex; + align-items: center; + gap: 10px; +} +.superadmin-layout .sa-page-head .sa-title .sa-title-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: 11px; + font-weight: 600; + color: var(--sa-accent); + background: var(--sa-accent-soft); + border-radius: 999px; + letter-spacing: 0.02em; +} +.superadmin-layout .sa-page-head .sa-subtitle { + margin: 0; + font-size: 13.5px; + color: var(--sa-text-sub); + line-height: 1.55; + max-width: 780px; +} +.superadmin-layout .sa-page-head.sa-page-head-row { + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; +} +.superadmin-layout .sa-page-head .sa-head-actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +/* Tab 切换 */ +.superadmin-layout .sa-tabs { + background-color: var(--sa-bg-sub); + padding: 4px; + border-radius: var(--sa-radius-sm); + display: inline-flex; + align-items: center; + gap: 4px; + max-width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} +.superadmin-layout .sa-tabs .sa-tab-item { + cursor: pointer; + border-radius: 6px; + white-space: nowrap; + color: var(--sa-text-sub); + padding: 7px 18px; + font-size: 13.5px; + font-weight: 500; + transition: all 0.18s; + line-height: 1.4; +} +.superadmin-layout .sa-tabs .sa-tab-item:hover { + color: var(--sa-text); +} +.superadmin-layout .sa-tabs .sa-tab-item.is-active { + background: var(--sa-card-bg); + color: var(--sa-text); + font-weight: 600; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08); +} +.superadmin-layout .sa-tabs .sa-tab-item.is-active .sa-tab-dot { + background: var(--sa-primary); +} +.superadmin-layout .sa-tabs .sa-tab-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--sa-text-muted); + margin-right: 6px; + vertical-align: middle; + transition: background 0.18s; +} + +/* 卡片 */ +.superadmin-layout .sa-card { + background: var(--sa-card-bg); + border-radius: var(--sa-radius); + border: 1px solid var(--sa-border); + box-shadow: var(--sa-shadow); + padding: 20px; +} +.superadmin-layout .sa-card.sa-card-pad-lg { + padding: 24px; +} +.superadmin-layout .sa-card.sa-card-flush { + padding: 0; +} +.superadmin-layout .sa-card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding-bottom: 14px; + margin-bottom: 14px; + border-bottom: 1px solid var(--sa-border-soft); +} +.superadmin-layout .sa-card-head .sa-card-title { + font-size: 15px; + font-weight: 600; + color: var(--sa-text); + display: flex; + align-items: center; + gap: 8px; +} +.superadmin-layout .sa-card-head .sa-card-sub { + font-size: 12.5px; + color: var(--sa-text-muted); +} + +/* KPI 卡片网格 */ +.superadmin-layout .sa-kpi-row { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 14px; +} +@media (max-width: 1200px) { + .superadmin-layout .sa-kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} +@media (max-width: 640px) { + .superadmin-layout .sa-kpi-row { grid-template-columns: 1fr; } +} +.superadmin-layout .sa-kpi-card { + background: var(--sa-card-bg); + border-radius: var(--sa-radius); + border: 1px solid var(--sa-border); + box-shadow: var(--sa-shadow); + padding: 18px 20px; + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + gap: 8px; + transition: all 0.2s; +} +.superadmin-layout .sa-kpi-card:hover { + box-shadow: var(--sa-shadow-hover); + transform: translateY(-1px); +} +.superadmin-layout .sa-kpi-card .sa-kpi-label { + font-size: 13px; + color: var(--sa-text-sub); + font-weight: 500; +} +.superadmin-layout .sa-kpi-card .sa-kpi-value { + font-size: 26px; + font-weight: 700; + color: var(--sa-text); + letter-spacing: -0.02em; + line-height: 1.1; +} +.superadmin-layout .sa-kpi-card .sa-kpi-foot { + font-size: 12px; + color: var(--sa-text-sub); + display: flex; + align-items: center; + gap: 6px; +} +.superadmin-layout .sa-kpi-card .sa-kpi-foot.is-positive { color: var(--sa-success); } +.superadmin-layout .sa-kpi-card .sa-kpi-foot.is-negative { color: var(--sa-accent); } +.superadmin-layout .sa-kpi-card .sa-kpi-icon { + position: absolute; + right: 14px; + top: 16px; + width: 36px; + height: 36px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; +} +.superadmin-layout .sa-kpi-card.sa-kpi-primary .sa-kpi-icon { background: var(--sa-primary-soft); color: var(--sa-primary); } +.superadmin-layout .sa-kpi-card.sa-kpi-success .sa-kpi-icon { background: var(--sa-success-soft); color: var(--sa-success); } +.superadmin-layout .sa-kpi-card.sa-kpi-warning .sa-kpi-icon { background: var(--sa-warning-soft); color: var(--sa-warning); } +.superadmin-layout .sa-kpi-card.sa-kpi-accent .sa-kpi-icon { background: var(--sa-accent-soft); color: var(--sa-accent); } +.superadmin-layout .sa-kpi-card.sa-kpi-info .sa-kpi-icon { background: var(--sa-info-soft); color: var(--sa-info); } + +/* 工具栏 */ +.superadmin-layout .sa-toolbar { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} +.superadmin-layout .sa-toolbar .sa-toolbar-label { + font-size: 12.5px; + color: var(--sa-text-sub); + margin-right: 2px; +} + +/* 主按钮 */ +.superadmin-layout .el-button--primary { + --el-button-bg-color: var(--sa-primary); + --el-button-border-color: var(--sa-primary); + --el-button-hover-bg-color: var(--sa-primary-hover); + --el-button-hover-border-color: var(--sa-primary-hover); + --el-button-active-bg-color: var(--sa-primary-deep); + --el-button-active-border-color: var(--sa-primary-deep); +} + +/* 侧栏高亮替换成新主色 */ +.superadmin-layout .layout-sidebar .sidebar-nav .nav-item.active { + background-color: var(--sa-primary-soft) !important; + color: var(--sa-primary) !important; +} +.superadmin-layout .layout-sidebar .sidebar-nav .nav-item.active::before { + background-color: var(--sa-primary) !important; +} +.superadmin-layout .layout-sidebar .sidebar-nav .nav-item.active .nav-icon { + color: var(--sa-primary) !important; +} + +/* 侧栏分组标题 */ +.superadmin-layout .layout-sidebar .sidebar-nav .nav-group-label { + padding: 14px 24px 6px; + font-size: 11px; + font-weight: 600; + color: var(--sa-text-muted); + letter-spacing: 0.08em; + text-transform: uppercase; +} + +/* 表格轻量化(与 admin 对齐) */ +.superadmin-layout .el-table { + --el-table-border-color: var(--sa-border); + --el-table-header-bg-color: var(--sa-bg); + --el-table-row-hover-bg-color: var(--sa-bg); + background: transparent; + color: var(--sa-text); +} +.superadmin-layout .el-table th.el-table__cell { + background-color: var(--sa-bg) !important; + color: var(--sa-text-sub) !important; + font-weight: 600; + font-size: 12.5px; +} +.superadmin-layout .el-table td.el-table__cell { + padding: 10px 0; +} + +/* 空状态 */ +.superadmin-layout .sa-empty-hint { + color: var(--sa-text-muted); + font-size: 12.5px; + padding: 40px 20px; + text-align: center; +} + +/* 嵌入模式:当子页以 embedded 形式被 Hub 使用时,隐藏重复页头 */ +.superadmin-layout .is-embedded .embedded-hide { + display: none !important; +} + +/* ========================================================================== + Overview.vue · 视觉对齐(零接触 .vue 文件,通过提高选择器特异性/重要性覆盖 scoped 样式) + ========================================================================== */ + +/* 总览区 & 统计卡 */ +.superadmin-layout .overview-section .section-header .section-title { + font-size: 15px !important; + font-weight: 600 !important; + color: var(--sa-text) !important; +} +.superadmin-layout .overview-section .section-header .section-subtitle { + color: var(--sa-text-sub) !important; + font-size: 13px !important; +} +.superadmin-layout .overview-section .stats-grid { + gap: 14px !important; +} +.superadmin-layout .overview-section .stats-grid .stat-card, +.superadmin-layout .quick-actions-section .actions-grid .action-card, +.superadmin-layout .invite-section, +.superadmin-layout .trend-section, +.superadmin-layout .recent-dynamics-section, +.superadmin-layout .ranking-section { + border-radius: var(--sa-radius) !important; + border: 1px solid var(--sa-border) !important; + box-shadow: var(--sa-shadow) !important; + background: var(--sa-card-bg) !important; + transition: all 0.2s !important; +} +.superadmin-layout .overview-section .stats-grid .stat-card:hover { + box-shadow: var(--sa-shadow-hover) !important; + transform: translateY(-1px) !important; +} +.superadmin-layout .overview-section .stats-grid .stat-card .stat-info .stat-label { + color: var(--sa-text-sub) !important; + font-size: 13px !important; + font-weight: 500 !important; +} +.superadmin-layout .overview-section .stats-grid .stat-card .stat-info .stat-value { + font-size: 26px !important; + letter-spacing: -0.02em !important; + color: var(--sa-text) !important; +} +.superadmin-layout .overview-section .stats-grid .stat-card .stat-info .stat-trend { + color: var(--sa-text-muted) !important; + font-size: 12px !important; +} + +/* stat-icon 色系柔化:purple→primary 靛紫 | green→success | orange→warning */ +.superadmin-layout .overview-section .stats-grid .stat-card .stat-icon { + width: 40px !important; + height: 40px !important; + border-radius: 10px !important; + font-size: 18px !important; +} +.superadmin-layout .overview-section .stats-grid .stat-card .stat-icon.purple { + background-color: var(--sa-primary-soft) !important; + color: var(--sa-primary) !important; +} +.superadmin-layout .overview-section .stats-grid .stat-card .stat-icon.green { + background-color: var(--sa-success-soft) !important; + color: var(--sa-success) !important; +} +.superadmin-layout .overview-section .stats-grid .stat-card .stat-icon.orange { + background-color: var(--sa-warning-soft) !important; + color: var(--sa-warning) !important; +} + +/* 快捷操作色系收敛:blue/purple 合并到 primary,其他柔化,图标色从纯白改为深色主调 */ +.superadmin-layout .quick-actions-section .section-header .section-title { + font-size: 15px !important; + font-weight: 600 !important; + color: var(--sa-text) !important; + display: flex; + align-items: center; + gap: 8px; +} +.superadmin-layout .quick-actions-section .actions-grid .action-card .action-icon { + width: 44px !important; + height: 44px !important; + border-radius: 10px !important; + font-size: 20px !important; +} +.superadmin-layout .quick-actions-section .actions-grid .action-card .action-label { + font-size: 13px !important; + color: var(--sa-text) !important; + font-weight: 500 !important; +} +.superadmin-layout .quick-actions-section .actions-grid .action-card.blue .action-icon, +.superadmin-layout .quick-actions-section .actions-grid .action-card.purple .action-icon { + background-color: var(--sa-primary-soft) !important; + color: var(--sa-primary) !important; +} +.superadmin-layout .quick-actions-section .actions-grid .action-card.green .action-icon { + background-color: var(--sa-success-soft) !important; + color: var(--sa-success) !important; +} +.superadmin-layout .quick-actions-section .actions-grid .action-card.orange .action-icon { + background-color: var(--sa-warning-soft) !important; + color: var(--sa-warning) !important; +} +.superadmin-layout .quick-actions-section .actions-grid .action-card.gray .action-icon { + background-color: var(--sa-info-soft) !important; + color: var(--sa-info) !important; +} +.superadmin-layout .quick-actions-section .actions-grid .action-card:hover { + box-shadow: var(--sa-shadow-hover) !important; + transform: translateY(-2px) !important; + border-color: var(--sa-primary-soft) !important; +} + +/* 邀请码卡片 */ +.superadmin-layout .invite-section .invite-label { + color: var(--sa-text-sub) !important; + font-weight: 500 !important; +} +.superadmin-layout .invite-section .invite-img { + border-color: var(--sa-border) !important; +} +.superadmin-layout .invite-section .invite-placeholder { + color: var(--sa-text-muted) !important; + font-size: 12.5px !important; +} + +/* 下方左右双栏:动态 / 排行榜 */ +.superadmin-layout .recent-dynamics-section .section-header, +.superadmin-layout .ranking-section .section-header { + border-bottom-color: var(--sa-border-soft) !important; + padding-bottom: 12px !important; + margin-bottom: 14px !important; +} +.superadmin-layout .recent-dynamics-section .section-header .section-title, +.superadmin-layout .ranking-section .section-header .section-title { + font-size: 15px !important; + font-weight: 600 !important; + color: var(--sa-text) !important; +} +.superadmin-layout .recent-dynamics-section .section-header .view-all-btn, +.superadmin-layout .ranking-section .section-header .view-all-btn { + color: var(--sa-primary) !important; +} +.superadmin-layout .recent-dynamics-section .section-header .view-all-btn:hover, +.superadmin-layout .ranking-section .section-header .view-all-btn:hover { + color: var(--sa-primary-hover) !important; +} + +/* 动态 / 排行条目分隔线淡化 */ +.superadmin-layout .dynamics-list .dynamics-item, +.superadmin-layout .ranking-list .ranking-item { + border-bottom-color: var(--sa-border-soft) !important; +} +.superadmin-layout .ranking-list .ranking-item .ranking-number { + background-color: var(--sa-bg-sub) !important; + color: var(--sa-text-sub) !important; +} +.superadmin-layout .ranking-list .ranking-item .ranking-number.top-one { + background-color: var(--sa-warning-soft) !important; + color: var(--sa-warning) !important; +} + +/* ========================================================================== + MpAnalytics.vue · 视觉对齐(嵌入态) + ========================================================================== */ +.superadmin-layout .mp-analytics .card-block { + margin-bottom: 16px !important; + border-radius: var(--sa-radius) !important; + border: 1px solid var(--sa-border) !important; + box-shadow: var(--sa-shadow) !important; +} +.superadmin-layout .mp-analytics .card-block .el-card__header { + padding: 14px 20px !important; + border-bottom: 1px solid var(--sa-border-soft) !important; + font-size: 14px !important; + font-weight: 600 !important; + color: var(--sa-text) !important; +} +.superadmin-layout .mp-analytics .card-block .el-card__body { + padding: 20px !important; +} +.superadmin-layout .mp-analytics .embedded-toolbar { + background: var(--sa-card-bg); + border: 1px solid var(--sa-border); + border-radius: var(--sa-radius-sm); + padding: 10px 14px; + margin-bottom: 16px; + box-shadow: var(--sa-shadow); +} +.superadmin-layout .mp-analytics .embedded-toolbar .hint { + color: var(--sa-text-sub) !important; + font-size: 12.5px !important; +} +.superadmin-layout .mp-analytics .embedded-toolbar .toolbar .label, +.superadmin-layout .mp-analytics .page-head .toolbar .label { + color: var(--sa-text-sub) !important; + font-size: 12.5px !important; +} +.superadmin-layout .mp-analytics .props-preview { + color: var(--sa-text-muted) !important; +} +.superadmin-layout .mp-analytics .empty-hint { + color: var(--sa-text-muted) !important; +} +.superadmin-layout .mp-analytics .filter-row { + margin-bottom: 16px !important; +} +.superadmin-layout .mp-analytics .pager { + margin-top: 16px !important; +} + +/* ========================================================================== + Users.vue / Enterprises.vue · 视觉对齐 + 仅通过选择器覆盖,不改 .vue + ========================================================================== */ + +/* 页壳与页头(两个大页都有 .page-container / .page-header) */ +.superadmin-layout .page-container { + padding: 0 !important; + background: transparent !important; +} +.superadmin-layout .page-container.is-embedded { + padding: 0 !important; +} +.superadmin-layout .page-header { + margin-bottom: 4px !important; +} +.superadmin-layout .page-header .title, +.superadmin-layout .page-header h1, +.superadmin-layout .page-header h2 { + font-size: 22px !important; + font-weight: 700 !important; + color: var(--sa-text) !important; + letter-spacing: -0.01em !important; +} +.superadmin-layout .page-header .subtitle { + color: var(--sa-text-sub) !important; + font-size: 13.5px !important; + margin-top: 4px !important; +} +.superadmin-layout .page-header .header-actions { + gap: 8px !important; +} + +/* Users.vue · 用户池总览卡 / 汇总指标卡 */ +.superadmin-layout .user-overview-grid { + gap: 14px !important; +} +.superadmin-layout .user-card { + border-radius: var(--sa-radius) !important; + border: 1px solid var(--sa-border) !important; + box-shadow: var(--sa-shadow) !important; + background: var(--sa-card-bg) !important; + transition: all 0.2s !important; +} +.superadmin-layout .user-card:hover { + box-shadow: var(--sa-shadow-hover) !important; + transform: translateY(-1px) !important; +} +.superadmin-layout .user-card .card-header .card-icon { + border-radius: 10px !important; + background: var(--sa-primary-soft) !important; + color: var(--sa-primary) !important; +} +.superadmin-layout .user-card .card-header .card-name { + color: var(--sa-text) !important; + font-weight: 600 !important; +} +.superadmin-layout .user-card .stat-main .stat-value-large { + font-size: 28px !important; + font-weight: 700 !important; + color: var(--sa-text) !important; + letter-spacing: -0.02em !important; +} +.superadmin-layout .user-card .stat-main .stat-label-large { + color: var(--sa-text-sub) !important; + font-size: 13px !important; +} +.superadmin-layout .user-card .stat-sub .stat-active { + color: var(--sa-success) !important; +} +.superadmin-layout .user-card .stat-sub .stat-tested { + color: var(--sa-info) !important; +} + +.superadmin-layout .summary-metrics-grid { + gap: 14px !important; +} +.superadmin-layout .summary-card { + border-radius: var(--sa-radius) !important; + border: 1px solid var(--sa-border) !important; + box-shadow: var(--sa-shadow) !important; + background: var(--sa-card-bg) !important; + transition: all 0.2s !important; +} +.superadmin-layout .summary-card:hover { + box-shadow: var(--sa-shadow-hover) !important; + transform: translateY(-1px) !important; +} +.superadmin-layout .summary-card .summary-label { + color: var(--sa-text-sub) !important; + font-size: 13px !important; + font-weight: 500 !important; +} +.superadmin-layout .summary-card .summary-value { + color: var(--sa-text) !important; + font-size: 26px !important; + font-weight: 700 !important; + letter-spacing: -0.02em !important; +} +.superadmin-layout .summary-card .summary-percent { + color: var(--sa-text-muted) !important; + font-size: 14px !important; + font-weight: 500 !important; + margin-left: 6px !important; +} + +/* Users.vue · MBTI 分布卡 / 搜索条 / 表格卡 */ +.superadmin-layout .mbti-distribution-card, +.superadmin-layout .search-section, +.superadmin-layout .table-card, +.superadmin-layout .content-card { + border-radius: var(--sa-radius) !important; + border: 1px solid var(--sa-border) !important; + box-shadow: var(--sa-shadow) !important; + background: var(--sa-card-bg) !important; +} +.superadmin-layout .mbti-distribution-card .mbti-header .mbti-icon { + color: var(--sa-primary) !important; +} +.superadmin-layout .mbti-distribution-card .mbti-header .mbti-title { + color: var(--sa-text) !important; + font-weight: 600 !important; +} + +/* 工具栏(Enterprises .toolbar / Users .search-section) */ +.superadmin-layout .toolbar { + gap: 10px !important; + align-items: center !important; +} + +/* 用户表格行:头像 + 元信息 */ +.superadmin-layout .user-table .user-info-cell { + gap: 10px !important; +} +.superadmin-layout .user-table .user-avatar { + border-radius: 10px !important; +} +.superadmin-layout .user-table .user-avatar.user-avatar-letter { + background: var(--sa-primary-soft) !important; + color: var(--sa-primary) !important; +} +.superadmin-layout .user-table .user-details .username { + color: var(--sa-text) !important; + font-weight: 600 !important; +} +.superadmin-layout .user-table .user-details .openid-line, +.superadmin-layout .user-table .user-details .phone-line { + color: var(--sa-text-muted) !important; + font-size: 12px !important; +} +.superadmin-layout .user-table .test-results .result-tag { + border-radius: 6px !important; + font-weight: 500 !important; +} +.superadmin-layout .user-table .test-results .no-test { + color: var(--sa-text-muted) !important; +} + +/* 分页 */ +.superadmin-layout .pagination-container { + padding-top: 8px !important; +} + +/* 企业详情弹窗抽屉侧栏:统一圆角边框 */ +.superadmin-layout .ud-card { + border-radius: var(--sa-radius-sm) !important; + border: 1px solid var(--sa-border) !important; + background: var(--sa-card-bg) !important; +} +.superadmin-layout .ud-side { + background: var(--sa-bg) !important; +} +.superadmin-layout .ud-tag { + background: var(--sa-primary-soft) !important; + color: var(--sa-primary) !important; + border-radius: 6px !important; +} +.superadmin-layout .ed-kpi-value { + color: var(--sa-text) !important; + font-weight: 700 !important; +} + +/* 空状态统一 */ +.superadmin-layout .empty-state { + color: var(--sa-text-muted) !important; +} + +/* ========================================================================== + Overview.vue · 视觉对齐(不改页面代码,只 remap 视觉 token) + ========================================================================== */ +.superadmin-layout .dashboard-container { + padding: 24px; + display: flex; + flex-direction: column; + gap: 20px; + background-color: var(--sa-bg); +} +.superadmin-layout .dashboard-container .section-header { + margin-bottom: 0 !important; + padding: 0 !important; +} +.superadmin-layout .dashboard-container .section-title { + font-size: 20px !important; + font-weight: 700 !important; + color: var(--sa-text) !important; + letter-spacing: -0.01em; + margin: 0 !important; +} +.superadmin-layout .dashboard-container .section-subtitle { + font-size: 12.5px !important; + color: var(--sa-text-sub) !important; + margin-top: 2px !important; +} +.superadmin-layout .dashboard-container .stats-grid { + gap: 14px !important; +} +.superadmin-layout .dashboard-container .stat-card { + background: var(--sa-card-bg) !important; + border: 1px solid var(--sa-border) !important; + border-radius: var(--sa-radius) !important; + box-shadow: var(--sa-shadow) !important; + padding: 18px 20px !important; + transition: transform 0.18s ease, box-shadow 0.2s ease; +} +.superadmin-layout .dashboard-container .stat-card:hover { + transform: translateY(-1px); + box-shadow: var(--sa-shadow-hover) !important; +} +.superadmin-layout .dashboard-container .stat-card .stat-label { + color: var(--sa-text-sub) !important; + font-size: 12.5px !important; + margin-bottom: 6px !important; +} +.superadmin-layout .dashboard-container .stat-card .stat-value { + color: var(--sa-text) !important; + font-size: 26px !important; + font-weight: 700 !important; + font-variant-numeric: tabular-nums; +} +.superadmin-layout .dashboard-container .stat-card .stat-trend { + font-size: 11.5px !important; + color: var(--sa-text-muted) !important; + margin-top: 6px !important; +} +.superadmin-layout .dashboard-container .stat-card .stat-icon { + width: 42px !important; + height: 42px !important; + border-radius: 12px !important; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; +} +.superadmin-layout .dashboard-container .stat-card .stat-icon.purple { background: var(--sa-primary-soft); color: var(--sa-primary); } +.superadmin-layout .dashboard-container .stat-card .stat-icon.green { background: var(--sa-success-soft); color: var(--sa-success); } +.superadmin-layout .dashboard-container .stat-card .stat-icon.orange { background: var(--sa-warning-soft); color: var(--sa-warning); } +.superadmin-layout .dashboard-container .stat-card .stat-icon.blue { background: var(--sa-primary-soft); color: var(--sa-primary); } + +.superadmin-layout .dashboard-container .actions-grid { + gap: 12px !important; +} +.superadmin-layout .dashboard-container .action-card { + background: var(--sa-card-bg) !important; + border: 1px solid var(--sa-border) !important; + border-radius: var(--sa-radius) !important; + box-shadow: var(--sa-shadow) !important; + padding: 16px 14px !important; + transition: transform 0.18s ease, box-shadow 0.2s ease, border-color 0.2s ease; +} +.superadmin-layout .dashboard-container .action-card:hover { + transform: translateY(-1px); + box-shadow: var(--sa-shadow-hover) !important; + border-color: var(--sa-primary) !important; +} +.superadmin-layout .dashboard-container .action-card .action-icon { + width: 40px !important; + height: 40px !important; + border-radius: 10px !important; + margin-bottom: 8px !important; + background: var(--sa-primary-soft) !important; + color: var(--sa-primary) !important; +} +.superadmin-layout .dashboard-container .action-card .action-label { + color: var(--sa-text) !important; + font-size: 13px !important; + font-weight: 600 !important; +} + +/* ========================================================================== + 通用:Users / Enterprises / Finance / Pricing / Questions / AIConfig / Settings + 把「筛选条 · 卡片 · 表格头」对齐 sa-* token(只 remap,不改页面) + ========================================================================== */ +.superadmin-layout .page-container, +.superadmin-layout .users-container, +.superadmin-layout .enterprise-container, +.superadmin-layout .enterprises-container, +.superadmin-layout .finance-container, +.superadmin-layout .pricing-container, +.superadmin-layout .questions-container, +.superadmin-layout .ai-config-container, +.superadmin-layout .settings-container, +.superadmin-layout .database-container, +.superadmin-layout .distribution-container { + padding: 24px; + background-color: var(--sa-bg); + min-height: calc(100vh - 56px); +} +.superadmin-layout .page-container .page-header, +.superadmin-layout .users-container .page-header, +.superadmin-layout .enterprise-container .page-header, +.superadmin-layout .enterprises-container .page-header, +.superadmin-layout .finance-container .page-header, +.superadmin-layout .pricing-container .page-header, +.superadmin-layout .questions-container .page-header, +.superadmin-layout .ai-config-container .page-header, +.superadmin-layout .settings-container .page-header, +.superadmin-layout .database-container .page-header, +.superadmin-layout .distribution-container .page-header { + background: var(--sa-card-bg); + border: 1px solid var(--sa-border); + border-radius: var(--sa-radius); + padding: 16px 20px; + margin-bottom: 16px; + box-shadow: var(--sa-shadow); +} +.superadmin-layout .page-container .page-header .page-title, +.superadmin-layout .page-container .page-header h2, +.superadmin-layout .users-container .page-header h2, +.superadmin-layout .enterprise-container .page-header h2, +.superadmin-layout .enterprises-container .page-header h2, +.superadmin-layout .finance-container .page-header h2, +.superadmin-layout .pricing-container .page-header h2, +.superadmin-layout .questions-container .page-header h2, +.superadmin-layout .ai-config-container .page-header h2, +.superadmin-layout .settings-container .page-header h2 { + font-size: 18px !important; + font-weight: 700 !important; + color: var(--sa-text) !important; + margin: 0 !important; +} + +/* 筛选条 */ +.superadmin-layout .filter-section, +.superadmin-layout .filter-bar, +.superadmin-layout .search-bar, +.superadmin-layout .query-bar { + background: var(--sa-card-bg) !important; + border: 1px solid var(--sa-border) !important; + border-radius: var(--sa-radius-sm) !important; + padding: 12px 14px !important; + margin-bottom: 14px !important; + box-shadow: var(--sa-shadow); +} + +/* 表格卡片壳(很多页用 .table-section / .content-card / .data-card / .list-section) */ +.superadmin-layout .table-section, +.superadmin-layout .content-card, +.superadmin-layout .data-card, +.superadmin-layout .list-section, +.superadmin-layout .chart-section { + background: var(--sa-card-bg); + border: 1px solid var(--sa-border); + border-radius: var(--sa-radius); + box-shadow: var(--sa-shadow); + padding: 16px; + margin-bottom: 16px; +} +.superadmin-layout .el-table th.el-table__cell { + background: var(--sa-bg-sub) !important; + color: var(--sa-text-sub) !important; + font-weight: 600 !important; +} +.superadmin-layout .el-table__empty-text { + color: var(--sa-text-muted) !important; +} + +/* 金额颜色 · 表格中的金额统一右对齐 + 暖色 */ +.superadmin-layout .amount-cell, +.superadmin-layout .money-value { + font-variant-numeric: tabular-nums; + font-weight: 600; + color: var(--sa-accent); +} +.superadmin-layout .amount-positive { + color: var(--sa-success); +} +.superadmin-layout .amount-negative { + color: var(--sa-accent); +} + +/* ========================================================================== + admin 端 · 企业后台 Dashboard.vue 强调余额 / 扣款 + ========================================================================== */ +.admin-layout .stat-card-recharge { + background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%) !important; + border: 1px solid #FDE68A !important; +} +.admin-layout .stat-card-recharge .stat-label { + color: #92400E !important; + font-weight: 600 !important; +} +.admin-layout .stat-card-recharge .stat-value { + color: #92400E !important; +} + diff --git a/admin/src/components/UserDetailDialog.vue b/admin/src/components/UserDetailDialog.vue index 25ba7d3..b1352bc 100644 --- a/admin/src/components/UserDetailDialog.vue +++ b/admin/src/components/UserDetailDialog.vue @@ -209,6 +209,42 @@
暂无人脸分析照片
+ + +
+
+ 统计范围 + + + + + + 刷新 +
+ + + + + + + + + + +
+
@@ -249,6 +285,7 @@ import VChart from 'vue-echarts' import { discTopTwoLabel, discCompactLabel } from '@/utils/discDisplay' import { buildFaceDetailFromParsed } from '@/utils/faceResultDetail' import { SBTI_RADAR_DIMENSION_ORDER, buildSbtiRadarValues, formatSbtiSummary } from '@/utils/sbtiDisplay' +import { request } from '@/utils/request' use([CanvasRenderer, RadarChart, GridComponent, TooltipComponent, LegendComponent, RadarComponent]) @@ -271,12 +308,17 @@ const udTab = ref('analysis') const testPage = ref(1) const testPageSize = 8 +const journeyDays = ref(30) +const journeyLoading = ref(false) +const journeyRows = ref([]) + watch( () => props.modelValue, v => { if (v) { udTab.value = 'analysis' testPage.value = 1 + journeyRows.value = [] } } ) @@ -285,9 +327,45 @@ watch( () => props.user?.id, () => { testPage.value = 1 + journeyRows.value = [] } ) +watch(udTab, (t) => { + if (t === 'journey' && journeyRows.value.length === 0) { + void loadJourney() + } +}) + +async function loadJourney() { + const uid = props.user?.id + if (!uid) { + journeyRows.value = [] + return + } + journeyLoading.value = true + try { + const res: any = await request.get('/superadmin/analytics/user-journey', { + params: { userId: uid, days: journeyDays.value } + }) + journeyRows.value = res.data?.list || [] + } catch { + journeyRows.value = [] + } finally { + journeyLoading.value = false + } +} + +function formatJourneyProps(p: unknown): string { + if (p == null) return '—' + try { + const s = JSON.stringify(p) + return s.length > 160 ? s.slice(0, 160) + '…' : s + } catch { + return '—' + } +} + const avatarLetter = computed(() => { const n = (props.user?.username || props.user?.nickname || '?').trim() return (n.charAt(0) || '?').toUpperCase() @@ -1096,6 +1174,35 @@ function openMail(email: string) { border-radius: 8px; } +.ud-journey { + display: flex; + flex-direction: column; + gap: 12px; +} + +.ud-journey__toolbar { + display: flex; + align-items: center; + gap: 10px; +} + +.ud-journey__label { + font-size: 12px; + color: #6b7280; +} + +.ud-journey__subname { + font-size: 12px; + color: #94a3b8; + margin-top: 2px; +} + +.ud-journey__props { + font-size: 12px; + color: #64748b; + word-break: break-all; +} + @media (max-width: 900px) { .ud-layout { flex-direction: column; diff --git a/admin/src/components/superadmin/SaPageHeader.vue b/admin/src/components/superadmin/SaPageHeader.vue new file mode 100644 index 0000000..3e069d0 --- /dev/null +++ b/admin/src/components/superadmin/SaPageHeader.vue @@ -0,0 +1,24 @@ + + + diff --git a/admin/src/components/superadmin/SaTabs.vue b/admin/src/components/superadmin/SaTabs.vue new file mode 100644 index 0000000..fa8f974 --- /dev/null +++ b/admin/src/components/superadmin/SaTabs.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/admin/src/layouts/AdminLayout.vue b/admin/src/layouts/AdminLayout.vue index 5d5535e..6f5f43c 100644 --- a/admin/src/layouts/AdminLayout.vue +++ b/admin/src/layouts/AdminLayout.vue @@ -86,11 +86,11 @@ const authStore = useAuthStore() const sidebarOpen = ref(false) const navItems = [ - { path: '/admin/dashboard', icon: DataLine, label: '概览' }, + { path: '/admin/dashboard', icon: DataLine, label: '企业概览' }, { path: '/admin/users', icon: User, label: '用户运营' }, { path: '/admin/orders', icon: ShoppingCart, label: '订单运营' }, { path: '/admin/distribution', icon: Share, label: '分销推广' }, - { path: '/admin/settings', icon: Setting, label: '系统设置' }, + { path: '/admin/settings', icon: Setting, label: '企业设置' }, ] const isActive = (path: string) => { diff --git a/admin/src/layouts/SuperAdminLayout.vue b/admin/src/layouts/SuperAdminLayout.vue index 8062f6c..a78c35e 100644 --- a/admin/src/layouts/SuperAdminLayout.vue +++ b/admin/src/layouts/SuperAdminLayout.vue @@ -35,21 +35,43 @@