feat: 企业权限上限与迁移脚本;管理端/超管概览与财务;双端小程序 tab/订单/历史/测评选择等同步

Made-with: Cursor
This commit is contained in:
Ghost
2026-03-30 17:12:59 +08:00
parent f589b5d50c
commit 3af7bb35dd
49 changed files with 2570 additions and 920 deletions

View File

@@ -10,7 +10,9 @@ Page({
siteTitle: '神仙团队AI性格测试',
startButtonEnterprise: '开始面部测试',
aiAnalysisText: '智能分析',
maintenanceMode: false
maintenanceMode: false,
reviewMode: false,
permFace: true
},
onLoad(options) {
@@ -62,13 +64,17 @@ Page({
const navbarHeightRpx = statusBarHeightRpx + 88
const gd = app.globalData
const maintenanceMode = !!(gd.reviewMode || gd.maintenanceMode)
const ep = gd.enterprisePermissions
const pf = !ep || ep.face !== false
this.setData({
statusBarHeight: statusBarHeightRpx,
navbarHeight: navbarHeightRpx,
siteTitle: gd.siteTitle || '神仙团队AI性格测试',
startButtonEnterprise: maintenanceMode ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始面部测试'),
startButtonEnterprise: (maintenanceMode || !pf) ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始面部测试'),
aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析',
maintenanceMode
maintenanceMode,
reviewMode: maintenanceMode,
permFace: pf
})
// 未绑定企业的用户:扫码带 eid 或超管配置了默认企业时也允许使用企业版
@@ -92,10 +98,14 @@ Page({
if (cfg.reviewMode !== undefined || cfg.maintenanceMode !== undefined) {
app.globalData.reviewMode = !!(cfg.reviewMode || cfg.maintenanceMode)
}
const ep2 = app.globalData.enterprisePermissions
const pf2 = !ep2 || ep2.face !== false
this.setData({
startButtonEnterprise: maintenanceMode ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonEnterprise || '开始面部测试'),
startButtonEnterprise: (maintenanceMode || !pf2) ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonEnterprise || '开始面部测试'),
aiAnalysisText: (cfg.textConfig && cfg.textConfig.aiAnalysisText) || '智能分析',
maintenanceMode
maintenanceMode,
reviewMode: maintenanceMode,
permFace: pf2
})
}
}).catch(() => {})
@@ -120,10 +130,14 @@ Page({
if (cfg.reviewMode !== undefined || cfg.maintenanceMode !== undefined) {
app.globalData.reviewMode = !!(cfg.reviewMode || cfg.maintenanceMode)
}
const ep3 = app.globalData.enterprisePermissions
const pf3 = !ep3 || ep3.face !== false
this.setData({
startButtonEnterprise: maintenanceMode ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonEnterprise || '开始面部测试'),
startButtonEnterprise: (maintenanceMode || !pf3) ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonEnterprise || '开始面部测试'),
aiAnalysisText: (cfg.textConfig && cfg.textConfig.aiAnalysisText) || '智能分析',
maintenanceMode
maintenanceMode,
reviewMode: maintenanceMode,
permFace: pf3
})
}
if ((cfg && cfg.pricingType) !== 'enterprise' && !app.globalData.enterpriseIdFromScene && !(app.globalData.defaultEnterpriseId && Number(app.globalData.defaultEnterpriseId) > 0)) {
@@ -157,18 +171,20 @@ Page({
onShow() {
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
const tabBar = this.getTabBar()
const gd = getApp().globalData || {}
const audit = !!(gd.reviewMode || gd.maintenanceMode)
tabBar.setData({ selected: 0, reviewMode: audit })
if (typeof tabBar.updateSelected === 'function') tabBar.updateSelected()
}
try { getApp().globalData.appScope = 'enterprise' } catch (e) {}
const gd = getApp().globalData
const maintenanceMode = !!(gd.reviewMode || gd.maintenanceMode)
const ep4 = gd.enterprisePermissions
const pf4 = !ep4 || ep4.face !== false
this.setData({
siteTitle: gd.siteTitle || '神仙团队AI性格测试',
startButtonEnterprise: maintenanceMode ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始面部测试'),
startButtonEnterprise: (maintenanceMode || !pf4) ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始面部测试'),
aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析',
maintenanceMode
maintenanceMode,
reviewMode: maintenanceMode,
permFace: pf4
})
},
@@ -181,7 +197,8 @@ Page({
// 开始AI面部测试先校验是否已上传简历再跳转相机审核模式下直接跳转 test-select
startAITest() {
if (this.data.maintenanceMode) {
const ep = getApp().globalData.enterprisePermissions
if (this.data.maintenanceMode || (ep && ep.face === false)) {
wx.navigateTo({ url: '/pages/test-select/index' })
return
}

View File

@@ -1,80 +1,80 @@
<!--pages/enterprise/index.wxml - 企业版首页-->
<view class="container" style="padding-top: {{navbarHeight}}px;">
<!-- 自定义导航栏 -->
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
<view class="navbar-content">
<view class="switch-personal-btn" bindtap="switchToPersonal">
<text class="personal-icon">👤</text>
<text class="personal-text">个人版</text>
</view>
<view class="navbar-title">{{siteTitle || '神仙团队性格测试'}}</view>
<view class="navbar-placeholder"></view>
</view>
</view>
<!-- 背景装饰 -->
<view class="bg-decoration bg-top-right"></view>
<view class="bg-decoration bg-bottom-left"></view>
<view class="top-image-section">
<view class="image-container">
<view class="image-wrapper">
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
</view>
<view class="float-tag tag-1" wx:if="{{!reviewMode}}">面相分析</view>
<view class="float-tag tag-2" wx:if="{{!reviewMode}}">骨相分析</view>
<view class="float-tag tag-3">性格测评</view>
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
</view>
</view>
<view class="process-section">
<view class="section-title">测试流程</view>
<view class="process-steps" wx:if="{{!reviewMode}}">
<view class="step-item">
<view class="step-circle active">1</view>
<text class="step-label">STEP1</text>
<text class="step-text">拍摄照片</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view class="step-circle">2</view>
<text class="step-label">STEP2</text>
<text class="step-text">{{aiAnalysisText || '分析'}}</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view class="step-circle">3</view>
<text class="step-label">STEP3</text>
<text class="step-text">生成报告</text>
</view>
</view>
<view class="process-steps" wx:if="{{reviewMode}}">
<view class="step-item">
<view class="step-circle active">1</view>
<text class="step-label">STEP1</text>
<text class="step-text">选择测试</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view class="step-circle">2</view>
<text class="step-label">STEP2</text>
<text class="step-text">回答问题</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view class="step-circle">3</view>
<text class="step-label">STEP3</text>
<text class="step-text">查看结果</text>
</view>
</view>
</view>
<view class="start-button" bindtap="startAITest">
<text class="button-text">{{startButtonEnterprise || '开始性格测试'}}</text>
</view>
<!-- 自定义底部导航 -->
<custom-tab-bar />
</view>
<!--pages/enterprise/index.wxml - 企业版首页-->
<view class="container" style="padding-top: {{navbarHeight}}px;">
<!-- 自定义导航栏 -->
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
<view class="navbar-content">
<view class="switch-personal-btn" bindtap="switchToPersonal">
<text class="personal-icon">👤</text>
<text class="personal-text">个人版</text>
</view>
<view class="navbar-title">{{siteTitle || '神仙团队性格测试'}}</view>
<view class="navbar-placeholder"></view>
</view>
</view>
<!-- 背景装饰 -->
<view class="bg-decoration bg-top-right"></view>
<view class="bg-decoration bg-bottom-left"></view>
<view class="top-image-section">
<view class="image-container">
<view class="image-wrapper">
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
</view>
<view class="float-tag tag-1" wx:if="{{!reviewMode && permFace}}">面相分析</view>
<view class="float-tag tag-2" wx:if="{{!reviewMode && permFace}}">骨相分析</view>
<view class="float-tag tag-3">性格测评</view>
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
</view>
</view>
<view class="process-section">
<view class="section-title">测试流程</view>
<view class="process-steps" wx:if="{{!reviewMode && permFace}}">
<view class="step-item">
<view class="step-circle active">1</view>
<text class="step-label">STEP1</text>
<text class="step-text">拍摄照片</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view class="step-circle">2</view>
<text class="step-label">STEP2</text>
<text class="step-text">{{aiAnalysisText || '分析'}}</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view class="step-circle">3</view>
<text class="step-label">STEP3</text>
<text class="step-text">生成报告</text>
</view>
</view>
<view class="process-steps" wx:if="{{reviewMode || !permFace}}">
<view class="step-item">
<view class="step-circle active">1</view>
<text class="step-label">STEP1</text>
<text class="step-text">选择测试</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view class="step-circle">2</view>
<text class="step-label">STEP2</text>
<text class="step-text">回答问题</text>
</view>
<view class="step-line"></view>
<view class="step-item">
<view class="step-circle">3</view>
<text class="step-label">STEP3</text>
<text class="step-text">查看结果</text>
</view>
</view>
</view>
<view class="start-button" bindtap="startAITest">
<text class="button-text">{{startButtonEnterprise || '开始性格测试'}}</text>
</view>
<!-- 自定义底部导航 -->
<custom-tab-bar />
</view>

View File

@@ -8,9 +8,16 @@ Page({
tabName: '',
list: [],
total: 0,
page: 1,
hasMore: false,
loading: false,
loadingMore: false,
isEnterprise: false,
reviewMode: false
reviewMode: false,
permMbti: true,
permPdp: true,
permDisc: true,
permFace: true
},
_checkIsEnterprise() {
@@ -21,54 +28,111 @@ Page({
return scope === 'enterprise' || !!enterpriseId
},
_syncPermsAndTab() {
const p = app.globalData.enterprisePermissions
const permMbti = !p || p.mbti !== false
const permPdp = !p || p.pdp !== false
const permDisc = !p || p.disc !== false
const permFace = !p || p.face !== false
const reviewMode = !!app.globalData.reviewMode
const names = { all: '', mbti: 'MBTI', pdp: 'PDP', disc: 'DISC', ai: '面相', resume: '简历' }
let { activeTab } = this.data
if (activeTab === 'mbti' && !permMbti) activeTab = 'all'
if (activeTab === 'pdp' && !permPdp) activeTab = 'all'
if (activeTab === 'disc' && !permDisc) activeTab = 'all'
if (activeTab === 'ai' && (!permFace || reviewMode)) activeTab = 'all'
this.setData({
permMbti, permPdp, permDisc, permFace, reviewMode,
activeTab,
tabName: names[activeTab] || ''
})
},
onLoad() {
this.setData({ isEnterprise: this._checkIsEnterprise() })
this._syncPermsAndTab()
this.loadAll()
},
onShow() {
this.setData({ isEnterprise: this._checkIsEnterprise(), reviewMode: !!app.globalData.reviewMode })
this.setData({ isEnterprise: this._checkIsEnterprise() })
this._syncPermsAndTab()
this.loadAll()
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 1 })
const tb = this.getTabBar()
if (typeof tb.updateSelected === 'function') tb.updateSelected()
}
},
// 一次拉取全部历史pageSize=500
/** 首屏/切换 Tab接口默认 pageSize=10更多走 loadMore */
loadAll() {
if (this.data.loading) return
this.setData({ loading: true, list: [] })
this.setData({ loading: true, list: [], page: 1, hasMore: false, loadingMore: false })
this._fetchHistoryPage(1, false)
},
/** 触底加载下一页 */
loadMore() {
if (!this.data.hasMore || this.data.loadingMore || this.data.loading) return
const next = this.data.page + 1
this.setData({ loadingMore: true })
this._fetchHistoryPage(next, true)
},
onReachBottom() {
this.loadMore()
},
_fetchHistoryPage(pageNum, append) {
const token = app.globalData.token || wx.getStorageSync('token')
const apiBase = app.globalData.apiBase
const { activeTab } = this.data
const typeParam = activeTab === 'all' ? '' : `&type=${activeTab}`
if (!token || !apiBase) {
this.setData({ loading: false })
this.setData({ loading: false, loadingMore: false })
this.loadFromStorage()
return
}
wx.request({
url: `${apiBase}/api/test/history?page=1&pageSize=500${typeParam}&scope=all`,
url: `${apiBase}/api/test/history?page=${pageNum}&pageSize=10${typeParam}&scope=all`,
method: 'GET',
header: { Authorization: `Bearer ${token}` },
success: (res) => {
if (res.statusCode === 200 && res.data && res.data.data) {
const payload = res.data.data
const rawList = Array.isArray(payload) ? payload : (payload.list || [])
const total = Array.isArray(payload) ? rawList.length : (payload.total || 0)
const rawList = payload.list || []
const formatted = this.formatList(rawList)
this.setData({ list: formatted, total, loading: false })
const total = typeof payload.total === 'number' ? payload.total : formatted.length
const hasMore = !!payload.hasMore
if (append) {
this.setData({
list: this.data.list.concat(formatted),
total,
hasMore,
page: pageNum,
loadingMore: false,
loading: false
})
} else {
this.setData({
list: formatted,
total,
hasMore,
page: pageNum,
loading: false,
loadingMore: false
})
}
} else {
this.setData({ loading: false })
this.loadFromStorage()
this.setData({ loading: false, loadingMore: false })
if (!append) this.loadFromStorage()
}
},
fail: () => {
this.setData({ loading: false })
this.loadFromStorage()
this.setData({ loading: false, loadingMore: false })
if (!append) this.loadFromStorage()
}
})
},
@@ -79,7 +143,8 @@ Page({
return rawList.map((item, idx) => {
if (item.typeName) {
return { ...item, enterpriseName: item.enterpriseName || '' }
const t = String(item.testType || item.type || 'mbti').toLowerCase()
return { ...item, type: t, enterpriseName: item.enterpriseName || '' }
}
const testType = (item.testType || item.type || 'mbti').toLowerCase()
const ts = item.createdAt || item.testTime || item.timestamp
@@ -97,16 +162,17 @@ Page({
// 本地缓存回退
loadFromStorage() {
const { permMbti, permPdp, permDisc, permFace, reviewMode } = this.data
const mbtiResult = wx.getStorageSync('mbtiResult')
const discResult = wx.getStorageSync('discResult')
const pdpResult = wx.getStorageSync('pdpResult')
const aiResult = wx.getStorageSync('aiResult')
const list = []
if (mbtiResult) list.push({ type: 'mbti', key: 'mbti', emoji: '🧠', typeName: 'MBTI性格测试', resultText: mbtiResult.mbtiType || '未知', testTime: this.formatTime(mbtiResult.timestamp), data: mbtiResult })
if (pdpResult) list.push({ type: 'pdp', key: 'pdp', emoji: pdpResult.description?.emoji || '🦁', typeName: 'PDP行为偏好测试', resultText: pdpResult.description?.type || '未知', testTime: this.formatTime(pdpResult.timestamp || pdpResult.completedAt), data: pdpResult })
if (discResult) list.push({ type: 'disc', key: 'disc', emoji: '📊', typeName: 'DISC性格测试', resultText: (discResult.dominantType || '未知') + '型', testTime: this.formatTime(discResult.timestamp || discResult.completedAt), data: discResult })
if (aiResult && !app.globalData.reviewMode) list.push({ type: 'ai', key: 'ai', emoji: '👁️', typeName: '面相分析', resultText: aiResult.mbti || '未知', testTime: this.formatTime(aiResult.timestamp || aiResult.completedAt), data: aiResult })
this.setData({ list, total: list.length, loading: false })
if (mbtiResult && permMbti) list.push({ type: 'mbti', key: 'mbti', emoji: '🧠', typeName: 'MBTI性格测试', resultText: mbtiResult.mbtiType || '未知', testTime: this.formatTime(mbtiResult.timestamp), data: mbtiResult })
if (pdpResult && permPdp) list.push({ type: 'pdp', key: 'pdp', emoji: pdpResult.description?.emoji || '🦁', typeName: 'PDP行为偏好测试', resultText: pdpResult.description?.type || '未知', testTime: this.formatTime(pdpResult.timestamp || pdpResult.completedAt), data: pdpResult })
if (discResult && permDisc) list.push({ type: 'disc', key: 'disc', emoji: '📊', typeName: 'DISC性格测试', resultText: (discResult.dominantType || '未知') + '型', testTime: this.formatTime(discResult.timestamp || discResult.completedAt), data: discResult })
if (aiResult && permFace && !reviewMode) list.push({ type: 'ai', key: 'ai', emoji: '👁️', typeName: '面相分析', resultText: aiResult.mbti || '未知', testTime: this.formatTime(aiResult.timestamp || aiResult.completedAt), data: aiResult })
this.setData({ list, total: list.length, page: 1, hasMore: false, loading: false, loadingMore: false })
},
changeTab(e) {

View File

@@ -7,21 +7,18 @@
<view class="tab-item {{activeTab === 'all' ? 'active' : ''}}" bindtap="changeTab" data-tab="all">
<text class="tab-text">全部</text>
</view>
<view class="tab-item {{activeTab === 'mbti' ? 'active' : ''}}" bindtap="changeTab" data-tab="mbti">
<view wx:if="{{permMbti}}" class="tab-item {{activeTab === 'mbti' ? 'active' : ''}}" bindtap="changeTab" data-tab="mbti">
<text class="tab-text">MBTI</text>
</view>
<view class="tab-item {{activeTab === 'pdp' ? 'active' : ''}}" bindtap="changeTab" data-tab="pdp">
<view wx:if="{{permPdp}}" class="tab-item {{activeTab === 'pdp' ? 'active' : ''}}" bindtap="changeTab" data-tab="pdp">
<text class="tab-text">PDP</text>
</view>
<view class="tab-item {{activeTab === 'disc' ? 'active' : ''}}" bindtap="changeTab" data-tab="disc">
<view wx:if="{{permDisc}}" class="tab-item {{activeTab === 'disc' ? 'active' : ''}}" bindtap="changeTab" data-tab="disc">
<text class="tab-text">DISC</text>
</view>
<view wx:if="{{!reviewMode}}" class="tab-item {{activeTab === 'ai' ? 'active' : ''}}" bindtap="changeTab" data-tab="ai">
<view wx:if="{{!reviewMode && permFace}}" class="tab-item {{activeTab === 'ai' ? 'active' : ''}}" bindtap="changeTab" data-tab="ai">
<text class="tab-text">面相</text>
</view>
<view wx:if="{{isEnterprise}}" class="tab-item {{activeTab === 'resume' ? 'active' : ''}}" bindtap="changeTab" data-tab="resume">
<text class="tab-text">简历</text>
</view>
</view>
</scroll-view>
</view>
@@ -57,11 +54,12 @@
</view>
</view>
<!-- 加载中 -->
<view class="load-more-row" wx:if="{{loading}}">
<text class="load-more-text">加载中...</text>
<view class="load-more-row" wx:if="{{loadingMore}}">
<text class="load-more-text">加载更多...</text>
</view>
<view wx:elif="{{hasMore && list.length > 0}}" class="load-more-row">
<text class="load-more-text">上拉或继续滑动加载更多</text>
</view>
<view wx:elif="{{list.length > 0}}" class="load-more-row">
<text class="load-more-text no-more">共 {{total}} 条</text>
</view>

View File

@@ -72,16 +72,22 @@ Page({
onShow() {
const rm = this._audit()
const faceOff = !!(app.globalData.enterprisePermissions && app.globalData.enterprisePermissions.face === false)
this.setData({ reviewMode: rm })
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 1, reviewMode: rm })
const tb = this.getTabBar()
if (typeof tb.updateSelected === 'function') tb.updateSelected()
}
// 审核模式:只展示本页引导,不再强跳 navigateTo失败时曾导致白屏且无拍摄区
if (rm) {
return
}
if (faceOff) {
wx.navigateTo({ url: '/pages/test-select/index' })
return
}
if (!ensureProfileCompleteAndRedirect()) {
return

View File

@@ -10,7 +10,8 @@ Page({
siteTitle: '神仙团队性格测试',
startButtonText: '开始性格测试',
aiAnalysisText: '分析',
reviewMode: true
reviewMode: true,
permFace: true
},
onLoad(options) {
@@ -25,6 +26,8 @@ Page({
const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {}
const gd = getApp().globalData
const rm0 = !!(gd.reviewMode || gd.maintenanceMode)
const ep = gd.enterprisePermissions
const pf = !ep || ep.face !== false
this.setData({
statusBarHeight: statusBarHeightRpx,
navbarHeight: navbarHeightRpx,
@@ -32,7 +35,8 @@ Page({
siteTitle: rm0 ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
startButtonText: rm0 ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '拍摄'),
aiAnalysisText: rm0 ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
reviewMode: rm0
reviewMode: rm0,
permFace: pf
})
// 预加载站点名称与文案配置
app.getRuntimeConfig().then((cfg) => {
@@ -53,7 +57,8 @@ Page({
aiAnalysisText: rm ? '分析' : (cfg.textConfig.aiAnalysisText || '分析')
})
}
this.setData({ reviewMode: rm })
const ep2 = getApp().globalData.enterprisePermissions
this.setData({ reviewMode: rm, permFace: !ep2 || ep2.face !== false })
try {
const tb = typeof this.getTabBar === 'function' ? this.getTabBar() : null
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
@@ -97,9 +102,9 @@ Page({
onShow() {
const gd0 = getApp().globalData || {}
const audit = !!(gd0.reviewMode || gd0.maintenanceMode)
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 0, reviewMode: audit })
const tb = this.getTabBar()
if (typeof tb.updateSelected === 'function') tb.updateSelected()
}
// 个人版首页:固定 scope=personal并清除企业来源上下文
try {
@@ -108,11 +113,13 @@ Page({
} catch (e) {}
const g = getApp().globalData
const rm = !!(g.reviewMode || g.maintenanceMode)
const ep3 = g.enterprisePermissions
this.setData({
siteTitle: rm ? (g.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (g.siteTitle || '神仙团队性格测试'),
startButtonText: rm ? '开始性格测试' : ((g.textConfig && g.textConfig.startButtonText) || '拍摄'),
startButtonText: (rm || (ep3 && ep3.face === false)) ? '开始性格测试' : ((g.textConfig && g.textConfig.startButtonText) || '拍摄'),
aiAnalysisText: rm ? '分析' : ((g.textConfig && g.textConfig.aiAnalysisText) || '分析'),
reviewMode: rm
reviewMode: rm,
permFace: !ep3 || ep3.face !== false
})
const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {}
this.setData({ showEnterpriseEntry: userInfo.hasEnterprise === true })
@@ -138,7 +145,9 @@ Page({
startCamera() {
try { getApp().globalData.appScope = 'personal' } catch (e) {}
const gd = getApp().globalData
if (gd.reviewMode || gd.maintenanceMode) {
const ep = gd.enterprisePermissions
const faceOff = ep && ep.face === false
if (gd.reviewMode || gd.maintenanceMode || faceOff) {
wx.navigateTo({ url: '/pages/test-select/index' })
return
}

View File

@@ -22,8 +22,8 @@
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
</view>
<!-- 审核模式:只显示性格测评标签 -->
<view class="float-tag tag-1" wx:if="{{!reviewMode}}">面相分析</view>
<view class="float-tag tag-2" wx:if="{{!reviewMode}}">骨相分析</view>
<view class="float-tag tag-1" wx:if="{{!reviewMode && permFace}}">面相分析</view>
<view class="float-tag tag-2" wx:if="{{!reviewMode && permFace}}">骨相分析</view>
<view class="float-tag tag-3">性格测评</view>
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
@@ -32,7 +32,7 @@
<view class="process-section">
<view class="section-title">测试流程</view>
<view class="process-steps" wx:if="{{!reviewMode}}">
<view class="process-steps" wx:if="{{!reviewMode && permFace}}">
<view class="step-item">
<view class="step-circle active">1</view>
<text class="step-label">STEP1</text>
@@ -51,8 +51,8 @@
<text class="step-text">生成报告</text>
</view>
</view>
<!-- 审核模式:纯问卷流程 -->
<view class="process-steps" wx:if="{{reviewMode}}">
<!-- 审核模式或 face 权限关闭:纯问卷流程 -->
<view class="process-steps" wx:if="{{reviewMode || !permFace}}">
<view class="step-item">
<view class="step-circle active">1</view>
<text class="step-label">STEP1</text>

View File

@@ -35,7 +35,8 @@ Page({
onShow() {
const audit = !!(app.globalData.reviewMode || app.globalData.maintenanceMode)
if (audit) {
const faceOff = !!(app.globalData.enterprisePermissions && app.globalData.enterprisePermissions.face === false)
if (audit || faceOff) {
wx.navigateTo({ url: '/pages/test-select/index' })
return
}
@@ -43,7 +44,8 @@ Page({
return
}
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 1, reviewMode: audit })
const tb = this.getTabBar()
if (typeof tb.updateSelected === 'function') tb.updateSelected()
}
this.setData({ needPhoneAuth: !hasPhone() })
const tc = app.globalData.textConfig

View File

@@ -40,25 +40,65 @@ Page({
promoTotalEarned: '0.00',
promoWithdrawable: '0.00',
/** 是否有企业权限(绑定企业):有则显示「我的简历」 */
hasEnterprise: false
hasEnterprise: false,
// 企业功能权限:默认全开(个人版 / 未配置时)
permFace: true,
permMbti: true,
permPdp: true,
permDisc: true,
permDistribution: true,
/** 最新测试横滑区:仅有已出结果的卡片时才显示,避免禁权或未测评占位 */
showLatestTestCards: false,
/** 用户卡片下性格标签:在「当前权限下无任何问卷结果」时显示灰色提示 */
showEmptyPersonalityTags: true
},
_computeShowLatestTestCards(d) {
const rm = !!(d.reviewMode)
if (d.permMbti && d.mbtiType) return true
if (d.permPdp && d.pdpType) return true
if (d.permDisc && d.discType) return true
if (!rm && d.permFace && (d.gallupPreview || d.aiType)) return true
return false
},
_computeShowEmptyPersonalityTags(d) {
return !((d.mbtiType && d.permMbti) || (d.discType && d.permDisc) || (d.pdpType && d.permPdp))
},
onLoad() {
// 仅在 onLoad 执行一次onShow 里的 runLoginThenLoad 会导致重复请求
this.runLoginThenLoad()
},
_syncPerms(overrides = {}) {
const p = app.globalData.enterprisePermissions
const next = {
permFace: !p || p.face !== false,
permMbti: !p || p.mbti !== false,
permPdp: !p || p.pdp !== false,
permDisc: !p || p.disc !== false,
permDistribution: !p || p.distribution !== false,
...overrides
}
const d = { ...this.data, ...next }
this.setData({
...next,
showLatestTestCards: this._computeShowLatestTestCards(d),
showEmptyPersonalityTags: this._computeShowEmptyPersonalityTags(d)
})
},
onShow() {
// 同步审核模式(与 runtime maintenanceMode / reviewMode 一致)
const gd = app.globalData
this.setData({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
this._syncPerms({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
// 如果是从其他页面返回,且已经登录,则只刷新数据而不重新执行登录流程
if (this.data.hasLogin) {
this.loadData()
}
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
const audit = !!(gd.reviewMode || gd.maintenanceMode)
this.getTabBar().setData({ selected: 2, reviewMode: audit })
const tb = this.getTabBar()
if (typeof tb.updateSelected === 'function') tb.updateSelected()
}
},
@@ -108,6 +148,9 @@ Page({
},
loadData() {
const gd = app.globalData
this._syncPerms({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo')
const token = app.globalData.token || wx.getStorageSync('token')
@@ -164,7 +207,7 @@ Page({
const discType = r.disc ? r.disc.resultText.replace(/型$/, '') : ''
const gallupPreview = (r.ai && r.ai.gallupPreview) ? String(r.ai.gallupPreview) : ''
this.setData({
const patch = {
testCount: totalCount,
hasResults: !!(r.mbti || r.disc || r.pdp || r.ai),
mbtiType: r.mbti ? r.mbti.resultText : '',
@@ -180,6 +223,12 @@ Page({
discResultId: r.disc ? r.disc.id : null,
pdpResultId: r.pdp ? r.pdp.id : null,
aiResultId: r.ai ? r.ai.id : null,
}
const d = { ...this.data, ...patch }
this.setData({
...patch,
showLatestTestCards: this._computeShowLatestTestCards(d),
showEmptyPersonalityTags: this._computeShowEmptyPersonalityTags(d)
})
},
fail: () => {
@@ -234,7 +283,7 @@ Page({
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
this.setData({
const patch = {
testCount,
hasResults: testCount > 0,
mbtiType: mbtiResult ? getTypeOnly(mbtiResult, 'mbti') : '',
@@ -250,6 +299,12 @@ Page({
discResultId: null,
pdpResultId: null,
aiResultId: null,
}
const d = { ...this.data, ...patch }
this.setData({
...patch,
showLatestTestCards: this._computeShowLatestTestCards(d),
showEmptyPersonalityTags: this._computeShowEmptyPersonalityTags(d)
})
},

View File

@@ -49,16 +49,16 @@
<!-- 类型标签 -->
<scroll-view scroll-x class="tags-scroll">
<view class="tags-row">
<view class="tag tag-purple" wx:if="{{mbtiType}}">
<view class="tag tag-purple" wx:if="{{mbtiType && permMbti}}">
<text class="tag-text">{{mbtiType}}</text>
</view>
<view class="tag tag-blue" wx:if="{{discType}}">
<view class="tag tag-blue" wx:if="{{discType && permDisc}}">
<text class="tag-text">{{discType}}型</text>
</view>
<view class="tag tag-orange" wx:if="{{pdpType}}">
<view class="tag tag-orange" wx:if="{{pdpType && permPdp}}">
<text class="tag-text">{{pdpType}}</text>
</view>
<view class="tag tag-gray" wx:if="{{!mbtiType && !discType && !pdpType}}">
<view class="tag tag-gray" wx:if="{{showEmptyPersonalityTags}}">
<text class="tag-text">暂无测试记录</text>
</view>
</view>
@@ -79,57 +79,57 @@
<text class="depth-header-chevron"></text>
</view>
</view>
<scroll-view scroll-x class="cards-scroll cards-scroll--in-card" enhanced show-scrollbar="{{false}}">
<scroll-view wx:if="{{showLatestTestCards}}" scroll-x class="cards-scroll cards-scroll--in-card" enhanced show-scrollbar="{{false}}">
<view class="cards-row cards-row--in-card">
<view class="result-card card-purple {{mbtiType ? '' : 'result-card--placeholder'}}" bindtap="viewMBTI">
<view class="result-card card-purple" bindtap="viewMBTI" wx:if="{{permMbti && mbtiType}}">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-purple">
<text class="card-icon">🧠</text>
</view>
<text class="card-label">MBTI性格</text>
<text class="card-value">{{mbtiType || '未测评'}}</text>
<text class="card-time">{{mbtiType ? mbtiTime : '点击进入问卷'}}</text>
<text class="card-value">{{mbtiType}}</text>
<text class="card-time">{{mbtiTime}}</text>
</view>
<view class="result-card card-orange {{pdpType ? '' : 'result-card--placeholder'}}" bindtap="viewPDP">
<view class="result-card card-orange" bindtap="viewPDP" wx:if="{{permPdp && pdpType}}">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-orange">
<text class="card-icon">🦁</text>
</view>
<text class="card-label">PDP行为</text>
<text class="card-value">{{pdpType || '未测评'}}</text>
<text class="card-time">{{pdpType ? pdpTime : '点击进入问卷'}}</text>
<text class="card-value">{{pdpType}}</text>
<text class="card-time">{{pdpTime}}</text>
</view>
<view class="result-card card-blue {{discType ? '' : 'result-card--placeholder'}}" bindtap="viewDISC">
<view class="result-card card-blue" bindtap="viewDISC" wx:if="{{permDisc && discType}}">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-blue">
<text class="card-icon">📊</text>
</view>
<text class="card-label">DISC测评</text>
<text class="card-value">{{discType ? discType + '型' : '未测评'}}</text>
<text class="card-time">{{discType ? discTime : '点击进入问卷'}}</text>
<text class="card-value">{{discType + '型'}}</text>
<text class="card-time">{{discTime}}</text>
</view>
<view class="result-card card-teal {{(gallupPreview || aiType) ? '' : 'result-card--placeholder'}}" bindtap="viewGallup" wx:if="{{!reviewMode}}">
<view class="result-card card-teal" bindtap="viewGallup" wx:if="{{!reviewMode && permFace && (gallupPreview || aiType)}}">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-teal">
<text class="card-icon">⭐</text>
</view>
<text class="card-label">盖洛普优势</text>
<text class="card-value card-value--small">{{gallupPreview || (aiType ? '见面相报告' : '未测评')}}</text>
<text class="card-time">{{gallupPreview ? aiTime : (aiType ? '已拍面相可查看' : '先完成面相拍摄')}}</text>
<text class="card-value card-value--small">{{gallupPreview || '见面相报告'}}</text>
<text class="card-time">{{aiTime}}</text>
</view>
<view class="result-card card-rose {{aiType ? '' : 'result-card--placeholder'}}" bindtap="viewAI" wx:if="{{!reviewMode}}">
<view class="result-card card-rose" bindtap="viewAI" wx:if="{{!reviewMode && permFace && aiType}}">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-rose">
<text class="card-icon">👁️</text>
</view>
<text class="card-label">面相分析</text>
<text class="card-value">{{aiType || '未测评'}}</text>
<text class="card-time">{{aiType ? aiTime : '去拍摄分析'}}</text>
<text class="card-value">{{aiType}}</text>
<text class="card-time">{{aiTime}}</text>
</view>
</view>
</scroll-view>
<view class="depth-empty-hint depth-empty-hint--compact">
<text>灰色为未完成项,点击即可进入测评;右上方可查看全部历史。</text>
<view wx:if="{{showLatestTestCards}}" class="depth-empty-hint depth-empty-hint--compact">
<text>点击卡片查看详情;右上方可查看全部测试记录。</text>
</view>
<view class="depth-inner-divider"></view>
<view class="menu-item menu-item--flat" bindtap="goToTestSelect">
@@ -179,7 +179,7 @@
</view>
<!-- 推广中心(与管理端开关、标题配置一致;卡片样式与其他区块统一) -->
<view class="section px-section" wx:if="{{hasLogin && promoDistributionEnabled && !reviewMode}}">
<view class="section px-section" wx:if="{{hasLogin && promoDistributionEnabled && !reviewMode && permDistribution}}">
<view class="promo-card" bindtap="goToPromo">
<view class="promo-header">
<view class="promo-title-wrap">

View File

@@ -1,21 +1,41 @@
// pages/test-select/index.js - 人脸测试后的详情性格测试入口MBTI / PDP / DISC
Page({
data: {},
onLoad() {},
// 进入 MBTI 测试
goMBTI() {
wx.navigateTo({ url: '/pages/test/mbti' })
},
// 进入 PDP 测试
goPDP() {
wx.navigateTo({ url: '/pages/test/pdp' })
},
// 进入 DISC 测试
goDISC() {
wx.navigateTo({ url: '/pages/test/disc' })
}
})
// pages/test-select/index.js
const app = getApp()
Page({
data: {
permFace: true,
permMbti: true,
permPdp: true,
permDisc: true
},
onLoad() {
this._syncPerms()
},
onShow() {
this._syncPerms()
},
_syncPerms() {
const p = app.globalData.enterprisePermissions
this.setData({
permFace: !p || p.face !== false,
permMbti: !p || p.mbti !== false,
permPdp: !p || p.pdp !== false,
permDisc: !p || p.disc !== false
})
},
goMBTI() {
wx.navigateTo({ url: '/pages/test/mbti' })
},
goPDP() {
wx.navigateTo({ url: '/pages/test/pdp' })
},
goDISC() {
wx.navigateTo({ url: '/pages/test/disc' })
}
})

View File

@@ -1,40 +1,40 @@
<!--pages/test-select/index.wxml - 详细性格测试入口MBTI / PDP / DISC-->
<view class="container">
<view class="card intro-card">
<text class="intro-title">选择一项详细性格测试</text>
<text class="intro-desc">完成问卷即可获得对应维度的专业性格报告</text>
</view>
<view class="card entry-card" bindtap="goMBTI">
<view class="entry-icon-wrap mbti">
<text class="entry-emoji">🧠</text>
</view>
<view class="entry-content">
<text class="entry-name">MBTI 性格测试</text>
<text class="entry-brief">16型人格 · 发现你的认知与决策风格</text>
</view>
<text class="entry-arrow">→</text>
</view>
<view class="card entry-card" bindtap="goPDP">
<view class="entry-icon-wrap pdp">
<text class="entry-emoji">🦁</text>
</view>
<view class="entry-content">
<text class="entry-name">PDP 行为偏好测试</text>
<text class="entry-brief">老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格</text>
</view>
<text class="entry-arrow">→</text>
</view>
<view class="card entry-card" bindtap="goDISC">
<view class="entry-icon-wrap disc">
<text class="entry-emoji">📊</text>
</view>
<view class="entry-content">
<text class="entry-name">DISC 性格测试</text>
<text class="entry-brief">D/I/S/C 四维 · 沟通与行为倾向</text>
</view>
<text class="entry-arrow">→</text>
</view>
</view>
<!--pages/test-select/index.wxml - 详细性格测试入口MBTI / PDP / DISC-->
<view class="container">
<view class="card intro-card">
<text class="intro-title">选择一项详细性格测试</text>
<text class="intro-desc">完成问卷即可获得对应维度的专业性格报告</text>
</view>
<view class="card entry-card" bindtap="goMBTI" wx:if="{{permMbti}}">
<view class="entry-icon-wrap mbti">
<text class="entry-emoji">🧠</text>
</view>
<view class="entry-content">
<text class="entry-name">MBTI 性格测试</text>
<text class="entry-brief">16型人格 · 发现你的认知与决策风格</text>
</view>
<text class="entry-arrow">→</text>
</view>
<view class="card entry-card" bindtap="goPDP" wx:if="{{permPdp}}">
<view class="entry-icon-wrap pdp">
<text class="entry-emoji">🦁</text>
</view>
<view class="entry-content">
<text class="entry-name">PDP 行为偏好测试</text>
<text class="entry-brief">老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格</text>
</view>
<text class="entry-arrow">→</text>
</view>
<view class="card entry-card" bindtap="goDISC" wx:if="{{permDisc}}">
<view class="entry-icon-wrap disc">
<text class="entry-emoji">📊</text>
</view>
<view class="entry-content">
<text class="entry-name">DISC 性格测试</text>
<text class="entry-brief">D/I/S/C 四维 · 沟通与行为倾向</text>
</view>
<text class="entry-arrow">→</text>
</view>
</view>