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

@@ -47,8 +47,8 @@ App({
// 超管配置的默认企业 ID无 scene/eid 等入口参数时回落)
defaultEnterpriseId: null,
// API基础地址开发时用本地生产环境替换为实际域名
apiBase: 'https://mbtiapi.quwanzhi.com',
//apiBase: 'http://mbti.com',
//apiBase: 'https://mbtiapi.quwanzhi.com',
apiBase: 'http://mbti.com',
// VIP信息
vipInfo: null,
// 测试次数
@@ -59,7 +59,9 @@ App({
mbtiResult: null,
discResult: null,
pdpResult: null,
aiResult: null
aiResult: null,
// 企业功能权限超管在企业管理中配置null=无限制(个人版),对象时按 key 控制
enterprisePermissions: null
},
onLaunch(launchOptions) {
@@ -69,30 +71,85 @@ App({
// 加载本地存储的数据
this.loadStoredData()
// 静默登录获取openId
this.silentLogin()
// 上报应用启动事件
try {
const analytics = require('./utils/analytics.js')
analytics.reportAppLaunch(launchOptions)
} catch (e) {}
// 预加载站点/小程序名称、维护模式与面相审核模式camera/首页文案)
this.getRuntimeConfig().then((cfg) => {
if (cfg) {
if (cfg.siteTitle) this.globalData.siteTitle = cfg.siteTitle
if (cfg.maintenanceMode !== undefined) this.globalData.maintenanceMode = !!cfg.maintenanceMode
if (typeof cfg.reviewMode === 'boolean') {
this.globalData.reviewMode = cfg.reviewMode
// 必须先完成静默登录再拉 runtime否则无 token 时 enterprisePermissions 永远 null清除缓存后必现「要二次刷新」
this.silentLogin()
.catch(() => {})
.then(() => this.getRuntimeConfig())
.then(() => {
this._afterRuntimeSynced()
})
.catch(() => {})
},
/**
* runtime 写入 globalData 后,同步当前栈顶页面与 tabBar避免首屏用旧 null 权限)
*/
_afterRuntimeSynced() {
try {
const pages = getCurrentPages()
if (!pages || pages.length === 0) return
const top = pages[pages.length - 1]
if (!top) return
const gd = this.globalData
const ep = gd.enterprisePermissions
const audit = !!(gd.reviewMode || gd.maintenanceMode)
const permFace = !ep || ep.face !== false
if (typeof top.getTabBar === 'function') {
const tb = top.getTabBar()
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
}
const route = top.route || ''
if (route === 'pages/index/index' && typeof top.setData === 'function') {
top.setData({
reviewMode: audit,
permFace,
siteTitle: audit ? String(gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
startButtonText: (audit || (ep && ep.face === false)) ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '拍摄'),
aiAnalysisText: audit ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析')
})
} else if (route === 'pages/profile/index' && typeof top.setData === 'function') {
if (typeof top._syncPerms === 'function') top._syncPerms.call(top)
top.setData({ reviewMode: audit })
if (typeof top.getTabBar === 'function') {
const tb = top.getTabBar()
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
}
if (cfg.defaultEnterpriseId != null && Number(cfg.defaultEnterpriseId) > 0) {
this.globalData.defaultEnterpriseId = Number(cfg.defaultEnterpriseId)
} else {
this.globalData.defaultEnterpriseId = null
} else if (route === 'pages/enterprise/index' && typeof top.setData === 'function') {
const maintenanceMode = audit
const pf = permFace
top.setData({
maintenanceMode,
reviewMode: maintenanceMode,
permFace: pf,
siteTitle: gd.siteTitle || '神仙团队AI性格测试',
startButtonEnterprise: (maintenanceMode || !pf) ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始面部测试'),
aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析'
})
if (typeof top.getTabBar === 'function') {
const tb = top.getTabBar()
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
}
} else if (route === 'pages/index/camera' && typeof top.setData === 'function') {
top.setData({ reviewMode: audit })
if (ep && ep.face === false) {
wx.navigateTo({ url: '/pages/test-select/index' })
}
if (typeof top.getTabBar === 'function') {
const tb = top.getTabBar()
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
}
}
}).catch(() => {})
} catch (e) {
console.error('_afterRuntimeSynced', e)
}
},
onShow() {
@@ -386,6 +443,7 @@ App({
* @returns {Promise<{pricingType, pricing, aiProviderId, aiProviderName}>}
*/
getRuntimeConfig() {
const reqId = (this._runtimeReqSeq = (this._runtimeReqSeq || 0) + 1)
return new Promise((resolve, reject) => {
const scope = this.globalData.appScope || 'personal'
const base = this.globalData.apiBase.replace(/\/$/, '')
@@ -396,6 +454,10 @@ App({
method: 'GET',
header: token ? { Authorization: 'Bearer ' + token } : {},
success: (res) => {
if (reqId !== this._runtimeReqSeq) {
resolve(null)
return
}
if (res.statusCode === 200 && res.data && res.data.code === 200) {
const data = res.data.data || {}
if (data.siteTitle) this.globalData.siteTitle = data.siteTitle
@@ -411,6 +473,11 @@ App({
} else {
this.globalData.defaultEnterpriseId = null
}
if (data.enterprisePermissions && typeof data.enterprisePermissions === 'object') {
this.globalData.enterprisePermissions = data.enterprisePermissions
} else {
this.globalData.enterprisePermissions = null
}
resolve(data)
} else {
reject(new Error(res.data && res.data.message ? res.data.message : '获取配置失败'))

View File

@@ -2,8 +2,10 @@
Component({
data: {
selected: 0,
/** 审核模式:为 true 时隐藏中间「拍摄」入口(与 globalData.reviewMode / maintenanceMode 一致) */
/** 审核模式 */
reviewMode: false,
/** 为 true 时隐藏中间「拍摄」:审核模式 或 企业权限关闭 face */
hideMiddleFab: false,
list: [
{ pagePath: '/pages/index/index', text: '首页', textKey: 'home', icon: 'home' },
{ pagePath: '/pages/index/camera', text: '拍摄', textKey: 'camera', icon: 'camera' },
@@ -30,18 +32,20 @@ Component({
const url = currentPage.route
const gd = getApp().globalData || {}
const reviewMode = !!(gd.reviewMode || gd.maintenanceMode)
const ep = gd.enterprisePermissions
const hideMiddleFab = reviewMode || !!(ep && ep.face === false)
let selected = 0
if (url === 'pages/index/index' || url === 'pages/enterprise/index') {
selected = 0
} else if (url === 'pages/index/camera') {
selected = 1
selected = hideMiddleFab ? 0 : 1
} else if (url === 'pages/profile/index') {
selected = 2
}
this.setData({ selected, reviewMode })
this.setData({ selected, reviewMode, hideMiddleFab })
} catch (error) {
console.error('updateSelected error:', error)
this.setData({ selected: 0, reviewMode: false })
this.setData({ selected: 0, reviewMode: false, hideMiddleFab: false })
}
},
switchTab(e) {
@@ -49,7 +53,8 @@ Component({
let url = e.currentTarget.dataset.path
const gd = getApp().globalData || {}
if (index === 1 && !!(gd.reviewMode || gd.maintenanceMode)) {
const faceOff = !!(gd.enterprisePermissions && gd.enterprisePermissions.face === false)
if (index === 1 && (!!(gd.reviewMode || gd.maintenanceMode) || faceOff)) {
return
}

View File

@@ -1,7 +1,7 @@
<!-- 自定义 tabBar顶部分割线 + 三栏;中间单独一列避免浮钮与左右叠层 -->
<view class="tab-bar">
<view class="tab-bar-line"></view>
<view class="tab-bar-inner {{reviewMode ? 'tab-bar-inner--audit' : ''}}">
<view class="tab-bar-inner {{hideMiddleFab ? 'tab-bar-inner--audit' : ''}}">
<view
class="tab-item {{selected === 0 ? 'active' : ''}}"
data-index="0"
@@ -13,7 +13,7 @@
</view>
<!-- 中间列:非审核模式显示「拍摄」 -->
<view class="tab-slot-middle" wx:if="{{!reviewMode}}">
<view class="tab-slot-middle" wx:if="{{!hideMiddleFab}}">
<view
class="middle-fab {{selected === 1 ? 'active' : ''}}"
data-index="1"

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>