feat: 企业权限上限与迁移脚本;管理端/超管概览与财务;双端小程序 tab/订单/历史/测评选择等同步
Made-with: Cursor
This commit is contained in:
@@ -2,9 +2,7 @@
|
|||||||
<div class="dashboard-viewport" v-loading="loading">
|
<div class="dashboard-viewport" v-loading="loading">
|
||||||
<header class="dash-head">
|
<header class="dash-head">
|
||||||
<h1 class="dash-title">数据概览</h1>
|
<h1 class="dash-title">数据概览</h1>
|
||||||
<p class="dash-tagline">
|
<p class="dash-tagline"></p>
|
||||||
企业侧测评闭环:小程序面相与 MBTI / DISC / PDP 答题 → MySQL 落库 → 本页看用户与测试趋势、拉新邀请码(全链路见开发文档《小程序全链路功能与接口》)。
|
|
||||||
</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="dash-kpis">
|
<div class="dash-kpis">
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="finance-page" :class="{ 'is-embedded': embedded }">
|
<div class="finance-page" :class="{ 'is-embedded': embedded }">
|
||||||
|
<!-- 嵌入系统设置「企业余额」时单独展示操作区(独立页仍有完整 page-header) -->
|
||||||
|
<div v-if="embedded" class="embedded-toolbar">
|
||||||
|
<div class="embedded-toolbar-actions">
|
||||||
|
<el-button @click="loadAll" :loading="loading">刷新</el-button>
|
||||||
|
<el-button type="primary" color="#7c3aed" @click="openRechargeDialog">生成充值码</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="!embedded" class="page-header">
|
<div v-if="!embedded" class="page-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>企业余额</h2>
|
<h2>企业余额</h2>
|
||||||
@@ -383,4 +391,17 @@ onMounted(() => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.embedded-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-toolbar-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -22,6 +22,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-content-card" :class="{ 'flat-embed': activeTab === 'finance' }">
|
<div class="tab-content-card" :class="{ 'flat-embed': activeTab === 'finance' }">
|
||||||
|
<div v-if="activeTab === 'features'" class="tab-content" v-loading="permLoading">
|
||||||
|
<div class="content-header">
|
||||||
|
<h3>终端功能开关</h3>
|
||||||
|
<p class="content-description">
|
||||||
|
仅可在超管为贵司授权的范围内调整。若超管未开放某项(如人脸分析),此处不显示对应开关。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-section">
|
||||||
|
<template v-if="visibleAdminPermItems.length === 0">
|
||||||
|
<p class="hint-muted">当前无超管授权项可配置,请联系平台管理员。</p>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="perm-admin-row" v-for="p in visibleAdminPermItems" :key="p.key">
|
||||||
|
<span class="perm-admin-label">{{ p.label }}</span>
|
||||||
|
<el-switch v-model="adminPerms[p.key]" />
|
||||||
|
</div>
|
||||||
|
<div class="save-actions">
|
||||||
|
<el-button type="primary" color="#7c3aed" class="save-btn" @click="saveAdminPermissions" :loading="permSaving">
|
||||||
|
保存功能开关
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="activeTab === 'account'" class="tab-content">
|
<div v-if="activeTab === 'account'" class="tab-content">
|
||||||
<div class="content-header">
|
<div class="content-header">
|
||||||
<h3>管理员账号设置</h3>
|
<h3>管理员账号设置</h3>
|
||||||
@@ -82,14 +107,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted, watch } from 'vue'
|
import { ref, reactive, onMounted, watch, computed } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { DocumentCopy } from '@element-plus/icons-vue'
|
import { DocumentCopy } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { request } from '@/utils/request'
|
import { request } from '@/utils/request'
|
||||||
|
import { getAdminRole } from '@/utils/authStorage'
|
||||||
import Finance from './Finance.vue'
|
import Finance from './Finance.vue'
|
||||||
|
|
||||||
const TAB_IDS = ['account', 'finance'] as const
|
const TAB_IDS = ['account', 'features', 'finance'] as const
|
||||||
type TabId = (typeof TAB_IDS)[number]
|
type TabId = (typeof TAB_IDS)[number]
|
||||||
|
|
||||||
function isTabId(s: string): s is TabId {
|
function isTabId(s: string): s is TabId {
|
||||||
@@ -101,15 +127,25 @@ const router = useRouter()
|
|||||||
const activeTab = ref<TabId>('account')
|
const activeTab = ref<TabId>('account')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
const tabs: { label: string; value: TabId }[] = [
|
const isEnterpriseAdmin = () => getAdminRole() === 'enterprise_admin'
|
||||||
{ label: '账号设置', value: 'account' },
|
|
||||||
{ label: '企业余额', value: 'finance' }
|
const tabs = computed(() => {
|
||||||
]
|
const rows: { label: string; value: TabId }[] = [{ label: '账号设置', value: 'account' }]
|
||||||
|
if (isEnterpriseAdmin()) {
|
||||||
|
rows.push({ label: '功能开关', value: 'features' })
|
||||||
|
}
|
||||||
|
rows.push({ label: '企业余额', value: 'finance' })
|
||||||
|
return rows
|
||||||
|
})
|
||||||
|
|
||||||
const applyRouteTab = () => {
|
const applyRouteTab = () => {
|
||||||
const t = route.query.tab
|
const t = route.query.tab
|
||||||
if (typeof t === 'string' && isTabId(t)) {
|
if (typeof t === 'string' && isTabId(t)) {
|
||||||
activeTab.value = t
|
if (t === 'features' && !isEnterpriseAdmin()) {
|
||||||
|
activeTab.value = 'account'
|
||||||
|
} else {
|
||||||
|
activeTab.value = t
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
activeTab.value = 'account'
|
activeTab.value = 'account'
|
||||||
}
|
}
|
||||||
@@ -136,6 +172,65 @@ const accountConfig = reactive({
|
|||||||
confirmPassword: ''
|
confirmPassword: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const permItems = [
|
||||||
|
{ key: 'face', label: '人脸分析' },
|
||||||
|
{ key: 'mbti', label: 'MBTI' },
|
||||||
|
{ key: 'pdp', label: 'PDP' },
|
||||||
|
{ key: 'disc', label: 'DISC' },
|
||||||
|
{ key: 'distribution', label: '分销推广' }
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const defaultAdminPermissions = () =>
|
||||||
|
({ face: true, mbti: true, pdp: true, disc: true, distribution: true }) as Record<string, boolean>
|
||||||
|
|
||||||
|
const permLoading = ref(false)
|
||||||
|
const permSaving = ref(false)
|
||||||
|
const adminPermsCeiling = ref<Record<string, boolean>>(defaultAdminPermissions())
|
||||||
|
const adminPerms = reactive<Record<string, boolean>>(defaultAdminPermissions())
|
||||||
|
|
||||||
|
const visibleAdminPermItems = computed(() =>
|
||||||
|
permItems.filter((p) => adminPermsCeiling.value[p.key] !== false)
|
||||||
|
)
|
||||||
|
|
||||||
|
const loadAdminPermissions = async () => {
|
||||||
|
if (!isEnterpriseAdmin()) return
|
||||||
|
permLoading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await request.get('/admin/enterprise/permissions')
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
Object.assign(adminPerms, defaultAdminPermissions(), res.data.permissions || {})
|
||||||
|
adminPermsCeiling.value = { ...defaultAdminPermissions(), ...(res.data.permissionsCeiling || {}) }
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e)
|
||||||
|
ElMessage.error(e?.message || '加载功能开关失败')
|
||||||
|
} finally {
|
||||||
|
permLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveAdminPermissions = async () => {
|
||||||
|
if (!isEnterpriseAdmin()) return
|
||||||
|
permSaving.value = true
|
||||||
|
try {
|
||||||
|
const body: Record<string, boolean> = { ...defaultAdminPermissions() }
|
||||||
|
for (const p of permItems) {
|
||||||
|
body[p.key] = adminPermsCeiling.value[p.key] === false ? false : !!adminPerms[p.key]
|
||||||
|
}
|
||||||
|
const res: any = await request.put('/admin/enterprise/permissions', { permissions: body })
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('功能开关已保存')
|
||||||
|
await loadAdminPermissions()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '保存失败')
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message || '保存失败')
|
||||||
|
} finally {
|
||||||
|
permSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -186,9 +281,21 @@ watch(
|
|||||||
() => applyRouteTab()
|
() => applyRouteTab()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => activeTab.value,
|
||||||
|
(tab) => {
|
||||||
|
if (tab === 'features' && isEnterpriseAdmin()) {
|
||||||
|
loadAdminPermissions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
applyRouteTab()
|
applyRouteTab()
|
||||||
loadSettings()
|
loadSettings()
|
||||||
|
if (activeTab.value === 'features' && isEnterpriseAdmin()) {
|
||||||
|
loadAdminPermissions()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -217,6 +324,26 @@ onMounted(() => {
|
|||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hint-muted {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-admin-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 360px;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-admin-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
.custom-tabs-container {
|
.custom-tabs-container {
|
||||||
background-color: #f3f4f6;
|
background-color: #f3f4f6;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
|||||||
@@ -106,9 +106,26 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column label="操作" width="140" fixed="right">
|
<el-table-column label="超管授权" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="perm-tags">
|
||||||
|
<el-tag v-for="p in permItems" :key="p.key" size="small"
|
||||||
|
:type="permCeilingVal(row, p.key) !== false ? 'success' : 'info'"
|
||||||
|
:effect="permCeilingVal(row, p.key) !== false ? 'light' : 'plain'"
|
||||||
|
class="perm-tag"
|
||||||
|
>{{ p.label }}</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="176" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
|
<el-tooltip content="邀请小程序码" placement="top">
|
||||||
|
<el-button link class="action-invite" @click="openInviteQrcodeDialog(row)">
|
||||||
|
<el-icon><Picture /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
<el-button link @click="handleView(row)"><el-icon><View /></el-icon></el-button>
|
<el-button link @click="handleView(row)"><el-icon><View /></el-icon></el-button>
|
||||||
<el-button link @click="handleEdit(row)"><el-icon><Edit /></el-icon></el-button>
|
<el-button link @click="handleEdit(row)"><el-icon><Edit /></el-icon></el-button>
|
||||||
<el-button link type="danger" @click="handleDelete(row)"><el-icon><Delete /></el-icon></el-button>
|
<el-button link type="danger" @click="handleDelete(row)"><el-icon><Delete /></el-icon></el-button>
|
||||||
@@ -222,6 +239,16 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<el-form-item label="功能授权(超管)" class="mt-12">
|
||||||
|
<p class="perm-form-hint">决定企业管理员能否在后台为终端用户打开该项;关闭后管理端不会出现对应开关。</p>
|
||||||
|
<div class="perm-switch-group">
|
||||||
|
<div class="perm-switch-item" v-for="p in permItems" :key="p.key">
|
||||||
|
<span class="perm-label">{{ p.label }}</span>
|
||||||
|
<el-switch v-model="newEnterprise.permissions[p.key]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -294,6 +321,16 @@
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="功能授权(超管)">
|
||||||
|
<p class="perm-form-hint">保存后将收紧企业管理员可调范围;对已关闭项,终端与企业后台同步关闭。</p>
|
||||||
|
<div class="perm-switch-group">
|
||||||
|
<div class="perm-switch-item" v-for="p in permItems" :key="p.key">
|
||||||
|
<span class="perm-label">{{ p.label }}</span>
|
||||||
|
<el-switch v-model="editEnterprise.permissions[p.key]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -660,6 +697,54 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 邀请小程序码(按当前行企业 ID) -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="showInviteQrcodeDialog"
|
||||||
|
width="440px"
|
||||||
|
class="custom-dialog invite-qrcode-dialog"
|
||||||
|
align-center
|
||||||
|
destroy-on-close
|
||||||
|
@closed="resetInviteQrcodeDialog"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="dialog-header">
|
||||||
|
<h3 class="dialog-title">邀请小程序码</h3>
|
||||||
|
<p class="dialog-subtitle">
|
||||||
|
{{ inviteDialogEnterprise?.name || '企业' }} · 企业版进企业测评,个人版进小程序首页
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-loading="inviteQrcodeLoading" class="invite-qrcode-dialog-body">
|
||||||
|
<div
|
||||||
|
v-if="inviteQrcodeEnterpriseB64 || inviteQrcodePersonalB64"
|
||||||
|
class="invite-qrcode-pair"
|
||||||
|
>
|
||||||
|
<div v-if="inviteQrcodeEnterpriseB64" class="invite-qrcode-card">
|
||||||
|
<span class="invite-qrcode-label">企业版</span>
|
||||||
|
<img :src="inviteQrcodeEnterpriseB64" alt="企业版太阳码" class="invite-qrcode-img" />
|
||||||
|
</div>
|
||||||
|
<div v-if="inviteQrcodePersonalB64" class="invite-qrcode-card">
|
||||||
|
<span class="invite-qrcode-label">个人版</span>
|
||||||
|
<img :src="inviteQrcodePersonalB64" alt="个人版太阳码" class="invite-qrcode-img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else class="invite-qrcode-placeholder">{{ inviteQrcodeError || '加载中…' }}</p>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="showInviteQrcodeDialog = false" class="cancel-btn">关闭</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
color="#3b82f6"
|
||||||
|
:loading="inviteQrcodeLoading"
|
||||||
|
@click="loadInviteQrcodeForDialog"
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -680,7 +765,8 @@ import {
|
|||||||
DataAnalysis,
|
DataAnalysis,
|
||||||
Document,
|
Document,
|
||||||
Wallet,
|
Wallet,
|
||||||
Postcard
|
Postcard,
|
||||||
|
Picture
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { request } from '@/utils/request'
|
import { request } from '@/utils/request'
|
||||||
@@ -715,6 +801,61 @@ const detailOrderPageSize = ref(10)
|
|||||||
const enterpriseDetailTab = ref('overview')
|
const enterpriseDetailTab = ref('overview')
|
||||||
const creating = ref(false)
|
const creating = ref(false)
|
||||||
|
|
||||||
|
/** 邀请小程序码(企业列表操作列) */
|
||||||
|
const showInviteQrcodeDialog = ref(false)
|
||||||
|
const inviteDialogEnterprise = ref<{ id: number; name: string } | null>(null)
|
||||||
|
const inviteQrcodeLoading = ref(false)
|
||||||
|
const inviteQrcodeEnterpriseB64 = ref('')
|
||||||
|
const inviteQrcodePersonalB64 = ref('')
|
||||||
|
const inviteQrcodeError = ref('')
|
||||||
|
|
||||||
|
const resetInviteQrcodeDialog = () => {
|
||||||
|
inviteDialogEnterprise.value = null
|
||||||
|
inviteQrcodeEnterpriseB64.value = ''
|
||||||
|
inviteQrcodePersonalB64.value = ''
|
||||||
|
inviteQrcodeError.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadInviteQrcodeForDialog = async () => {
|
||||||
|
const ent = inviteDialogEnterprise.value
|
||||||
|
if (!ent?.id) return
|
||||||
|
inviteQrcodeLoading.value = true
|
||||||
|
inviteQrcodeError.value = ''
|
||||||
|
try {
|
||||||
|
const res: any = await request.get('/superadmin/invite/qrcode', {
|
||||||
|
params: { enterpriseId: ent.id }
|
||||||
|
})
|
||||||
|
const d = res?.data
|
||||||
|
const qEnt = d?.enterprise?.qrcode ?? d?.qrcode
|
||||||
|
const qPer = d?.personal?.qrcode
|
||||||
|
inviteQrcodeEnterpriseB64.value = typeof qEnt === 'string' && qEnt ? qEnt : ''
|
||||||
|
inviteQrcodePersonalB64.value = typeof qPer === 'string' && qPer ? qPer : ''
|
||||||
|
if (!inviteQrcodeEnterpriseB64.value && !inviteQrcodePersonalB64.value) {
|
||||||
|
inviteQrcodeError.value = res?.message || res?.msg || '生成失败,请检查小程序配置'
|
||||||
|
ElMessage.error(inviteQrcodeError.value)
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
inviteQrcodeError.value = e?.message || '加载失败'
|
||||||
|
ElMessage.error(inviteQrcodeError.value)
|
||||||
|
} finally {
|
||||||
|
inviteQrcodeLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openInviteQrcodeDialog = (row: any) => {
|
||||||
|
inviteDialogEnterprise.value = {
|
||||||
|
id: Number(row.id),
|
||||||
|
name: row.name ? String(row.name) : `企业#${row.id}`
|
||||||
|
}
|
||||||
|
inviteQrcodeEnterpriseB64.value = ''
|
||||||
|
inviteQrcodePersonalB64.value = ''
|
||||||
|
inviteQrcodeError.value = ''
|
||||||
|
showInviteQrcodeDialog.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
loadInviteQrcodeForDialog()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 企业侧栏头像首字 */
|
/** 企业侧栏头像首字 */
|
||||||
const enterpriseAvatarLetter = computed(() => {
|
const enterpriseAvatarLetter = computed(() => {
|
||||||
const n = viewEnterpriseData.value?.name
|
const n = viewEnterpriseData.value?.name
|
||||||
@@ -773,6 +914,25 @@ const statusOptions = [
|
|||||||
{ label: '已停用', value: 'disabled' }
|
{ label: '已停用', value: 'disabled' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const permItems = [
|
||||||
|
{ key: 'face', label: '人脸分析' },
|
||||||
|
{ key: 'mbti', label: 'MBTI' },
|
||||||
|
{ key: 'pdp', label: 'PDP' },
|
||||||
|
{ key: 'disc', label: 'DISC' },
|
||||||
|
{ key: 'distribution', label: '分销' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultPermissions = () => ({ face: true, mbti: true, pdp: true, disc: true, distribution: true })
|
||||||
|
|
||||||
|
/** 列表/展示:超管授权上限(兼容未返回 permissionsCeiling 的旧接口) */
|
||||||
|
const permCeilingVal = (row: Record<string, any>, key: string) => {
|
||||||
|
const c = row.permissionsCeiling
|
||||||
|
if (c && typeof c === 'object' && key in c) return c[key]
|
||||||
|
const p = row.permissions
|
||||||
|
if (p && typeof p === 'object' && key in p) return p[key]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const enterprises = ref<any[]>([])
|
const enterprises = ref<any[]>([])
|
||||||
|
|
||||||
const newEnterprise = reactive({
|
const newEnterprise = reactive({
|
||||||
@@ -785,7 +945,8 @@ const newEnterprise = reactive({
|
|||||||
contactPhone: '',
|
contactPhone: '',
|
||||||
contactEmail: '',
|
contactEmail: '',
|
||||||
status: 'operating',
|
status: 'operating',
|
||||||
trialExpireAt: '' as string
|
trialExpireAt: '' as string,
|
||||||
|
permissions: defaultPermissions() as Record<string, boolean>
|
||||||
})
|
})
|
||||||
|
|
||||||
const editEnterprise = reactive({
|
const editEnterprise = reactive({
|
||||||
@@ -795,7 +956,8 @@ const editEnterprise = reactive({
|
|||||||
contactPhone: '',
|
contactPhone: '',
|
||||||
contactEmail: '',
|
contactEmail: '',
|
||||||
status: 'operating',
|
status: 'operating',
|
||||||
trialExpireAt: ''
|
trialExpireAt: '',
|
||||||
|
permissions: defaultPermissions() as Record<string, boolean>
|
||||||
})
|
})
|
||||||
|
|
||||||
const getStatusLabel = (status: string) => {
|
const getStatusLabel = (status: string) => {
|
||||||
@@ -925,7 +1087,8 @@ const handleCreateEnterprise = async () => {
|
|||||||
contactName: newEnterprise.contactName,
|
contactName: newEnterprise.contactName,
|
||||||
contactPhone: newEnterprise.contactPhone,
|
contactPhone: newEnterprise.contactPhone,
|
||||||
contactEmail: newEnterprise.contactEmail,
|
contactEmail: newEnterprise.contactEmail,
|
||||||
status: newEnterprise.status
|
status: newEnterprise.status,
|
||||||
|
permissions: { ...newEnterprise.permissions }
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
@@ -941,7 +1104,8 @@ const handleCreateEnterprise = async () => {
|
|||||||
contactPhone: '',
|
contactPhone: '',
|
||||||
contactEmail: '',
|
contactEmail: '',
|
||||||
status: 'operating',
|
status: 'operating',
|
||||||
trialExpireAt: ''
|
trialExpireAt: '',
|
||||||
|
permissions: defaultPermissions()
|
||||||
})
|
})
|
||||||
loadEnterprises()
|
loadEnterprises()
|
||||||
}
|
}
|
||||||
@@ -1059,11 +1223,18 @@ const handleEditDialogOpen = async () => {
|
|||||||
editEnterprise.contactEmail = ''
|
editEnterprise.contactEmail = ''
|
||||||
editEnterprise.status = 'operating'
|
editEnterprise.status = 'operating'
|
||||||
editEnterprise.trialExpireAt = ''
|
editEnterprise.trialExpireAt = ''
|
||||||
|
editEnterprise.permissions = defaultPermissions()
|
||||||
|
|
||||||
// 等待一个tick确保清空完成
|
// 等待一个tick确保清空完成
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
// 使用 Object.assign 一次性更新所有字段
|
// 使用 Object.assign 一次性更新所有字段
|
||||||
|
const ceilingFromApi =
|
||||||
|
data.permissionsCeiling && typeof data.permissionsCeiling === 'object'
|
||||||
|
? { ...defaultPermissions(), ...data.permissionsCeiling }
|
||||||
|
: data.permissions && typeof data.permissions === 'object'
|
||||||
|
? { ...defaultPermissions(), ...data.permissions }
|
||||||
|
: defaultPermissions()
|
||||||
Object.assign(editEnterprise, {
|
Object.assign(editEnterprise, {
|
||||||
name: data.name || '',
|
name: data.name || '',
|
||||||
code: data.code || '',
|
code: data.code || '',
|
||||||
@@ -1071,7 +1242,8 @@ const handleEditDialogOpen = async () => {
|
|||||||
contactPhone: data.contactPhone || '',
|
contactPhone: data.contactPhone || '',
|
||||||
contactEmail: data.contactEmail || '',
|
contactEmail: data.contactEmail || '',
|
||||||
status: data.status || 'operating',
|
status: data.status || 'operating',
|
||||||
trialExpireAt: trialExpireAtStr
|
trialExpireAt: trialExpireAtStr,
|
||||||
|
permissions: ceilingFromApi
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('表单数据已填充:', editEnterprise)
|
console.log('表单数据已填充:', editEnterprise)
|
||||||
@@ -1101,7 +1273,8 @@ const handleSaveEdit = async () => {
|
|||||||
contactName: editEnterprise.contactName,
|
contactName: editEnterprise.contactName,
|
||||||
contactPhone: editEnterprise.contactPhone,
|
contactPhone: editEnterprise.contactPhone,
|
||||||
contactEmail: editEnterprise.contactEmail,
|
contactEmail: editEnterprise.contactEmail,
|
||||||
status: editEnterprise.status
|
status: editEnterprise.status,
|
||||||
|
permissions: { ...editEnterprise.permissions }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果选择试用,添加到期时间(转换为时间戳)
|
// 如果选择试用,添加到期时间(转换为时间戳)
|
||||||
@@ -1336,10 +1509,56 @@ watch([searchTerm, statusFilter], () => {
|
|||||||
&.el-button--danger:hover {
|
&.el-button--danger:hover {
|
||||||
color: #ef4444;
|
color: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.action-invite:hover {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invite-qrcode-dialog-body {
|
||||||
|
min-height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-qrcode-pair {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-qrcode-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-qrcode-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-qrcode-img {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-qrcode-placeholder {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ca3af;
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
padding: 60px;
|
padding: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -1962,6 +2181,48 @@ watch([searchTerm, statusFilter], () => {
|
|||||||
min-height: auto;
|
min-height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 权限标签 */
|
||||||
|
.perm-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-tag {
|
||||||
|
font-size: 11px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-form-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 权限开关组(编辑 / 创建弹窗) */
|
||||||
|
.perm-switch-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 14px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-switch-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #374151;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-12 {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.ud-wrap {
|
.ud-wrap {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -133,6 +133,50 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 邀请小程序码(与企业后台一致:企业版 + 个人版) -->
|
||||||
|
<div class="invite-section">
|
||||||
|
<div class="section-header invite-header">
|
||||||
|
<div>
|
||||||
|
<h2 class="section-title">邀请小程序码</h2>
|
||||||
|
<p class="section-subtitle">企业版进企业测评,个人版进小程序首页;企业版太阳码对应下方所选企业</p>
|
||||||
|
</div>
|
||||||
|
<div class="invite-header-actions">
|
||||||
|
<el-select
|
||||||
|
v-model="inviteSelectedEnterpriseId"
|
||||||
|
placeholder="选择企业(生成企业版码)"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
style="width: 220px"
|
||||||
|
size="small"
|
||||||
|
@change="loadInviteQrcode"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="opt in inviteEnterpriseOptions"
|
||||||
|
:key="opt.id"
|
||||||
|
:label="opt.name"
|
||||||
|
:value="opt.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" size="small" :loading="inviteLoading" @click="loadInviteQrcode">
|
||||||
|
{{ inviteQrcodeEnterprise || inviteQrcodePersonal ? '刷新' : '生成' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="invite-body">
|
||||||
|
<template v-if="inviteQrcodeEnterprise || inviteQrcodePersonal">
|
||||||
|
<div v-if="inviteQrcodeEnterprise" class="invite-card">
|
||||||
|
<span class="invite-label">企业版</span>
|
||||||
|
<img :src="inviteQrcodeEnterprise" alt="企业版太阳码" class="invite-img" />
|
||||||
|
</div>
|
||||||
|
<div v-if="inviteQrcodePersonal" class="invite-card">
|
||||||
|
<span class="invite-label">个人版</span>
|
||||||
|
<img :src="inviteQrcodePersonal" alt="个人版太阳码" class="invite-img" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-else class="invite-placeholder">{{ inviteLoadError || '选择企业后点击生成,或未选企业时使用系统默认企业' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 测试趋势折线图 -->
|
<!-- 测试趋势折线图 -->
|
||||||
<div class="trend-section">
|
<div class="trend-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -238,6 +282,7 @@ import {
|
|||||||
Setting
|
Setting
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { request } from '@/utils/request'
|
import { request } from '@/utils/request'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
import { use } from 'echarts/core'
|
import { use } from 'echarts/core'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
import { LineChart } from 'echarts/charts'
|
import { LineChart } from 'echarts/charts'
|
||||||
@@ -272,10 +317,18 @@ const stats = reactive({
|
|||||||
|
|
||||||
interface DynamicItem { icon: string; text: string; time: string }
|
interface DynamicItem { icon: string; text: string; time: string }
|
||||||
interface RankingItem { name: string; tests: number; amount: number }
|
interface RankingItem { name: string; tests: number; amount: number }
|
||||||
|
interface InviteEnterpriseOption { id: number; name: string }
|
||||||
|
|
||||||
const recentDynamics = ref<DynamicItem[]>([])
|
const recentDynamics = ref<DynamicItem[]>([])
|
||||||
const enterpriseRanking = ref<RankingItem[]>([])
|
const enterpriseRanking = ref<RankingItem[]>([])
|
||||||
|
|
||||||
|
const inviteLoading = ref(false)
|
||||||
|
const inviteQrcodeEnterprise = ref<string>('')
|
||||||
|
const inviteQrcodePersonal = ref<string>('')
|
||||||
|
const inviteLoadError = ref<string>('')
|
||||||
|
const inviteEnterpriseOptions = ref<InviteEnterpriseOption[]>([])
|
||||||
|
const inviteSelectedEnterpriseId = ref<number | null>(null)
|
||||||
|
|
||||||
type TrendPoint = { date: string; face: number; mbti: number; pdp: number; disc: number; total: number }
|
type TrendPoint = { date: string; face: number; mbti: number; pdp: number; disc: number; total: number }
|
||||||
const testTrends = ref<TrendPoint[]>([])
|
const testTrends = ref<TrendPoint[]>([])
|
||||||
|
|
||||||
@@ -430,11 +483,76 @@ const handleViewAll = () => {
|
|||||||
router.push('/superadmin/enterprises')
|
router.push('/superadmin/enterprises')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
const loadInviteEnterprisesAndSettings = async () => {
|
||||||
|
try {
|
||||||
|
const [settingsRes, entRes]: any[] = await Promise.all([
|
||||||
|
request.get('/superadmin/settings'),
|
||||||
|
request.get('/superadmin/enterprises', { params: { page: 1, pageSize: 500 } })
|
||||||
|
])
|
||||||
|
const list = entRes?.data?.list ?? []
|
||||||
|
inviteEnterpriseOptions.value = list.map((r: any) => ({
|
||||||
|
id: Number(r.id),
|
||||||
|
name: r.name ? String(r.name) : `企业#${r.id}`
|
||||||
|
}))
|
||||||
|
const defRaw = settingsRes?.data?.system?.defaultEnterpriseId
|
||||||
|
const defNum =
|
||||||
|
defRaw != null && defRaw !== '' && !Number.isNaN(Number(defRaw)) ? Number(defRaw) : null
|
||||||
|
const idSet = new Set(inviteEnterpriseOptions.value.map(o => o.id))
|
||||||
|
if (defNum != null && defNum > 0 && idSet.has(defNum)) {
|
||||||
|
inviteSelectedEnterpriseId.value = defNum
|
||||||
|
} else if (inviteEnterpriseOptions.value.length > 0) {
|
||||||
|
inviteSelectedEnterpriseId.value = inviteEnterpriseOptions.value[0].id
|
||||||
|
} else {
|
||||||
|
inviteSelectedEnterpriseId.value = null
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载邀请码企业列表失败:', e)
|
||||||
|
inviteEnterpriseOptions.value = []
|
||||||
|
inviteSelectedEnterpriseId.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadInviteQrcode = async () => {
|
||||||
|
if (inviteLoading.value) return
|
||||||
|
inviteLoading.value = true
|
||||||
|
inviteLoadError.value = ''
|
||||||
|
try {
|
||||||
|
const params: Record<string, number> = {}
|
||||||
|
if (inviteSelectedEnterpriseId.value != null && inviteSelectedEnterpriseId.value > 0) {
|
||||||
|
params.enterpriseId = inviteSelectedEnterpriseId.value
|
||||||
|
}
|
||||||
|
const res: any = await request.get('/superadmin/invite/qrcode', {
|
||||||
|
params: Object.keys(params).length ? params : undefined
|
||||||
|
})
|
||||||
|
const d = res?.data
|
||||||
|
const ent = d?.enterprise?.qrcode ?? d?.qrcode
|
||||||
|
const per = d?.personal?.qrcode
|
||||||
|
if (typeof ent === 'string' && ent) inviteQrcodeEnterprise.value = ent
|
||||||
|
else inviteQrcodeEnterprise.value = ''
|
||||||
|
if (typeof per === 'string' && per) inviteQrcodePersonal.value = per
|
||||||
|
else inviteQrcodePersonal.value = ''
|
||||||
|
|
||||||
|
if (!inviteQrcodeEnterprise.value && !inviteQrcodePersonal.value) {
|
||||||
|
const msg = res?.message || res?.msg || '生成失败,请确认小程序配置与企业'
|
||||||
|
inviteLoadError.value = msg
|
||||||
|
ElMessage.error(msg)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
const msg = error?.message || '生成失败'
|
||||||
|
inviteLoadError.value = msg
|
||||||
|
ElMessage.error(msg)
|
||||||
|
} finally {
|
||||||
|
inviteLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
loadOverview()
|
loadOverview()
|
||||||
loadRecentDynamics()
|
loadRecentDynamics()
|
||||||
loadEnterpriseRanking()
|
loadEnterpriseRanking()
|
||||||
loadTestTrends()
|
loadTestTrends()
|
||||||
|
await loadInviteEnterprisesAndSettings()
|
||||||
|
loadInviteQrcode()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -622,6 +740,68 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invite-section {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||||
|
border: 1px solid #f3f4f6;
|
||||||
|
|
||||||
|
.invite-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-body {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
min-height: 112px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-img {
|
||||||
|
width: 112px;
|
||||||
|
height: 112px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-placeholder {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.trend-section {
|
.trend-section {
|
||||||
margin-bottom: 28px;
|
margin-bottom: 28px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
|||||||
@@ -45,46 +45,77 @@ class AppUser extends BaseController
|
|||||||
$enterpriseId = $adminRow['enterpriseId'] ?? null;
|
$enterpriseId = $adminRow['enterpriseId'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 若有企业ID:先从 user_profile 中取出属于本企业的 userId 列表(以画像为主表)
|
$baseQuery = null;
|
||||||
$profileUserIds = [];
|
|
||||||
|
// 企业场景:禁止 column 全量 userId + 超大 whereIn,改为「画像池子 + openid 去重」子查询 JOIN(全在库内完成)
|
||||||
if ($enterpriseId) {
|
if ($enterpriseId) {
|
||||||
$profileUserIds = Db::name('user_profile')
|
$eid = (int) $enterpriseId;
|
||||||
->where('enterpriseId', $enterpriseId)
|
// 子查询会在 SQL 中出现两次,条件用字面量避免占位符绑定重复/错位的风险
|
||||||
->column('userId');
|
$poolSql = Db::name('user_profile')
|
||||||
$profileUserIds = $profileUserIds ? array_values(array_unique(array_filter($profileUserIds))) : [];
|
->whereRaw('enterpriseId = ' . $eid)
|
||||||
if (empty($profileUserIds)) {
|
->group('userId')
|
||||||
|
->field('userId')
|
||||||
|
->buildSql(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dedupSql = Db::name('wechat_users')
|
||||||
|
->alias('w2')
|
||||||
|
->join([$poolSql => 'p2'], 'w2.id = p2.userId')
|
||||||
|
->field('w2.openid, MAX(w2.id) AS mid')
|
||||||
|
->group('w2.openid')
|
||||||
|
->buildSql(true);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
return paginate_response([], 0, $page, $pageSize);
|
return paginate_response([], 0, $page, $pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$baseQuery = Db::name('wechat_users')->alias('w')
|
||||||
|
->join([$poolSql => 'p'], 'w.id = p.userId')
|
||||||
|
->join([$dedupSql => 'd'], 'w.id = d.mid');
|
||||||
|
|
||||||
|
if ($keyword !== '') {
|
||||||
|
$kw = '%' . $keyword . '%';
|
||||||
|
$baseQuery->where(function ($q) use ($kw) {
|
||||||
|
$q->whereLike('w.nickname', $kw)
|
||||||
|
->whereOr('w.phone', 'like', $kw)
|
||||||
|
->whereOr('w.city', 'like', $kw)
|
||||||
|
->whereOr('w.province', 'like', $kw);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 无企业归属(极少):沿用全表 openid 去重 + IN 列表
|
||||||
|
try {
|
||||||
|
$dedupIds = Db::name('wechat_users')->field('openid, MAX(id) as mid')->group('openid')->column('mid');
|
||||||
|
$dedupIds = $dedupIds ? array_values(array_filter($dedupIds)) : [];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$dedupIds = Db::name('wechat_users')->column('id');
|
||||||
|
$dedupIds = $dedupIds ? array_values(array_filter($dedupIds)) : [];
|
||||||
|
}
|
||||||
|
if (empty($dedupIds)) {
|
||||||
|
return paginate_response([], 0, $page, $pageSize);
|
||||||
|
}
|
||||||
|
$baseQuery = Db::name('wechat_users')->where('id', 'in', $dedupIds);
|
||||||
|
if ($where) {
|
||||||
|
$baseQuery->where($where);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按 openid 去重:每个 openid 只保留 id 最大的一条;失败则不去重
|
if ($enterpriseId) {
|
||||||
try {
|
$total = (int) (clone $baseQuery)->distinct(true)->count('w.id');
|
||||||
$dedupIds = Db::name('wechat_users')->field('openid, MAX(id) as mid')->group('openid')->column('mid');
|
$list = (clone $baseQuery)
|
||||||
$dedupIds = $dedupIds ? array_values(array_filter($dedupIds)) : [];
|
->field('w.id,w.nickname,w.openid,w.avatar,w.phone,w.gender,w.country,w.province,w.city,w.status,w.lastLoginAt,w.createdAt')
|
||||||
} catch (\Throwable $e) {
|
->order('w.createdAt', 'desc')
|
||||||
$dedupIds = Db::name('wechat_users')->column('id');
|
->page($page, $pageSize)
|
||||||
$dedupIds = $dedupIds ? array_values(array_filter($dedupIds)) : [];
|
->select()
|
||||||
|
->toArray();
|
||||||
|
} else {
|
||||||
|
$total = (int) (clone $baseQuery)->count();
|
||||||
|
$list = (clone $baseQuery)
|
||||||
|
->field('id,nickname,openid,avatar,phone,gender,country,province,city,status,lastLoginAt,createdAt')
|
||||||
|
->order('createdAt', 'desc')
|
||||||
|
->page($page, $pageSize)
|
||||||
|
->select()
|
||||||
|
->toArray();
|
||||||
}
|
}
|
||||||
if (empty($dedupIds)) {
|
|
||||||
return paginate_response([], 0, $page, $pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
$baseQuery = Db::name('wechat_users')->where('id', 'in', $dedupIds);
|
|
||||||
// 若从画像表中筛出了当前企业的用户池,则仅保留这些 userId
|
|
||||||
if (!empty($profileUserIds)) {
|
|
||||||
$baseQuery->whereIn('id', $profileUserIds);
|
|
||||||
}
|
|
||||||
if ($where) {
|
|
||||||
$baseQuery->where($where);
|
|
||||||
}
|
|
||||||
|
|
||||||
$total = (int) $baseQuery->count();
|
|
||||||
$list = (clone $baseQuery)
|
|
||||||
->field('id,nickname,openid,avatar,phone,gender,country,province,city,status,lastLoginAt,createdAt')
|
|
||||||
->order('createdAt', 'desc')
|
|
||||||
->page($page, $pageSize)
|
|
||||||
->select()
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
// 为每条用户附加测试统计(test_results.userId 对应 wechat_users.id)
|
// 为每条用户附加测试统计(test_results.userId 对应 wechat_users.id)
|
||||||
$ids = array_column($list, 'id');
|
$ids = array_column($list, 'id');
|
||||||
@@ -103,20 +134,49 @@ class AppUser extends BaseController
|
|||||||
if ($enterpriseId) {
|
if ($enterpriseId) {
|
||||||
$trBase->where('enterpriseId', $enterpriseId);
|
$trBase->where('enterpriseId', $enterpriseId);
|
||||||
}
|
}
|
||||||
$counts = (clone $trBase)
|
// 次数与最近测试时间一次 GROUP 查完,减少往返
|
||||||
|
$trAggRows = (clone $trBase)
|
||||||
|
->field('userId, COUNT(*) AS cnt, MAX(createdAt) AS lastAt')
|
||||||
->group('userId')
|
->group('userId')
|
||||||
->column('COUNT(*) as cnt', 'userId');
|
->select()
|
||||||
$testCounts = $counts ?: [];
|
->toArray();
|
||||||
|
foreach ($trAggRows as $r) {
|
||||||
|
$uid = (int) ($r['userId'] ?? 0);
|
||||||
|
if ($uid > 0) {
|
||||||
|
$testCounts[$uid] = (int) ($r['cnt'] ?? 0);
|
||||||
|
$lastTestAt[$uid] = $r['lastAt'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每人每种 testType 仅取最新一条(含 MAX(id) 消解同一时间戳多行),禁止 select 全量记录
|
||||||
|
try {
|
||||||
|
$aggSub = Db::name('test_results')->where('userId', 'in', $ids);
|
||||||
|
if ($enterpriseId) {
|
||||||
|
$aggSub->where('enterpriseId', $enterpriseId);
|
||||||
|
}
|
||||||
|
$aggSql = $aggSub
|
||||||
|
->field('userId, testType, MAX(createdAt) as mc, MAX(id) as mid')
|
||||||
|
->group('userId, testType')
|
||||||
|
->buildSql(true);
|
||||||
|
|
||||||
|
$lastDetailQuery = Db::name('test_results')->alias('t')
|
||||||
|
->join([$aggSql => 'agg'], 't.userId = agg.userId AND t.testType = agg.testType AND t.id = agg.mid')
|
||||||
|
->field('t.id, t.userId, t.testType, t.resultData, t.createdAt, t.enterpriseId as testEnterpriseId');
|
||||||
|
if ($enterpriseId) {
|
||||||
|
$lastDetailQuery->where('t.enterpriseId', $enterpriseId);
|
||||||
|
}
|
||||||
|
$lastRows = $lastDetailQuery->select()->toArray();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$lastRows = (clone $trBase)
|
||||||
|
->field('id, userId, testType, resultData, createdAt, enterpriseId as testEnterpriseId')
|
||||||
|
->order('createdAt', 'desc')
|
||||||
|
->limit(2000)
|
||||||
|
->select()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
$lastRows = (clone $trBase)
|
|
||||||
->field('id, userId, testType, resultData, createdAt, enterpriseId as testEnterpriseId')
|
|
||||||
->order('createdAt', 'desc')
|
|
||||||
->select();
|
|
||||||
foreach ($lastRows as $row) {
|
foreach ($lastRows as $row) {
|
||||||
$uid = $row['userId'];
|
$uid = $row['userId'];
|
||||||
if (!isset($lastTestAt[$uid])) {
|
|
||||||
$lastTestAt[$uid] = $row['createdAt'];
|
|
||||||
}
|
|
||||||
if (!isset($testTypes[$uid])) {
|
if (!isset($testTypes[$uid])) {
|
||||||
$testTypes[$uid] = [];
|
$testTypes[$uid] = [];
|
||||||
}
|
}
|
||||||
@@ -127,6 +187,12 @@ class AppUser extends BaseController
|
|||||||
'testScope' => !empty($row['testEnterpriseId']) ? 'enterprise' : 'personal',
|
'testScope' => !empty($row['testEnterpriseId']) ? 'enterprise' : 'personal',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
foreach ($testTypes as $uid => &$tlist) {
|
||||||
|
usort($tlist, static function ($a, $b) {
|
||||||
|
return (int) ($b['createdAt'] ?? 0) <=> (int) ($a['createdAt'] ?? 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
unset($tlist);
|
||||||
// 付款统计:user_profile(按当前企业过滤)
|
// 付款统计:user_profile(按当前企业过滤)
|
||||||
try {
|
try {
|
||||||
$profilesQuery = Db::name('user_profile')
|
$profilesQuery = Db::name('user_profile')
|
||||||
|
|||||||
@@ -32,9 +32,20 @@ class Dashboard extends BaseController
|
|||||||
try {
|
try {
|
||||||
// admin / enterprise_admin 均只统计本企业数据
|
// admin / enterprise_admin 均只统计本企业数据
|
||||||
$enterpriseId = $user['enterpriseId'] ?? null;
|
$enterpriseId = $user['enterpriseId'] ?? null;
|
||||||
|
if (is_array($enterpriseId)) {
|
||||||
|
$enterpriseId = null;
|
||||||
|
}
|
||||||
|
$enterpriseId = $enterpriseId !== null && $enterpriseId !== '' ? (int) $enterpriseId : null;
|
||||||
|
if ($enterpriseId !== null && $enterpriseId <= 0) {
|
||||||
|
$enterpriseId = null;
|
||||||
|
}
|
||||||
if (!$enterpriseId) {
|
if (!$enterpriseId) {
|
||||||
$adminRow = Db::name('users')->where('id', $user['userId'] ?? 0)->find();
|
$adminRow = Db::name('users')->where('id', $user['userId'] ?? 0)->find();
|
||||||
$enterpriseId = $adminRow['enterpriseId'] ?? null;
|
$eid = $adminRow['enterpriseId'] ?? null;
|
||||||
|
$enterpriseId = ($eid !== null && $eid !== '') ? (int) $eid : null;
|
||||||
|
if ($enterpriseId !== null && $enterpriseId <= 0) {
|
||||||
|
$enterpriseId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 企业用户 ID 集合(用于后续统计个人版测试)
|
// 企业用户 ID 集合(用于后续统计个人版测试)
|
||||||
|
|||||||
88
api/app/controller/admin/EnterprisePermissions.php
Normal file
88
api/app/controller/admin/EnterprisePermissions.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
namespace app\controller\admin;
|
||||||
|
|
||||||
|
use app\BaseController;
|
||||||
|
use app\model\Enterprise as EnterpriseModel;
|
||||||
|
use think\facade\Db;
|
||||||
|
use think\facade\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业管理员:在超管授权上限内配置各功能开关(PUT 不可突破 permissionsCeiling)
|
||||||
|
*/
|
||||||
|
class EnterprisePermissions extends BaseController
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$user = $this->request->user ?? null;
|
||||||
|
if (!$user || ($user['role'] ?? '') !== 'enterprise_admin') {
|
||||||
|
return error('仅企业管理员可配置', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$eid = $this->resolveEnterpriseId($user);
|
||||||
|
if (!$eid || $eid <= 0) {
|
||||||
|
return error('未绑定企业', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$enterprise = EnterpriseModel::find($eid);
|
||||||
|
if (!$enterprise) {
|
||||||
|
return error('企业不存在', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ceiling = EnterpriseModel::normalizedPermissionsCeiling($enterprise);
|
||||||
|
$effective = EnterpriseModel::normalizePermissionsValue($enterprise->permissions ?? null);
|
||||||
|
|
||||||
|
return success([
|
||||||
|
'permissions' => $effective,
|
||||||
|
'permissionsCeiling' => $ceiling,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update()
|
||||||
|
{
|
||||||
|
$user = $this->request->user ?? null;
|
||||||
|
if (!$user || ($user['role'] ?? '') !== 'enterprise_admin') {
|
||||||
|
return error('仅企业管理员可配置', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$eid = $this->resolveEnterpriseId($user);
|
||||||
|
if (!$eid || $eid <= 0) {
|
||||||
|
return error('未绑定企业', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$enterprise = EnterpriseModel::find($eid);
|
||||||
|
if (!$enterprise) {
|
||||||
|
return error('企业不存在', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = Request::put();
|
||||||
|
if (!is_array($body)) {
|
||||||
|
$body = [];
|
||||||
|
}
|
||||||
|
$incoming = $body['permissions'] ?? null;
|
||||||
|
if (!is_array($incoming)) {
|
||||||
|
return error('请提交 permissions 对象', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ceiling = EnterpriseModel::normalizedPermissionsCeiling($enterprise);
|
||||||
|
$effective = EnterpriseModel::clampPermissionsToCeiling($ceiling, $incoming);
|
||||||
|
|
||||||
|
$enterprise->permissions = $effective;
|
||||||
|
$enterprise->save();
|
||||||
|
|
||||||
|
return success([
|
||||||
|
'permissions' => $effective,
|
||||||
|
'permissionsCeiling' => $ceiling,
|
||||||
|
], '已保存');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveEnterpriseId(array $user): ?int
|
||||||
|
{
|
||||||
|
$adminRow = Db::name('users')->where('id', $user['userId'] ?? 0)->find();
|
||||||
|
$eid = $adminRow['enterpriseId'] ?? null;
|
||||||
|
if ($eid === null || $eid === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) $eid;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,14 +19,23 @@ class Invite extends BaseController
|
|||||||
public function qrcode()
|
public function qrcode()
|
||||||
{
|
{
|
||||||
$admin = $this->request->user ?? null;
|
$admin = $this->request->user ?? null;
|
||||||
if (!$admin || !in_array($admin['role'] ?? '', ['admin', 'enterprise_admin'])) {
|
$role = $admin['role'] ?? '';
|
||||||
|
if (!$admin || !in_array($role, ['admin', 'enterprise_admin', 'superadmin'], true)) {
|
||||||
return error('无权限访问', 403);
|
return error('无权限访问', 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$enterpriseId = null;
|
$enterpriseId = null;
|
||||||
if (($admin['role'] ?? '') === 'enterprise_admin') {
|
if ($role === 'enterprise_admin') {
|
||||||
$row = Db::name('users')->where('id', (int) ($admin['userId'] ?? 0))->find();
|
$row = Db::name('users')->where('id', (int) ($admin['userId'] ?? 0))->find();
|
||||||
$enterpriseId = isset($row['enterpriseId']) ? (int) $row['enterpriseId'] : null;
|
$enterpriseId = isset($row['enterpriseId']) ? (int) $row['enterpriseId'] : null;
|
||||||
|
} elseif ($role === 'superadmin') {
|
||||||
|
$enterpriseId = (int) $this->request->param('enterpriseId', 0);
|
||||||
|
if ($enterpriseId <= 0) {
|
||||||
|
$enterpriseId = self::resolveDefaultEnterpriseIdForPlatform();
|
||||||
|
}
|
||||||
|
if ($enterpriseId <= 0) {
|
||||||
|
return error('请传 enterpriseId,或在系统设置中配置默认企业', 400);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$enterpriseId = (int) $this->request->param('enterpriseId', 0);
|
$enterpriseId = (int) $this->request->param('enterpriseId', 0);
|
||||||
if ($enterpriseId <= 0) {
|
if ($enterpriseId <= 0) {
|
||||||
@@ -80,5 +89,46 @@ class Invite extends BaseController
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超管生成企业版太阳码时:请求未带 enterpriseId 则按系统 defaultEnterpriseId → 首家运营企业
|
||||||
|
*/
|
||||||
|
private static function resolveDefaultEnterpriseIdForPlatform(): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$systemRow = Db::name('system_config')->where('key', 'system')->where('enterprise_id', 0)->find();
|
||||||
|
if ($systemRow && !empty($systemRow['value'])) {
|
||||||
|
$sysVal = is_string($systemRow['value']) ? json_decode($systemRow['value'], true) : $systemRow['value'];
|
||||||
|
if (is_array($sysVal) && !empty($sysVal['defaultEnterpriseId'])) {
|
||||||
|
$de = (int) $sysVal['defaultEnterpriseId'];
|
||||||
|
if ($de > 0) {
|
||||||
|
return $de;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$first = Db::name('enterprises')
|
||||||
|
->whereNull('deletedAt')
|
||||||
|
->where('status', 'operating')
|
||||||
|
->order('id', 'asc')
|
||||||
|
->value('id');
|
||||||
|
if ($first) {
|
||||||
|
return (int) $first;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$any = Db::name('enterprises')->whereNull('deletedAt')->order('id', 'asc')->value('id');
|
||||||
|
|
||||||
|
return $any ? (int) $any : 0;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,38 @@ namespace app\controller\admin\concern;
|
|||||||
*/
|
*/
|
||||||
trait ExtractsTestResults
|
trait ExtractsTestResults
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 结果字段可能为数组/对象,禁止直接 (string) 强转导致 Array to string conversion
|
||||||
|
*/
|
||||||
|
private function coerceResultLabel($value): string
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (is_string($value)) {
|
||||||
|
return trim($value);
|
||||||
|
}
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
if (is_array($value)) {
|
||||||
|
if (isset($value['type']) && is_string($value['type'])) {
|
||||||
|
return trim($value['type']);
|
||||||
|
}
|
||||||
|
if (isset($value['primary']) && is_string($value['primary'])) {
|
||||||
|
return trim($value['primary']);
|
||||||
|
}
|
||||||
|
foreach (['name', 'label', 'code'] as $k) {
|
||||||
|
if (isset($value[$k]) && is_string($value[$k]) && $value[$k] !== '') {
|
||||||
|
return trim($value[$k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
private function extractResultType(array $tests, string $type): string
|
private function extractResultType(array $tests, string $type): string
|
||||||
{
|
{
|
||||||
$targetType = strtolower($type);
|
$targetType = strtolower($type);
|
||||||
@@ -27,7 +59,13 @@ trait ExtractsTestResults
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($targetType === 'mbti') {
|
if ($targetType === 'mbti') {
|
||||||
return (string) ($dec['mbtiType'] ?? $dec['type'] ?? $dec['result'] ?? '');
|
foreach (['mbtiType', 'type', 'result'] as $k) {
|
||||||
|
$s = $this->coerceResultLabel($dec[$k] ?? null);
|
||||||
|
if ($s !== '') {
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($targetType === 'disc') {
|
if ($targetType === 'disc') {
|
||||||
@@ -35,10 +73,11 @@ trait ExtractsTestResults
|
|||||||
if (is_string($desc) && $desc !== '') {
|
if (is_string($desc) && $desc !== '') {
|
||||||
return $desc;
|
return $desc;
|
||||||
}
|
}
|
||||||
if (!empty($dec['dominantType'])) {
|
$dom = $this->coerceResultLabel($dec['dominantType'] ?? null);
|
||||||
return (string) $dec['dominantType'];
|
if ($dom !== '') {
|
||||||
|
return $dom;
|
||||||
}
|
}
|
||||||
return (string) ($dec['disc'] ?? '');
|
return $this->coerceResultLabel($dec['disc'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($targetType === 'pdp') {
|
if ($targetType === 'pdp') {
|
||||||
@@ -46,13 +85,18 @@ trait ExtractsTestResults
|
|||||||
if (is_string($desc) && $desc !== '') {
|
if (is_string($desc) && $desc !== '') {
|
||||||
return $desc;
|
return $desc;
|
||||||
}
|
}
|
||||||
if (!empty($dec['dominantType'])) {
|
$dom = $this->coerceResultLabel($dec['dominantType'] ?? null);
|
||||||
return (string) $dec['dominantType'];
|
if ($dom !== '') {
|
||||||
|
return $dom;
|
||||||
}
|
}
|
||||||
return (string) ($dec['pdp'] ?? '');
|
return $this->coerceResultLabel($dec['pdp'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (string) ($dec['type'] ?? $dec['result'] ?? '');
|
$fallback = $this->coerceResultLabel($dec['type'] ?? null);
|
||||||
|
if ($fallback !== '') {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
return $this->coerceResultLabel($dec['result'] ?? null);
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -82,17 +126,25 @@ trait ExtractsTestResults
|
|||||||
}
|
}
|
||||||
} elseif ($target === 'disc') {
|
} elseif ($target === 'disc') {
|
||||||
if (!empty($dec['disc']['primary'])) {
|
if (!empty($dec['disc']['primary'])) {
|
||||||
return (string) $dec['disc']['primary'];
|
return $this->coerceResultLabel($dec['disc']['primary']);
|
||||||
}
|
}
|
||||||
if (!empty($dec['disc'])) {
|
$d = $dec['disc'] ?? null;
|
||||||
return (string) $dec['disc'];
|
if (is_array($d)) {
|
||||||
|
$s = $this->coerceResultLabel($d);
|
||||||
|
if ($s !== '') {
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif ($target === 'pdp') {
|
} elseif ($target === 'pdp') {
|
||||||
if (!empty($dec['pdp']['primary'])) {
|
if (!empty($dec['pdp']['primary'])) {
|
||||||
return (string) $dec['pdp']['primary'];
|
return $this->coerceResultLabel($dec['pdp']['primary']);
|
||||||
}
|
}
|
||||||
if (!empty($dec['pdp'])) {
|
$p = $dec['pdp'] ?? null;
|
||||||
return (string) $dec['pdp'];
|
if (is_array($p)) {
|
||||||
|
$s = $this->coerceResultLabel($p);
|
||||||
|
if ($s !== '') {
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
namespace app\controller\api;
|
namespace app\controller\api;
|
||||||
|
|
||||||
use app\BaseController;
|
use app\BaseController;
|
||||||
|
use app\model\Enterprise as EnterpriseModel;
|
||||||
use app\model\PricingConfig as PricingConfigModel;
|
use app\model\PricingConfig as PricingConfigModel;
|
||||||
use app\model\AiProvider as AiProviderModel;
|
use app\model\AiProvider as AiProviderModel;
|
||||||
use app\common\service\JwtService;
|
use app\common\service\JwtService;
|
||||||
@@ -39,16 +40,22 @@ class AppConfig extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($scope !== 'personal' && $user && ($user['source'] ?? '') === 'wechat') {
|
// 绑定企业 ID:任意 scope 都要能读到(个人首页也是 scope=personal,否则 enterprisePermissions 永远为 null)
|
||||||
|
$userBoundEnterpriseId = null;
|
||||||
|
if ($user && ($user['source'] ?? '') === 'wechat') {
|
||||||
$userId = (int) ($user['user_id'] ?? $user['userId'] ?? 0);
|
$userId = (int) ($user['user_id'] ?? $user['userId'] ?? 0);
|
||||||
if ($userId > 0) {
|
if ($userId > 0) {
|
||||||
$row = Db::name('wechat_users')->where('id', $userId)->field('enterpriseId')->find();
|
$row = Db::name('wechat_users')->where('id', $userId)->field('enterpriseId')->find();
|
||||||
if (!empty($row['enterpriseId'])) {
|
if (!empty($row['enterpriseId'])) {
|
||||||
$enterpriseId = (int) $row['enterpriseId'];
|
$userBoundEnterpriseId = (int) $row['enterpriseId'];
|
||||||
$pricingType = 'enterprise';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 定价/文案用的 enterpriseId:仅在非个人 scope 时走企业定价(与个人页 scope=personal 仍可返回企业权限)
|
||||||
|
if ($scope !== 'personal' && $userBoundEnterpriseId !== null && $userBoundEnterpriseId > 0) {
|
||||||
|
$enterpriseId = $userBoundEnterpriseId;
|
||||||
|
$pricingType = 'enterprise';
|
||||||
|
}
|
||||||
|
|
||||||
// 系统配置:审核模式、默认企业(无带参入口时小程序回落)
|
// 系统配置:审核模式、默认企业(无带参入口时小程序回落)
|
||||||
$maintenanceMode = false;
|
$maintenanceMode = false;
|
||||||
@@ -161,6 +168,20 @@ class AppConfig extends BaseController
|
|||||||
// 与 maintenanceMode 同源:小程序旧逻辑读 reviewMode
|
// 与 maintenanceMode 同源:小程序旧逻辑读 reviewMode
|
||||||
$reviewMode = $maintenanceMode;
|
$reviewMode = $maintenanceMode;
|
||||||
|
|
||||||
|
// 企业功能权限:与定价 scope 解耦;库中 permissions 为空/异常时仍返回默认值,避免前端判空异常
|
||||||
|
$eidForPermissions = ($enterpriseId > 0) ? $enterpriseId : (($userBoundEnterpriseId ?? 0) > 0 ? $userBoundEnterpriseId : null);
|
||||||
|
$enterprisePermissions = null;
|
||||||
|
if ($eidForPermissions > 0) {
|
||||||
|
try {
|
||||||
|
$ent = Db::name('enterprises')->where('id', $eidForPermissions)->field('permissions')->find();
|
||||||
|
if (is_array($ent)) {
|
||||||
|
$enterprisePermissions = EnterpriseModel::normalizePermissionsValue($ent['permissions'] ?? null);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$enterprisePermissions = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return success([
|
return success([
|
||||||
'pricingType' => $pricingType,
|
'pricingType' => $pricingType,
|
||||||
'pricing' => $pricing,
|
'pricing' => $pricing,
|
||||||
@@ -174,6 +195,7 @@ class AppConfig extends BaseController
|
|||||||
'maintenanceMode' => $maintenanceMode,
|
'maintenanceMode' => $maintenanceMode,
|
||||||
'reviewMode' => $reviewMode,
|
'reviewMode' => $reviewMode,
|
||||||
'defaultEnterpriseId' => $defaultEnterpriseId,
|
'defaultEnterpriseId' => $defaultEnterpriseId,
|
||||||
|
'enterprisePermissions' => $enterprisePermissions,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
namespace app\controller\api;
|
namespace app\controller\api;
|
||||||
|
|
||||||
use app\BaseController;
|
use app\BaseController;
|
||||||
|
use app\model\Enterprise as EnterpriseModel;
|
||||||
use app\model\PricingConfig as PricingConfigModel;
|
use app\model\PricingConfig as PricingConfigModel;
|
||||||
use app\model\UserProfile as UserProfileModel;
|
use app\model\UserProfile as UserProfileModel;
|
||||||
use think\facade\Db;
|
use think\facade\Db;
|
||||||
@@ -28,24 +29,45 @@ class Test extends BaseController
|
|||||||
return error('未登录', 401);
|
return error('未登录', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = Request::param('type', 'all'); // all|mbti|disc|pdp|face
|
$type = Request::param('type', 'all'); // all|mbti|disc|pdp|face|ai|resume
|
||||||
$scope = Request::param('scope', 'all'); // all|personal|enterprise
|
$scope = Request::param('scope', 'all'); // all|personal|enterprise
|
||||||
$page = max(1, (int) Request::param('page', 1));
|
$page = max(1, (int) Request::param('page', 1));
|
||||||
$pageSize = (int) Request::param('pageSize', 0);
|
$pageSize = (int) Request::param('pageSize', 0);
|
||||||
if ($pageSize <= 0) {
|
if ($pageSize <= 0) {
|
||||||
$pageSize = 500;
|
$pageSize = 10;
|
||||||
}
|
}
|
||||||
$pageSize = min(500, max(1, $pageSize));
|
$pageSize = min(100, max(1, $pageSize));
|
||||||
|
|
||||||
|
$allowedTestTypes = $this->wechatAllowedTestTypes($userId);
|
||||||
|
|
||||||
|
// 列表只展示摘要:禁止 tr.* 拉全表 resultData(单条可达数 MB×500=卡死);仅截取 JSON 前若干字符用于解析标题
|
||||||
|
$listJsonSliceLen = 32768;
|
||||||
$base = Db::name('test_results')
|
$base = Db::name('test_results')
|
||||||
->alias('tr')
|
->alias('tr')
|
||||||
->leftJoin('wechat_users wu', 'tr.userId = wu.id')
|
->leftJoin('wechat_users wu', 'tr.userId = wu.id')
|
||||||
->leftJoin('enterprises e_tr', 'tr.enterpriseId = e_tr.id')
|
->leftJoin('enterprises e_tr', 'tr.enterpriseId = e_tr.id')
|
||||||
->leftJoin('enterprises e_wu', 'wu.enterpriseId = e_wu.id')
|
->leftJoin('enterprises e_wu', 'wu.enterpriseId = e_wu.id')
|
||||||
->where('tr.userId', $userId)
|
->where('tr.userId', $userId)
|
||||||
->field('tr.*, e_tr.name as enterpriseName, wu.enterpriseId as bindEnterpriseId, e_wu.name as bindEnterpriseName')
|
->field(
|
||||||
|
'tr.id,tr.testType,tr.createdAt,tr.enterpriseId,tr.requiresPayment,tr.isPaid,tr.orderId,tr.paidAmount,' .
|
||||||
|
'e_tr.name as enterpriseName,wu.enterpriseId as bindEnterpriseId,e_wu.name as bindEnterpriseName,' .
|
||||||
|
'SUBSTRING(COALESCE(CAST(tr.resultData AS CHAR(65532)),\'\'),1,' . $listJsonSliceLen . ') as resultDataLite'
|
||||||
|
)
|
||||||
->order('tr.createdAt', 'desc');
|
->order('tr.createdAt', 'desc');
|
||||||
|
|
||||||
|
$profileIncomplete = !self::isWechatProfileComplete($userId);
|
||||||
|
|
||||||
|
if ($allowedTestTypes === []) {
|
||||||
|
return success([
|
||||||
|
'list' => [],
|
||||||
|
'total' => 0,
|
||||||
|
'page' => $page,
|
||||||
|
'pageSize' => $pageSize,
|
||||||
|
'hasMore' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$base->whereIn('tr.testType', $allowedTestTypes);
|
||||||
|
|
||||||
if ($type !== 'all') {
|
if ($type !== 'all') {
|
||||||
if (in_array($type, ['face', 'ai'], true)) {
|
if (in_array($type, ['face', 'ai'], true)) {
|
||||||
$base->whereIn('tr.testType', ['face', 'ai']);
|
$base->whereIn('tr.testType', ['face', 'ai']);
|
||||||
@@ -82,9 +104,8 @@ class Test extends BaseController
|
|||||||
$orderId = isset($row['orderId']) ? (int) $row['orderId'] : null;
|
$orderId = isset($row['orderId']) ? (int) $row['orderId'] : null;
|
||||||
$paidAmountRow = isset($row['paidAmount']) ? (int) $row['paidAmount'] : 0;
|
$paidAmountRow = isset($row['paidAmount']) ? (int) $row['paidAmount'] : 0;
|
||||||
$needPayUnlock = $requiresPayment && !$isPaid && $paidAmountRow > 0;
|
$needPayUnlock = $requiresPayment && !$isPaid && $paidAmountRow > 0;
|
||||||
$profileIncomplete = !self::isWechatProfileComplete($userId);
|
|
||||||
|
|
||||||
$raw = $row['resultData'] ?? ($row['result'] ?? null);
|
$raw = $row['resultDataLite'] ?? ($row['resultData'] ?? null);
|
||||||
$data = null;
|
$data = null;
|
||||||
if ($raw !== null && $raw !== '') {
|
if ($raw !== null && $raw !== '') {
|
||||||
$decoded = json_decode($raw, true);
|
$decoded = json_decode($raw, true);
|
||||||
@@ -107,37 +128,37 @@ class Test extends BaseController
|
|||||||
'enterpriseName' => $enterpriseName,
|
'enterpriseName' => $enterpriseName,
|
||||||
];
|
];
|
||||||
|
|
||||||
// 映射为小程序 history 页需要的结构
|
// 映射为小程序 history 页列表结构;完整报告走 GET /api/test/detail?id= 减轻响应体积
|
||||||
switch ($testType) {
|
switch ($testType) {
|
||||||
case 'mbti':
|
case 'mbti':
|
||||||
$mbtiType = $data['mbtiType'] ?? $data['mbti'] ?? '未知';
|
$mbtiType = is_array($data) ? ($data['mbtiType'] ?? $data['mbti'] ?? '未知') : '未知';
|
||||||
$list[] = array_merge([
|
$list[] = array_merge([
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'type' => 'mbti',
|
'type' => 'mbti',
|
||||||
'key' => 'mbti_' . $id,
|
'key' => 'mbti_' . $id,
|
||||||
'emoji' => '🧠',
|
'emoji' => '🧠',
|
||||||
'typeName' => 'MBTI性格测试',
|
'typeName' => 'MBTI性格测试',
|
||||||
'resultText'=> $mbtiType,
|
'resultText'=> is_string($mbtiType) || is_numeric($mbtiType) ? (string) $mbtiType : '未知',
|
||||||
'testTime' => $timeLabel,
|
'testTime' => $timeLabel,
|
||||||
'data' => $data,
|
'data' => null,
|
||||||
], $paymentFields);
|
], $paymentFields);
|
||||||
break;
|
break;
|
||||||
case 'disc':
|
case 'disc':
|
||||||
$discType = $data['dominantType'] ?? $data['disc'] ?? '未知';
|
$discType = is_array($data) ? ($data['dominantType'] ?? $data['disc'] ?? '未知') : '未知';
|
||||||
$list[] = array_merge([
|
$list[] = array_merge([
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'type' => 'disc',
|
'type' => 'disc',
|
||||||
'key' => 'disc_' . $id,
|
'key' => 'disc_' . $id,
|
||||||
'emoji' => '📊',
|
'emoji' => '📊',
|
||||||
'typeName' => 'DISC性格测试',
|
'typeName' => 'DISC性格测试',
|
||||||
'resultText'=> $discType . '型',
|
'resultText'=> (is_string($discType) || is_numeric($discType) ? (string) $discType : '未知') . '型',
|
||||||
'testTime' => $timeLabel,
|
'testTime' => $timeLabel,
|
||||||
'data' => $data,
|
'data' => null,
|
||||||
], $paymentFields);
|
], $paymentFields);
|
||||||
break;
|
break;
|
||||||
case 'pdp':
|
case 'pdp':
|
||||||
$primary = $data['description']['type'] ?? $data['pdp'] ?? '未知';
|
$primary = is_array($data) ? ($data['description']['type'] ?? $data['pdp'] ?? '未知') : '未知';
|
||||||
$emoji = $data['description']['emoji'] ?? '🦁';
|
$emoji = (is_array($data) && isset($data['description']['emoji'])) ? $data['description']['emoji'] : '🦁';
|
||||||
$list[] = array_merge([
|
$list[] = array_merge([
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'type' => 'pdp',
|
'type' => 'pdp',
|
||||||
@@ -146,7 +167,7 @@ class Test extends BaseController
|
|||||||
'typeName' => 'PDP行为偏好测试',
|
'typeName' => 'PDP行为偏好测试',
|
||||||
'resultText'=> $primary,
|
'resultText'=> $primary,
|
||||||
'testTime' => $timeLabel,
|
'testTime' => $timeLabel,
|
||||||
'data' => $data,
|
'data' => null,
|
||||||
], $paymentFields);
|
], $paymentFields);
|
||||||
break;
|
break;
|
||||||
case 'face':
|
case 'face':
|
||||||
@@ -167,7 +188,7 @@ class Test extends BaseController
|
|||||||
'typeName' => '面相分析',
|
'typeName' => '面相分析',
|
||||||
'resultText'=> $mbtiShort ?: '未知',
|
'resultText'=> $mbtiShort ?: '未知',
|
||||||
'testTime' => $timeLabel,
|
'testTime' => $timeLabel,
|
||||||
'data' => $data,
|
'data' => null,
|
||||||
], $paymentFields);
|
], $paymentFields);
|
||||||
break;
|
break;
|
||||||
case 'resume':
|
case 'resume':
|
||||||
@@ -186,7 +207,7 @@ class Test extends BaseController
|
|||||||
'typeName' => '简历综合分析',
|
'typeName' => '简历综合分析',
|
||||||
'resultText'=> $summary ?: '简历综合分析',
|
'resultText'=> $summary ?: '简历综合分析',
|
||||||
'testTime' => $timeLabel,
|
'testTime' => $timeLabel,
|
||||||
'data' => $data,
|
'data' => null,
|
||||||
], $paymentFields);
|
], $paymentFields);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -222,20 +243,28 @@ class Test extends BaseController
|
|||||||
|
|
||||||
$scope = Request::param('scope', 'all'); // all|personal|enterprise
|
$scope = Request::param('scope', 'all'); // all|personal|enterprise
|
||||||
|
|
||||||
|
$allowedAll = $this->wechatAllowedTestTypes($userId);
|
||||||
|
// 「我的」卡片不含简历,但 totalCount 与列表需与 history 权限一致
|
||||||
|
$allowedForRecent = array_values(array_intersect($allowedAll, ['mbti', 'pdp', 'disc', 'face', 'ai']));
|
||||||
|
if ($allowedForRecent === []) {
|
||||||
|
return success([
|
||||||
|
'records' => new \stdClass(),
|
||||||
|
'totalCount' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$records = [];
|
$records = [];
|
||||||
|
|
||||||
// 优化:一次性查询所有需要的最新记录,减少数据库连接和查询次数
|
|
||||||
$query = Db::name('test_results')
|
$query = Db::name('test_results')
|
||||||
->where('userId', $userId);
|
->where('userId', $userId)
|
||||||
|
->whereIn('testType', $allowedForRecent);
|
||||||
|
|
||||||
if ($scope === 'personal') {
|
if ($scope === 'personal') {
|
||||||
$query->whereNull('enterpriseId');
|
$query->whereNull('enterpriseId');
|
||||||
} elseif ($scope === 'enterprise') {
|
} elseif ($scope === 'enterprise') {
|
||||||
$query->whereNotNull('enterpriseId');
|
$query->whereNotNull('enterpriseId');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用子查询或 Union 可能更复杂,这里采用分组取最新的优化思路
|
|
||||||
// 但 ThinkPHP 中最简单有效的优化是先查出所有类型,再处理
|
|
||||||
$allRows = $query->order('createdAt', 'desc')->select()->toArray();
|
$allRows = $query->order('createdAt', 'desc')->select()->toArray();
|
||||||
|
|
||||||
$foundTypes = [];
|
$foundTypes = [];
|
||||||
@@ -264,6 +293,50 @@ class Test extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信用户当前允许的 test_results.testType(绑定企业 permissions + 审核模式;简历仅绑定企业可见)
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
protected function wechatAllowedTestTypes(int $userId): array
|
||||||
|
{
|
||||||
|
$bindRow = Db::name('wechat_users')->where('id', $userId)->field('enterpriseId')->find();
|
||||||
|
$boundEnterpriseId = (int) ($bindRow['enterpriseId'] ?? 0);
|
||||||
|
$enterprisePerms = EnterpriseModel::permissionDefaults();
|
||||||
|
if ($boundEnterpriseId > 0) {
|
||||||
|
$entPermRow = Db::name('enterprises')->where('id', $boundEnterpriseId)->field('permissions')->find();
|
||||||
|
if (is_array($entPermRow)) {
|
||||||
|
$enterprisePerms = EnterpriseModel::normalizePermissionsValue($entPermRow['permissions'] ?? null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$reviewMode = false;
|
||||||
|
$systemRow = Db::name('system_config')->where('key', 'system')->find();
|
||||||
|
if ($systemRow && !empty($systemRow['value'])) {
|
||||||
|
$sysVal = is_string($systemRow['value']) ? json_decode($systemRow['value'], true) : $systemRow['value'];
|
||||||
|
if (is_array($sysVal) && !empty($sysVal['maintenanceMode'])) {
|
||||||
|
$reviewMode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$allowed = [];
|
||||||
|
if (!empty($enterprisePerms['mbti'])) {
|
||||||
|
$allowed[] = 'mbti';
|
||||||
|
}
|
||||||
|
if (!empty($enterprisePerms['pdp'])) {
|
||||||
|
$allowed[] = 'pdp';
|
||||||
|
}
|
||||||
|
if (!empty($enterprisePerms['disc'])) {
|
||||||
|
$allowed[] = 'disc';
|
||||||
|
}
|
||||||
|
if (!empty($enterprisePerms['face']) && !$reviewMode) {
|
||||||
|
$allowed[] = 'face';
|
||||||
|
$allowed[] = 'ai';
|
||||||
|
}
|
||||||
|
if ($boundEnterpriseId > 0) {
|
||||||
|
$allowed[] = 'resume';
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($allowed));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化单条记录为 recent 接口返回结构
|
* 格式化单条记录为 recent 接口返回结构
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ class Enterprise extends BaseController
|
|||||||
* 获取企业列表
|
* 获取企业列表
|
||||||
* @return \think\response\Json
|
* @return \think\response\Json
|
||||||
*/
|
*/
|
||||||
|
private function normalizePerms($raw): array
|
||||||
|
{
|
||||||
|
return EnterpriseModel::normalizePermissionsValue($raw);
|
||||||
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
// 验证是否为超级管理员
|
// 验证是否为超级管理员
|
||||||
@@ -48,6 +53,12 @@ class Enterprise extends BaseController
|
|||||||
->select()
|
->select()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
|
foreach ($list as &$item) {
|
||||||
|
$item['permissions'] = $this->normalizePerms($item['permissions'] ?? null);
|
||||||
|
$item['permissionsCeiling'] = EnterpriseModel::normalizedPermissionsCeiling($item);
|
||||||
|
}
|
||||||
|
unset($item);
|
||||||
|
|
||||||
// 统计每个企业的用户数和测试用量
|
// 统计每个企业的用户数和测试用量
|
||||||
foreach ($list as &$item) {
|
foreach ($list as &$item) {
|
||||||
// 统计用户数(只统计未删除的用户)
|
// 统计用户数(只统计未删除的用户)
|
||||||
@@ -121,15 +132,16 @@ class Enterprise extends BaseController
|
|||||||
$orderPageSize = min(100, max(1, (int) Request::param('orderPageSize', 10)));
|
$orderPageSize = min(100, max(1, (int) Request::param('orderPageSize', 10)));
|
||||||
|
|
||||||
$data = $enterprise->toArray();
|
$data = $enterprise->toArray();
|
||||||
|
$data['permissionsCeiling'] = EnterpriseModel::normalizedPermissionsCeiling($enterprise);
|
||||||
// 获取企业下的所有用户ID(只统计未删除的用户)
|
$data['permissions'] = $this->normalizePerms($data['permissions'] ?? null);
|
||||||
$userIds = Db::name('users')
|
|
||||||
->where('enterpriseId', $id)
|
$eid = (int) $id;
|
||||||
|
|
||||||
|
// 统计后台账号用户数(避免 column 全量 id + 巨大 whereIn)
|
||||||
|
$data['userCount'] = (int) Db::name('users')
|
||||||
|
->where('enterpriseId', $eid)
|
||||||
->where('deletedAt', null)
|
->where('deletedAt', null)
|
||||||
->column('id');
|
->count();
|
||||||
|
|
||||||
// 统计用户数
|
|
||||||
$data['userCount'] = count($userIds);
|
|
||||||
|
|
||||||
// 获取管理员账号列表(企业管理员角色,只获取未删除的)
|
// 获取管理员账号列表(企业管理员角色,只获取未删除的)
|
||||||
$adminAccounts = Db::name('users')
|
$adminAccounts = Db::name('users')
|
||||||
@@ -152,42 +164,45 @@ class Enterprise extends BaseController
|
|||||||
->toArray();
|
->toArray();
|
||||||
$data['users'] = $users;
|
$data['users'] = $users;
|
||||||
|
|
||||||
// 获取测试结果列表
|
// 测试结果(后台 users 归属):JOIN 企业,禁止 whereIn 海量 userId
|
||||||
$testResults = [];
|
$testResults = [];
|
||||||
if (!empty($userIds)) {
|
try {
|
||||||
$testResults = Db::name('test_results')
|
if ($data['userCount'] > 0) {
|
||||||
->alias('tr')
|
$testResults = Db::name('test_results')
|
||||||
->leftJoin('users u', 'tr.userId = u.id')
|
->alias('tr')
|
||||||
->where('tr.userId', 'in', $userIds)
|
->join('users u', 'tr.userId = u.id')
|
||||||
->field('tr.id,tr.testType,tr.createdAt,tr.userId,tr.resultData,u.username')
|
->where('u.enterpriseId', $eid)
|
||||||
->order('tr.createdAt', 'desc')
|
->where('u.deletedAt', null)
|
||||||
->limit(50) // 限制返回数量
|
->field('tr.id,tr.testType,tr.createdAt,tr.userId,tr.resultData,u.username')
|
||||||
->select()
|
->order('tr.createdAt', 'desc')
|
||||||
->toArray();
|
->limit(50)
|
||||||
|
->select()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$testResults = [];
|
||||||
}
|
}
|
||||||
$data['testResults'] = $testResults;
|
$data['testResults'] = $testResults;
|
||||||
$this->attachResultSummaries($data['testResults']);
|
$this->attachResultSummaries($data['testResults']);
|
||||||
|
|
||||||
// 统计测试用量
|
try {
|
||||||
if (!empty($userIds)) {
|
$data['testUsage'] = $data['userCount'] > 0
|
||||||
$data['testUsage'] = Db::name('test_results')
|
? (int) Db::name('test_results')
|
||||||
->where('userId', 'in', $userIds)
|
->alias('tr')
|
||||||
->count();
|
->join('users u', 'tr.userId = u.id')
|
||||||
} else {
|
->where('u.enterpriseId', $eid)
|
||||||
|
->where('u.deletedAt', null)
|
||||||
|
->count()
|
||||||
|
: 0;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
$data['testUsage'] = 0;
|
$data['testUsage'] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// —— 小程序侧用户(wechat_users.enterpriseId)——
|
// —— 小程序侧用户(wechat_users.enterpriseId)——
|
||||||
$wechatIds = [];
|
// 禁止加载全量 wechat user id 到 PHP 再 IN(...) 查 analytics_events(大数据必超时)
|
||||||
try {
|
|
||||||
$wechatIds = Db::name('wechat_users')->where('enterpriseId', $id)->column('id');
|
|
||||||
$wechatIds = array_values(array_filter($wechatIds));
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$wechatIds = [];
|
|
||||||
}
|
|
||||||
$wechatUserTotalCount = 0;
|
$wechatUserTotalCount = 0;
|
||||||
try {
|
try {
|
||||||
$wechatUserTotalCount = (int) Db::name('wechat_users')->where('enterpriseId', $id)->count();
|
$wechatUserTotalCount = (int) Db::name('wechat_users')->where('enterpriseId', $eid)->count();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$wechatUserTotalCount = 0;
|
$wechatUserTotalCount = 0;
|
||||||
}
|
}
|
||||||
@@ -196,7 +211,7 @@ class Enterprise extends BaseController
|
|||||||
$data['wechatUsers'] = [];
|
$data['wechatUsers'] = [];
|
||||||
try {
|
try {
|
||||||
$data['wechatUsers'] = Db::name('wechat_users')
|
$data['wechatUsers'] = Db::name('wechat_users')
|
||||||
->where('enterpriseId', $id)
|
->where('enterpriseId', $eid)
|
||||||
->field('id,openid,nickname,phone,avatar,status,lastLoginAt,createdAt')
|
->field('id,openid,nickname,phone,avatar,status,lastLoginAt,createdAt')
|
||||||
->order('createdAt', 'desc')
|
->order('createdAt', 'desc')
|
||||||
->page($wechatPage, $wechatPageSize)
|
->page($wechatPage, $wechatPageSize)
|
||||||
@@ -210,11 +225,11 @@ class Enterprise extends BaseController
|
|||||||
$data['miniprogramTestResults'] = [];
|
$data['miniprogramTestResults'] = [];
|
||||||
$data['miniprogramTestResultsTotal'] = 0;
|
$data['miniprogramTestResultsTotal'] = 0;
|
||||||
try {
|
try {
|
||||||
$data['miniprogramTestResultsTotal'] = (int) Db::name('test_results')->where('enterpriseId', $id)->count();
|
$data['miniprogramTestResultsTotal'] = (int) Db::name('test_results')->where('enterpriseId', $eid)->count();
|
||||||
$data['miniprogramTestResults'] = Db::name('test_results')
|
$data['miniprogramTestResults'] = Db::name('test_results')
|
||||||
->alias('tr')
|
->alias('tr')
|
||||||
->leftJoin('wechat_users w', 'tr.userId = w.id')
|
->leftJoin('wechat_users w', 'tr.userId = w.id')
|
||||||
->where('tr.enterpriseId', $id)
|
->where('tr.enterpriseId', $eid)
|
||||||
->field('tr.id,tr.testType,tr.createdAt,tr.userId,tr.resultData,w.nickname as wechatNickname')
|
->field('tr.id,tr.testType,tr.createdAt,tr.userId,tr.resultData,w.nickname as wechatNickname')
|
||||||
->order('tr.createdAt', 'desc')
|
->order('tr.createdAt', 'desc')
|
||||||
->page($testPage, $testPageSize)
|
->page($testPage, $testPageSize)
|
||||||
@@ -224,17 +239,17 @@ class Enterprise extends BaseController
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$data['miniprogramTestResults'] = [];
|
$data['miniprogramTestResults'] = [];
|
||||||
}
|
}
|
||||||
// 订单与消耗(金额分)
|
// 订单与消耗(金额分)
|
||||||
$paidStatuses = ['paid', 'completed'];
|
$paidStatuses = ['paid', 'completed'];
|
||||||
try {
|
try {
|
||||||
$data['orderStats'] = [
|
$data['orderStats'] = [
|
||||||
'totalCount' => (int) Db::name('orders')->where('enterpriseId', $id)->count(),
|
'totalCount' => (int) Db::name('orders')->where('enterpriseId', $eid)->count(),
|
||||||
'paidCount' => (int) Db::name('orders')->where('enterpriseId', $id)->whereIn('status', $paidStatuses)->count(),
|
'paidCount' => (int) Db::name('orders')->where('enterpriseId', $eid)->whereIn('status', $paidStatuses)->count(),
|
||||||
'paidAmountFen' => (int) (Db::name('orders')->where('enterpriseId', $id)->whereIn('status', $paidStatuses)->sum('amount') ?? 0),
|
'paidAmountFen' => (int) (Db::name('orders')->where('enterpriseId', $eid)->whereIn('status', $paidStatuses)->sum('amount') ?? 0),
|
||||||
];
|
];
|
||||||
$data['recentOrdersTotal'] = (int) Db::name('orders')->where('enterpriseId', $id)->count();
|
$data['recentOrdersTotal'] = (int) Db::name('orders')->where('enterpriseId', $eid)->count();
|
||||||
$data['recentOrders'] = Db::name('orders')
|
$data['recentOrders'] = Db::name('orders')
|
||||||
->where('enterpriseId', $id)
|
->where('enterpriseId', $eid)
|
||||||
->order('createdAt', 'desc')
|
->order('createdAt', 'desc')
|
||||||
->page($orderPage, $orderPageSize)
|
->page($orderPage, $orderPageSize)
|
||||||
->field('id,orderNo,status,amount,productType,userId,createdAt')
|
->field('id,orderNo,status,amount,productType,userId,createdAt')
|
||||||
@@ -258,23 +273,27 @@ class Enterprise extends BaseController
|
|||||||
'hint' => null,
|
'hint' => null,
|
||||||
'windowDays' => 30,
|
'windowDays' => 30,
|
||||||
];
|
];
|
||||||
if (empty($wechatIds)) {
|
if ($wechatUserTotalCount <= 0) {
|
||||||
$data['analyticsStats']['hint'] = '暂无 enterpriseId 归属该企业的微信小程序用户,无法按企业聚合埋点';
|
$data['analyticsStats']['hint'] = '暂无 enterpriseId 归属该企业的微信小程序用户,无法按企业聚合埋点';
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
$wechatIdSub = Db::name('wechat_users')
|
||||||
|
->whereRaw('enterpriseId = ' . $eid)
|
||||||
|
->field('id')
|
||||||
|
->buildSql(true);
|
||||||
$since = date('Y-m-d H:i:s', time() - 30 * 86400);
|
$since = date('Y-m-d H:i:s', time() - 30 * 86400);
|
||||||
$data['analyticsStats']['eventTotal'] = (int) Db::name('analytics_events')
|
$data['analyticsStats']['eventTotal'] = (int) Db::name('analytics_events')
|
||||||
->where('userId', 'in', $wechatIds)
|
|
||||||
->where('createdAt', '>=', $since)
|
->where('createdAt', '>=', $since)
|
||||||
|
->whereRaw('userId IN ' . $wechatIdSub)
|
||||||
->count();
|
->count();
|
||||||
$data['analyticsStats']['pageViewCount'] = (int) Db::name('analytics_events')
|
$data['analyticsStats']['pageViewCount'] = (int) Db::name('analytics_events')
|
||||||
->where('userId', 'in', $wechatIds)
|
|
||||||
->where('createdAt', '>=', $since)
|
->where('createdAt', '>=', $since)
|
||||||
->where('eventName', 'page_view')
|
->where('eventName', 'page_view')
|
||||||
|
->whereRaw('userId IN ' . $wechatIdSub)
|
||||||
->count();
|
->count();
|
||||||
$byEvent = Db::name('analytics_events')
|
$byEvent = Db::name('analytics_events')
|
||||||
->where('userId', 'in', $wechatIds)
|
|
||||||
->where('createdAt', '>=', $since)
|
->where('createdAt', '>=', $since)
|
||||||
|
->whereRaw('userId IN ' . $wechatIdSub)
|
||||||
->field('eventName, COUNT(*) AS cnt')
|
->field('eventName, COUNT(*) AS cnt')
|
||||||
->group('eventName')
|
->group('eventName')
|
||||||
->order('cnt', 'desc')
|
->order('cnt', 'desc')
|
||||||
@@ -376,6 +395,13 @@ class Enterprise extends BaseController
|
|||||||
$enterprise->balance = $data['balance'] ?? 0.00;
|
$enterprise->balance = $data['balance'] ?? 0.00;
|
||||||
$enterprise->status = $status;
|
$enterprise->status = $status;
|
||||||
$enterprise->trialExpireAt = ($status === 'trial' && isset($data['trialExpireAt'])) ? $data['trialExpireAt'] : null;
|
$enterprise->trialExpireAt = ($status === 'trial' && isset($data['trialExpireAt'])) ? $data['trialExpireAt'] : null;
|
||||||
|
if (isset($data['permissions']) && is_array($data['permissions'])) {
|
||||||
|
$norm = $this->normalizePerms($data['permissions']);
|
||||||
|
} else {
|
||||||
|
$norm = EnterpriseModel::permissionDefaults();
|
||||||
|
}
|
||||||
|
$enterprise->permissions = $norm;
|
||||||
|
$enterprise->permissionsCeiling = $norm;
|
||||||
$enterprise->save();
|
$enterprise->save();
|
||||||
|
|
||||||
$enterpriseId = $enterprise->id;
|
$enterpriseId = $enterprise->id;
|
||||||
@@ -460,6 +486,14 @@ class Enterprise extends BaseController
|
|||||||
$enterprise->trialExpireAt = null;
|
$enterprise->trialExpireAt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($data['permissions']) && is_array($data['permissions'])) {
|
||||||
|
$newCeiling = $this->normalizePerms($data['permissions']);
|
||||||
|
$oldEff = EnterpriseModel::normalizePermissionsValue($enterprise->permissions ?? null);
|
||||||
|
$merged = EnterpriseModel::clampEffectiveToNewCeiling($newCeiling, $oldEff);
|
||||||
|
$enterprise->permissions = $merged;
|
||||||
|
$enterprise->permissionsCeiling = $newCeiling;
|
||||||
|
unset($data['permissions']);
|
||||||
|
}
|
||||||
$enterprise->save($data);
|
$enterprise->save($data);
|
||||||
|
|
||||||
$newBalance = (float) ($enterprise->balance ?? 0);
|
$newBalance = (float) ($enterprise->balance ?? 0);
|
||||||
@@ -472,7 +506,9 @@ class Enterprise extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$enterpriseData = $enterprise->toArray();
|
$enterpriseData = $enterprise->toArray();
|
||||||
|
$enterpriseData['permissionsCeiling'] = EnterpriseModel::normalizedPermissionsCeiling($enterprise);
|
||||||
|
$enterpriseData['permissions'] = $this->normalizePerms($enterpriseData['permissions'] ?? null);
|
||||||
|
|
||||||
// 统计用户数和测试用量(只统计未删除的用户)
|
// 统计用户数和测试用量(只统计未删除的用户)
|
||||||
$enterpriseData['userCount'] = Db::name('users')
|
$enterpriseData['userCount'] = Db::name('users')
|
||||||
->where('enterpriseId', $id)
|
->where('enterpriseId', $id)
|
||||||
|
|||||||
@@ -1,51 +1,166 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace app\model;
|
namespace app\model;
|
||||||
|
|
||||||
use think\Model;
|
use think\Model;
|
||||||
use think\model\concern\SoftDelete;
|
use think\model\concern\SoftDelete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 企业模型
|
* 企业模型
|
||||||
*/
|
*/
|
||||||
class Enterprise extends Model
|
class Enterprise extends Model
|
||||||
{
|
{
|
||||||
use SoftDelete;
|
use SoftDelete;
|
||||||
|
|
||||||
// 设置表名(不带前缀,前缀在数据库配置中设置)
|
// 设置表名(不带前缀,前缀在数据库配置中设置)
|
||||||
protected $name = 'enterprises';
|
protected $name = 'enterprises';
|
||||||
|
|
||||||
// 软删除字段(驼峰命名,时间戳格式)
|
// 软删除字段(驼峰命名,时间戳格式)
|
||||||
protected $deleteTime = 'deletedAt';
|
protected $deleteTime = 'deletedAt';
|
||||||
|
|
||||||
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
// 设置字段信息(匹配数据库字段命名:驼峰命名)
|
||||||
protected $schema = [
|
protected $schema = [
|
||||||
'id' => 'int',
|
'id' => 'int',
|
||||||
'name' => 'string',
|
'name' => 'string',
|
||||||
'code' => 'string',
|
'code' => 'string',
|
||||||
'contactName' => 'string',
|
'contactName' => 'string',
|
||||||
'contactPhone' => 'string',
|
'contactPhone' => 'string',
|
||||||
'contactEmail' => 'string',
|
'contactEmail' => 'string',
|
||||||
'balance' => 'float',
|
'balance' => 'float',
|
||||||
'status' => 'string',
|
'status' => 'string',
|
||||||
'trialExpireAt' => 'int',
|
'permissions' => 'json',
|
||||||
'deletedAt' => 'int',
|
'permissionsCeiling' => 'json',
|
||||||
'createdAt' => 'int',
|
'trialExpireAt' => 'int',
|
||||||
'updatedAt' => 'int',
|
'deletedAt' => 'int',
|
||||||
];
|
'createdAt' => 'int',
|
||||||
|
'updatedAt' => 'int',
|
||||||
// 自动时间戳(使用驼峰命名,时间戳格式)
|
];
|
||||||
protected $autoWriteTimestamp = 'int';
|
|
||||||
|
// 自动时间戳(使用驼峰命名,时间戳格式)
|
||||||
// 时间戳字段名(驼峰命名,匹配数据库)
|
protected $autoWriteTimestamp = 'int';
|
||||||
protected $createTime = 'createdAt';
|
|
||||||
protected $updateTime = 'updatedAt';
|
// 时间戳字段名(驼峰命名,匹配数据库)
|
||||||
|
protected $createTime = 'createdAt';
|
||||||
// 时间字段类型(时间戳格式)
|
protected $updateTime = 'updatedAt';
|
||||||
protected $type = [
|
|
||||||
'trialExpireAt' => 'integer',
|
// 时间字段类型(时间戳格式)
|
||||||
'deletedAt' => 'integer',
|
protected $type = [
|
||||||
'createdAt' => 'integer',
|
'permissions' => 'json',
|
||||||
'updatedAt' => 'integer',
|
'permissionsCeiling' => 'json',
|
||||||
];
|
'trialExpireAt' => 'integer',
|
||||||
}
|
'deletedAt' => 'integer',
|
||||||
|
'createdAt' => 'integer',
|
||||||
|
'updatedAt' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 权限键默认值(库中为 null / 空 / 缺列 / 解析失败时一律回落至此) */
|
||||||
|
public static function permissionDefaults(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'face' => true,
|
||||||
|
'mbti' => true,
|
||||||
|
'pdp' => true,
|
||||||
|
'disc' => true,
|
||||||
|
'distribution' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将库中任意形态的 permissions 规范化为固定键数组(兼容 null、''、{}、[]、非法 JSON、双重 JSON 字符串)
|
||||||
|
* @param mixed $value 原始列值
|
||||||
|
*/
|
||||||
|
public static function normalizePermissionsValue($value): array
|
||||||
|
{
|
||||||
|
$defaults = self::permissionDefaults();
|
||||||
|
if ($value === null) {
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
if (is_string($value)) {
|
||||||
|
$trim = trim($value);
|
||||||
|
if ($trim === '' || strcasecmp($trim, 'null') === 0) {
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
$decoded = json_decode($trim, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
$value = $decoded;
|
||||||
|
}
|
||||||
|
// 双重编码:第一次 decode 后仍是 JSON 字符串
|
||||||
|
if (is_string($value)) {
|
||||||
|
$trim = trim($value);
|
||||||
|
$decoded = $trim === '' ? [] : json_decode($trim, true);
|
||||||
|
$value = (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) ? $decoded : [];
|
||||||
|
}
|
||||||
|
if (is_object($value)) {
|
||||||
|
$value = json_decode(json_encode($value, JSON_UNESCAPED_UNICODE), true);
|
||||||
|
}
|
||||||
|
if (!is_array($value)) {
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
return array_merge($defaults, array_intersect_key($value, $defaults));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取规范化权限(未设置的键默认 true)
|
||||||
|
*/
|
||||||
|
public function getNormalizedPermissions(): array
|
||||||
|
{
|
||||||
|
return self::normalizePermissionsValue($this->permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超管授权上限(未单独配置时回落为当前 permissions,兼容未跑迁移库)
|
||||||
|
*
|
||||||
|
* @param self|array<string,mixed>|null $enterprise 模型或 toArray 行
|
||||||
|
*/
|
||||||
|
public static function normalizedPermissionsCeiling($enterprise): array
|
||||||
|
{
|
||||||
|
if ($enterprise instanceof self) {
|
||||||
|
$raw = $enterprise->getAttr('permissionsCeiling');
|
||||||
|
if ($raw === null || $raw === '') {
|
||||||
|
$raw = $enterprise->getAttr('permissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::normalizePermissionsValue($raw);
|
||||||
|
}
|
||||||
|
if (is_array($enterprise)) {
|
||||||
|
$raw = $enterprise['permissionsCeiling'] ?? null;
|
||||||
|
if ($raw === null || $raw === '') {
|
||||||
|
$raw = $enterprise['permissions'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::normalizePermissionsValue($raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::permissionDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在超管上限内合并企业管理员提交的开关
|
||||||
|
*/
|
||||||
|
public static function clampPermissionsToCeiling(array $ceiling, array $requested): array
|
||||||
|
{
|
||||||
|
$req = self::normalizePermissionsValue($requested);
|
||||||
|
$out = [];
|
||||||
|
foreach (array_keys(self::permissionDefaults()) as $k) {
|
||||||
|
$out[$k] = !empty($ceiling[$k]) && !empty($req[$k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超管收紧上限后,同步收缩已生效 permissions(关掉的项强制 false)
|
||||||
|
*/
|
||||||
|
public static function clampEffectiveToNewCeiling(array $newCeiling, array $oldEffective): array
|
||||||
|
{
|
||||||
|
$old = self::normalizePermissionsValue($oldEffective);
|
||||||
|
$out = [];
|
||||||
|
foreach (array_keys(self::permissionDefaults()) as $k) {
|
||||||
|
$out[$k] = !empty($newCeiling[$k]) && !empty($old[$k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
15
api/database/migrations/_check_add_permissions.php
Normal file
15
api/database/migrations/_check_add_permissions.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
$pdo = new PDO(
|
||||||
|
'mysql:host=56b4c23f6853c.gz.cdb.myqcloud.com;port=14413;dbname=mbti;charset=utf8mb4',
|
||||||
|
'mbti',
|
||||||
|
'Zhiqun1984',
|
||||||
|
[PDO::ATTR_TIMEOUT => 10]
|
||||||
|
);
|
||||||
|
|
||||||
|
$r = $pdo->query("SHOW COLUMNS FROM mbti_enterprises LIKE 'permissions'");
|
||||||
|
if ($r->rowCount() > 0) {
|
||||||
|
echo "Column 'permissions' already exists.\n";
|
||||||
|
} else {
|
||||||
|
$pdo->exec("ALTER TABLE mbti_enterprises ADD COLUMN permissions json NULL COMMENT '功能权限开关' AFTER status");
|
||||||
|
echo "Column 'permissions' added.\n";
|
||||||
|
}
|
||||||
27
api/database/migrations/_run_add_permissions.py
Normal file
27
api/database/migrations/_run_add_permissions.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import pymysql
|
||||||
|
|
||||||
|
conn = pymysql.connect(
|
||||||
|
host='56b4c23f6853c.gz.cdb.myqcloud.com',
|
||||||
|
port=14413,
|
||||||
|
user='mbti',
|
||||||
|
password='Zhiqun1984',
|
||||||
|
database='mbti',
|
||||||
|
charset='utf8mb4'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("SHOW COLUMNS FROM mbti_enterprises LIKE 'permissions'")
|
||||||
|
row = cur.fetchone()
|
||||||
|
if row:
|
||||||
|
print('Column permissions already exists, skipping.')
|
||||||
|
else:
|
||||||
|
cur.execute("""
|
||||||
|
ALTER TABLE mbti_enterprises
|
||||||
|
ADD COLUMN permissions json NULL
|
||||||
|
COMMENT '功能权限开关 {face,mbti,pdp,disc,distribution}'
|
||||||
|
AFTER status
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
print('Column permissions added successfully.')
|
||||||
|
|
||||||
|
conn.close()
|
||||||
5
api/database/migrations/add_enterprise_permissions.sql
Normal file
5
api/database/migrations/add_enterprise_permissions.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
-- 企业权限字段:JSON 存储各功能开关(face/mbti/pdp/disc/distribution)
|
||||||
|
-- 默认全开,未设置时后端视为全部允许
|
||||||
|
ALTER TABLE `mbti_enterprises`
|
||||||
|
ADD COLUMN `permissions` json NULL COMMENT '功能权限开关 {"face":true,"mbti":true,"pdp":true,"disc":true,"distribution":true}'
|
||||||
|
AFTER `status`;
|
||||||
7
api/database/migrations/add_permissions_ceiling.sql
Normal file
7
api/database/migrations/add_permissions_ceiling.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
-- 企业:permissions = 终端生效;permissionsCeiling = 仅超管可改的上限
|
||||||
|
ALTER TABLE mbti_enterprises
|
||||||
|
ADD COLUMN permissionsCeiling json NULL COMMENT '超管功能授权上限' AFTER permissions;
|
||||||
|
|
||||||
|
UPDATE mbti_enterprises
|
||||||
|
SET permissionsCeiling = permissions
|
||||||
|
WHERE permissionsCeiling IS NULL AND permissions IS NOT NULL;
|
||||||
@@ -137,6 +137,10 @@ Route::group('api/v1/admin', function () {
|
|||||||
Route::get('settings/fonts', 'admin.Settings/getFonts');
|
Route::get('settings/fonts', 'admin.Settings/getFonts');
|
||||||
Route::put('settings/credentials', 'admin.Settings/updateCredentials');
|
Route::put('settings/credentials', 'admin.Settings/updateCredentials');
|
||||||
Route::get('settings', 'admin.Settings/index');
|
Route::get('settings', 'admin.Settings/index');
|
||||||
|
|
||||||
|
// 企业功能开关(企业管理员,受超管 permissionsCeiling 约束)
|
||||||
|
Route::get('enterprise/permissions', 'admin.EnterprisePermissions/index');
|
||||||
|
Route::put('enterprise/permissions', 'admin.EnterprisePermissions/update');
|
||||||
|
|
||||||
// 分销管理(企业管理员)
|
// 分销管理(企业管理员)
|
||||||
Route::get('distribution/overview', 'admin.Distribution/overview');
|
Route::get('distribution/overview', 'admin.Distribution/overview');
|
||||||
@@ -248,7 +252,9 @@ Route::group('api/v1/superadmin', function () {
|
|||||||
Route::get('overview/enterprise-ranking', 'superadmin.Overview/enterpriseRanking');
|
Route::get('overview/enterprise-ranking', 'superadmin.Overview/enterpriseRanking');
|
||||||
Route::get('overview/test-trends', 'superadmin.Overview/testTrends');
|
Route::get('overview/test-trends', 'superadmin.Overview/testTrends');
|
||||||
Route::get('overview', 'superadmin.Overview/index');
|
Route::get('overview', 'superadmin.Overview/index');
|
||||||
|
// 超管邀请小程序码(与企业后台同一控制层逻辑,企业版需 enterpriseId 或系统默认企业)
|
||||||
|
Route::get('invite/qrcode', 'admin.Invite/qrcode');
|
||||||
|
|
||||||
// 财务管理(超管专用)
|
// 财务管理(超管专用)
|
||||||
Route::get('finance/overview', 'superadmin.Finance/overview');
|
Route::get('finance/overview', 'superadmin.Finance/overview');
|
||||||
Route::get('finance/revenue-details', 'superadmin.Finance/revenueDetails');
|
Route::get('finance/revenue-details', 'superadmin.Finance/revenueDetails');
|
||||||
|
|||||||
@@ -42,30 +42,88 @@ App({
|
|||||||
pdpResult: null,
|
pdpResult: null,
|
||||||
aiResult: null,
|
aiResult: null,
|
||||||
maintenanceMode: undefined,
|
maintenanceMode: undefined,
|
||||||
reviewMode: undefined
|
reviewMode: undefined,
|
||||||
|
enterprisePermissions: null
|
||||||
},
|
},
|
||||||
|
|
||||||
onLaunch(launchOptions) {
|
onLaunch(launchOptions) {
|
||||||
this.globalData.scene = launchOptions && launchOptions.scene ? launchOptions.scene : ''
|
this.globalData.scene = launchOptions && launchOptions.scene ? launchOptions.scene : ''
|
||||||
this.loadStoredData()
|
this.loadStoredData()
|
||||||
this.silentLogin()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const analytics = require('./utils/analytics.js')
|
const analytics = require('./utils/analytics.js')
|
||||||
analytics.reportAppLaunch(launchOptions)
|
analytics.reportAppLaunch(launchOptions)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
this.getRuntimeConfig().then((cfg) => {
|
this.silentLogin()
|
||||||
if (cfg) {
|
.catch(() => {})
|
||||||
if (cfg.siteTitle) this.globalData.siteTitle = cfg.siteTitle
|
.then(() => this.getRuntimeConfig())
|
||||||
if (cfg.maintenanceMode !== undefined) this.globalData.maintenanceMode = !!cfg.maintenanceMode
|
.then(() => {
|
||||||
if (cfg.reviewMode !== undefined) {
|
this._afterRuntimeSynced()
|
||||||
this.globalData.reviewMode = !!cfg.reviewMode
|
})
|
||||||
} else if (cfg.maintenanceMode !== undefined) {
|
.catch(() => {})
|
||||||
this.globalData.reviewMode = !!cfg.maintenanceMode
|
},
|
||||||
|
|
||||||
|
_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()
|
||||||
|
}
|
||||||
|
} 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 || '神仙团队性格测试',
|
||||||
|
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) {
|
||||||
|
tt.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() {
|
onShow() {
|
||||||
@@ -342,6 +400,7 @@ App({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getRuntimeConfig() {
|
getRuntimeConfig() {
|
||||||
|
const reqId = (this._runtimeReqSeq = (this._runtimeReqSeq || 0) + 1)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const scope = this.globalData.appScope || 'personal'
|
const scope = this.globalData.appScope || 'personal'
|
||||||
const base = this.globalData.apiBase.replace(/\/$/, '')
|
const base = this.globalData.apiBase.replace(/\/$/, '')
|
||||||
@@ -352,6 +411,10 @@ App({
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
header: token ? { Authorization: 'Bearer ' + token } : {},
|
header: token ? { Authorization: 'Bearer ' + token } : {},
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
|
if (reqId !== this._runtimeReqSeq) {
|
||||||
|
resolve(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||||
const data = res.data.data || {}
|
const data = res.data.data || {}
|
||||||
if (data.siteTitle) this.globalData.siteTitle = data.siteTitle
|
if (data.siteTitle) this.globalData.siteTitle = data.siteTitle
|
||||||
@@ -362,6 +425,16 @@ App({
|
|||||||
} else if (data.maintenanceMode !== undefined) {
|
} else if (data.maintenanceMode !== undefined) {
|
||||||
this.globalData.reviewMode = !!data.maintenanceMode
|
this.globalData.reviewMode = !!data.maintenanceMode
|
||||||
}
|
}
|
||||||
|
if (data.defaultEnterpriseId != null && Number(data.defaultEnterpriseId) > 0) {
|
||||||
|
this.globalData.defaultEnterpriseId = Number(data.defaultEnterpriseId)
|
||||||
|
} else {
|
||||||
|
this.globalData.defaultEnterpriseId = null
|
||||||
|
}
|
||||||
|
if (data.enterprisePermissions && typeof data.enterprisePermissions === 'object') {
|
||||||
|
this.globalData.enterprisePermissions = data.enterprisePermissions
|
||||||
|
} else {
|
||||||
|
this.globalData.enterprisePermissions = null
|
||||||
|
}
|
||||||
resolve(data)
|
resolve(data)
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(res.data && res.data.message ? res.data.message : '获取配置失败'))
|
reject(new Error(res.data && res.data.message ? res.data.message : '获取配置失败'))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Component({
|
|||||||
data: {
|
data: {
|
||||||
selected: 0,
|
selected: 0,
|
||||||
reviewMode: false,
|
reviewMode: false,
|
||||||
|
hideMiddleFab: false,
|
||||||
list: [
|
list: [
|
||||||
{ pagePath: '/pages/index/index', text: '首页', textKey: 'home', icon: 'home' },
|
{ pagePath: '/pages/index/index', text: '首页', textKey: 'home', icon: 'home' },
|
||||||
{ pagePath: '/pages/index/camera', text: '拍摄', textKey: 'camera', icon: 'camera' },
|
{ pagePath: '/pages/index/camera', text: '拍摄', textKey: 'camera', icon: 'camera' },
|
||||||
@@ -29,18 +30,20 @@ Component({
|
|||||||
const url = currentPage.route
|
const url = currentPage.route
|
||||||
const gd = getApp().globalData || {}
|
const gd = getApp().globalData || {}
|
||||||
const reviewMode = !!(gd.reviewMode || gd.maintenanceMode)
|
const reviewMode = !!(gd.reviewMode || gd.maintenanceMode)
|
||||||
|
const ep = gd.enterprisePermissions
|
||||||
|
const hideMiddleFab = reviewMode || !!(ep && ep.face === false)
|
||||||
let selected = 0
|
let selected = 0
|
||||||
if (url === 'pages/index/index' || url === 'pages/enterprise/index') {
|
if (url === 'pages/index/index' || url === 'pages/enterprise/index') {
|
||||||
selected = 0
|
selected = 0
|
||||||
} else if (url === 'pages/index/camera') {
|
} else if (url === 'pages/index/camera') {
|
||||||
selected = 1
|
selected = hideMiddleFab ? 0 : 1
|
||||||
} else if (url === 'pages/profile/index') {
|
} else if (url === 'pages/profile/index') {
|
||||||
selected = 2
|
selected = 2
|
||||||
}
|
}
|
||||||
this.setData({ selected, reviewMode })
|
this.setData({ selected, reviewMode, hideMiddleFab })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('updateSelected error:', error)
|
console.error('updateSelected error:', error)
|
||||||
this.setData({ selected: 0, reviewMode: false })
|
this.setData({ selected: 0, reviewMode: false, hideMiddleFab: false })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
switchTab(e) {
|
switchTab(e) {
|
||||||
@@ -48,7 +51,8 @@ Component({
|
|||||||
let url = e.currentTarget.dataset.path
|
let url = e.currentTarget.dataset.path
|
||||||
|
|
||||||
const gd = getApp().globalData || {}
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!-- 自定义 tabBar:与微信端一致,中间固定「拍摄」 -->
|
<!-- 自定义 tabBar:与微信端一致,中间固定「拍摄」 -->
|
||||||
<view class="tab-bar">
|
<view class="tab-bar">
|
||||||
<view class="tab-bar-line"></view>
|
<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
|
<view
|
||||||
class="tab-item {{selected === 0 ? 'active' : ''}}"
|
class="tab-item {{selected === 0 ? 'active' : ''}}"
|
||||||
data-index="0"
|
data-index="0"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<text class="tab-text">首页</text>
|
<text class="tab-text">首页</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="tab-slot-middle" tt:if="{{!reviewMode}}">
|
<view class="tab-slot-middle" tt:if="{{!hideMiddleFab}}">
|
||||||
<view
|
<view
|
||||||
class="middle-fab {{selected === 1 ? 'active' : ''}}"
|
class="middle-fab {{selected === 1 ? 'active' : ''}}"
|
||||||
data-index="1"
|
data-index="1"
|
||||||
|
|||||||
@@ -1,227 +1,238 @@
|
|||||||
// pages/enterprise/index.js - 企业版首页
|
// pages/enterprise/index.js - 企业版首页
|
||||||
const app = getApp()
|
const app = getApp()
|
||||||
const { request } = require('../../utils/request')
|
const { request } = require('../../utils/request')
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
statusBarHeight: 0,
|
statusBarHeight: 0,
|
||||||
navbarHeight: 88,
|
navbarHeight: 88,
|
||||||
siteTitle: '神仙团队性格测试',
|
siteTitle: '神仙团队性格测试',
|
||||||
startButtonEnterprise: '开始性格测试',
|
startButtonEnterprise: '开始性格测试',
|
||||||
aiAnalysisText: '分析',
|
aiAnalysisText: '分析',
|
||||||
reviewMode: true
|
reviewMode: true,
|
||||||
},
|
permFace: true
|
||||||
|
},
|
||||||
onLoad(options) {
|
|
||||||
// 企业版首页:固定 scope=enterprise
|
onLoad(options) {
|
||||||
const app = getApp()
|
// 企业版首页:固定 scope=enterprise
|
||||||
try { app.globalData.appScope = 'enterprise' } catch (e) {}
|
const app = getApp()
|
||||||
|
try { app.globalData.appScope = 'enterprise' } catch (e) {}
|
||||||
// ── 解析入参(兼容两种来源:扫码 scene / 分享链接 options)──
|
|
||||||
const rawScene = (options && options.scene) ? decodeURIComponent(options.scene) : ''
|
// ── 解析入参(兼容两种来源:扫码 scene / 分享链接 options)──
|
||||||
// 解析 scene 中的 key=value 对(如 uid=1&eid=6)
|
const rawScene = (options && options.scene) ? decodeURIComponent(options.scene) : ''
|
||||||
const sceneParams = {}
|
// 解析 scene 中的 key=value 对(如 uid=1&eid=6)
|
||||||
if (rawScene) {
|
const sceneParams = {}
|
||||||
rawScene.split('&').forEach(pair => {
|
if (rawScene) {
|
||||||
const [k, v] = pair.split('=')
|
rawScene.split('&').forEach(pair => {
|
||||||
if (k) sceneParams[k] = v || ''
|
const [k, v] = pair.split('=')
|
||||||
})
|
if (k) sceneParams[k] = v || ''
|
||||||
}
|
})
|
||||||
|
}
|
||||||
// 合并所有来源:scene > options(分享链接)
|
|
||||||
const uid = parseInt(sceneParams.uid || options.uid || 0, 10)
|
// 合并所有来源:scene > options(分享链接)
|
||||||
let eid = parseInt(sceneParams.eid || options.eid || 0, 10)
|
const uid = parseInt(sceneParams.uid || options.uid || 0, 10)
|
||||||
|
let eid = parseInt(sceneParams.eid || options.eid || 0, 10)
|
||||||
// 兼容旧格式 scene: e_企业ID
|
|
||||||
if (!eid && rawScene && rawScene.indexOf('e_') === 0) {
|
// 兼容旧格式 scene: e_企业ID
|
||||||
eid = parseInt(rawScene.slice(2), 10) || 0
|
if (!eid && rawScene && rawScene.indexOf('e_') === 0) {
|
||||||
}
|
eid = parseInt(rawScene.slice(2), 10) || 0
|
||||||
|
}
|
||||||
console.log('[enterprise/onLoad] 解码参数 =>', {
|
|
||||||
rawScene,
|
console.log('[enterprise/onLoad] 解码参数 =>', {
|
||||||
sceneParams,
|
rawScene,
|
||||||
options,
|
sceneParams,
|
||||||
uid,
|
options,
|
||||||
eid
|
uid,
|
||||||
})
|
eid
|
||||||
|
})
|
||||||
if (eid > 0) app.globalData.enterpriseIdFromScene = eid
|
|
||||||
|
if (eid > 0) app.globalData.enterpriseIdFromScene = eid
|
||||||
// 企业版分销绑定:uid > 0 且 eid > 0 时触发
|
|
||||||
if (uid > 0 && eid > 0) {
|
// 企业版分销绑定:uid > 0 且 eid > 0 时触发
|
||||||
app.globalData._pendingInviterId = uid
|
if (uid > 0 && eid > 0) {
|
||||||
app.globalData._pendingInviterScope = 'enterprise'
|
app.globalData._pendingInviterId = uid
|
||||||
app.globalData._pendingInviterEid = eid
|
app.globalData._pendingInviterScope = 'enterprise'
|
||||||
}
|
app.globalData._pendingInviterEid = eid
|
||||||
// 获取状态栏高度和屏幕信息
|
}
|
||||||
const systemInfo = tt.getSystemInfoSync()
|
// 获取状态栏高度和屏幕信息
|
||||||
const statusBarHeight = systemInfo.statusBarHeight || 0
|
const systemInfo = tt.getSystemInfoSync()
|
||||||
const screenWidth = systemInfo.screenWidth || 375
|
const statusBarHeight = systemInfo.statusBarHeight || 0
|
||||||
const statusBarHeightRpx = (statusBarHeight * 750) / screenWidth
|
const screenWidth = systemInfo.screenWidth || 375
|
||||||
const navbarHeightRpx = statusBarHeightRpx + 88
|
const statusBarHeightRpx = (statusBarHeight * 750) / screenWidth
|
||||||
const gd = app.globalData
|
const navbarHeightRpx = statusBarHeightRpx + 88
|
||||||
const rm = !!gd.reviewMode
|
const gd = app.globalData
|
||||||
this.setData({
|
const rm = !!(gd.reviewMode || gd.maintenanceMode)
|
||||||
statusBarHeight: statusBarHeightRpx,
|
const ep = gd.enterprisePermissions
|
||||||
navbarHeight: navbarHeightRpx,
|
const pf = !ep || ep.face !== false
|
||||||
siteTitle: rm ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
this.setData({
|
||||||
startButtonEnterprise: rm ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始性格测试'),
|
statusBarHeight: statusBarHeightRpx,
|
||||||
aiAnalysisText: rm ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
navbarHeight: navbarHeightRpx,
|
||||||
reviewMode: rm
|
siteTitle: rm ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
||||||
})
|
startButtonEnterprise: (rm || !pf) ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始性格测试'),
|
||||||
|
aiAnalysisText: rm ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
||||||
// 未绑定企业的用户:若从邀请码扫码进入(有 enterpriseIdFromScene)也允许使用企业版
|
reviewMode: rm,
|
||||||
const userInfo = app.globalData.userInfo || tt.getStorageSync('userInfo') || {}
|
permFace: pf
|
||||||
const fromInvite = !!app.globalData.enterpriseIdFromScene
|
})
|
||||||
const redirectBack = () => {
|
|
||||||
tt.showToast({ title: '您尚未绑定任何企业,无法使用企业版', icon: 'none', duration: 2500 })
|
// 未绑定企业的用户:若从邀请码扫码进入(有 enterpriseIdFromScene)也允许使用企业版
|
||||||
setTimeout(() => tt.switchTab({ url: '/pages/index/index' }), 600)
|
const userInfo = app.globalData.userInfo || tt.getStorageSync('userInfo') || {}
|
||||||
}
|
const fromInvite = !!app.globalData.enterpriseIdFromScene
|
||||||
// 如果不是通过企业邀请码进入:沿用原有 hasEnterprise 判定
|
const redirectBack = () => {
|
||||||
if (!fromInvite) {
|
tt.showToast({ title: '您尚未绑定任何企业,无法使用企业版', icon: 'none', duration: 2500 })
|
||||||
if (userInfo.hasEnterprise === true) {
|
setTimeout(() => tt.switchTab({ url: '/pages/index/index' }), 600)
|
||||||
app.getRuntimeConfig().then((cfg) => {
|
}
|
||||||
if (cfg && cfg.siteTitle) {
|
// 如果不是通过企业邀请码进入:沿用原有 hasEnterprise 判定
|
||||||
app.globalData.siteTitle = cfg.siteTitle
|
if (!fromInvite) {
|
||||||
this.setData({ siteTitle: cfg.siteTitle })
|
if (userInfo.hasEnterprise === true) {
|
||||||
}
|
app.getRuntimeConfig().then((cfg) => {
|
||||||
}).catch(() => {})
|
if (cfg && cfg.siteTitle) {
|
||||||
return
|
app.globalData.siteTitle = cfg.siteTitle
|
||||||
}
|
this.setData({ siteTitle: cfg.siteTitle })
|
||||||
if (userInfo.hasEnterprise === false) {
|
}
|
||||||
redirectBack()
|
}).catch(() => {})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
if (userInfo.hasEnterprise === false) {
|
||||||
app.ensureLogin()
|
redirectBack()
|
||||||
.then(() => app.getRuntimeConfig())
|
return
|
||||||
.then((cfg) => {
|
}
|
||||||
if (cfg) {
|
}
|
||||||
if (cfg.siteTitle) {
|
app.ensureLogin()
|
||||||
app.globalData.siteTitle = cfg.siteTitle
|
.then(() => app.getRuntimeConfig())
|
||||||
this.setData({ siteTitle: cfg.siteTitle })
|
.then((cfg) => {
|
||||||
}
|
if (cfg) {
|
||||||
if (cfg.textConfig) {
|
if (cfg.siteTitle) {
|
||||||
app.globalData.textConfig = cfg.textConfig
|
app.globalData.siteTitle = cfg.siteTitle
|
||||||
const rm2 = !!app.globalData.reviewMode
|
this.setData({ siteTitle: cfg.siteTitle })
|
||||||
this.setData({
|
}
|
||||||
startButtonEnterprise: rm2 ? '开始性格测试' : (cfg.textConfig.startButtonEnterprise || '开始性格测试'),
|
if (cfg.textConfig) {
|
||||||
aiAnalysisText: rm2 ? '分析' : (cfg.textConfig.aiAnalysisText || '分析')
|
app.globalData.textConfig = cfg.textConfig
|
||||||
})
|
const rm2 = !!(app.globalData.reviewMode || app.globalData.maintenanceMode)
|
||||||
}
|
const ep2 = app.globalData.enterprisePermissions
|
||||||
}
|
const pf2 = !ep2 || ep2.face !== false
|
||||||
if ((cfg && cfg.pricingType) !== 'enterprise' && !app.globalData.enterpriseIdFromScene) {
|
this.setData({
|
||||||
redirectBack()
|
startButtonEnterprise: (rm2 || !pf2) ? '开始性格测试' : (cfg.textConfig.startButtonEnterprise || '开始性格测试'),
|
||||||
return
|
aiAnalysisText: rm2 ? '分析' : (cfg.textConfig.aiAnalysisText || '分析'),
|
||||||
}
|
reviewMode: rm2,
|
||||||
// 若通过企业邀请码进入(带 enterpriseIdFromScene),登录后绑定到 wechat_users.enterpriseId
|
permFace: pf2
|
||||||
const eid = app.globalData.enterpriseIdFromScene
|
})
|
||||||
if (eid) {
|
}
|
||||||
request({
|
}
|
||||||
url: '/api/enterprise/bind',
|
if ((cfg && cfg.pricingType) !== 'enterprise' && !app.globalData.enterpriseIdFromScene) {
|
||||||
method: 'POST',
|
redirectBack()
|
||||||
data: { enterpriseId: eid },
|
return
|
||||||
success(res) {
|
}
|
||||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
// 若通过企业邀请码进入(带 enterpriseIdFromScene),登录后绑定到 wechat_users.enterpriseId
|
||||||
const data = res.data.data || {}
|
const eid = app.globalData.enterpriseIdFromScene
|
||||||
const merged = { ...(app.globalData.userInfo || {}), ...data }
|
if (eid) {
|
||||||
app.globalData.userInfo = merged
|
request({
|
||||||
tt.setStorageSync('userInfo', merged)
|
url: '/api/enterprise/bind',
|
||||||
}
|
method: 'POST',
|
||||||
}
|
data: { enterpriseId: eid },
|
||||||
})
|
success(res) {
|
||||||
}
|
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||||
})
|
const data = res.data.data || {}
|
||||||
.catch(() => {
|
const merged = { ...(app.globalData.userInfo || {}), ...data }
|
||||||
tt.showToast({ title: '请先登录', icon: 'none' })
|
app.globalData.userInfo = merged
|
||||||
setTimeout(() => tt.switchTab({ url: '/pages/index/index' }), 600)
|
tt.setStorageSync('userInfo', merged)
|
||||||
})
|
}
|
||||||
},
|
}
|
||||||
|
})
|
||||||
onShow() {
|
}
|
||||||
const gtab = getApp().globalData || {}
|
})
|
||||||
const auditTb = !!(gtab.reviewMode || gtab.maintenanceMode)
|
.catch(() => {
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
tt.showToast({ title: '请先登录', icon: 'none' })
|
||||||
this.getTabBar().setData({ selected: 0, reviewMode: auditTb })
|
setTimeout(() => tt.switchTab({ url: '/pages/index/index' }), 600)
|
||||||
}
|
})
|
||||||
try { getApp().globalData.appScope = 'enterprise' } catch (e) {}
|
},
|
||||||
const gd = getApp().globalData
|
|
||||||
const rm = !!(gd.reviewMode || gd.maintenanceMode)
|
onShow() {
|
||||||
this.setData({
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||||
siteTitle: rm ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
const tb = this.getTabBar()
|
||||||
startButtonEnterprise: rm ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始性格测试'),
|
if (typeof tb.updateSelected === 'function') tb.updateSelected()
|
||||||
aiAnalysisText: rm ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
}
|
||||||
reviewMode: rm
|
try { getApp().globalData.appScope = 'enterprise' } catch (e) {}
|
||||||
})
|
const gd = getApp().globalData
|
||||||
},
|
const rm = !!(gd.reviewMode || gd.maintenanceMode)
|
||||||
|
const ep4 = gd.enterprisePermissions
|
||||||
// 切换到个人版
|
const pf4 = !ep4 || ep4.face !== false
|
||||||
switchToPersonal() {
|
this.setData({
|
||||||
tt.switchTab({
|
siteTitle: rm ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
||||||
url: '/pages/index/index'
|
startButtonEnterprise: (rm || !pf4) ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始性格测试'),
|
||||||
})
|
aiAnalysisText: rm ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
||||||
},
|
reviewMode: rm,
|
||||||
|
permFace: pf4
|
||||||
startAITest() {
|
})
|
||||||
if (this.data.reviewMode) {
|
},
|
||||||
tt.navigateTo({ url: '/pages/test-select/index' })
|
|
||||||
return
|
// 切换到个人版
|
||||||
}
|
switchToPersonal() {
|
||||||
const eid = (app.globalData && app.globalData.enterpriseIdFromScene) || (app.globalData && app.globalData.userInfo && app.globalData.userInfo.enterpriseId) || (tt.getStorageSync('userInfo') || {}).enterpriseId || null
|
tt.switchTab({
|
||||||
const query = eid ? `?enterpriseId=${eid}&pageSize=1` : '?pageSize=1'
|
url: '/pages/index/index'
|
||||||
request({
|
})
|
||||||
url: '/api/enterprise/resume-uploads' + query,
|
},
|
||||||
method: 'GET',
|
|
||||||
needAuth: true,
|
startAITest() {
|
||||||
success: (res) => {
|
const ep = app.globalData.enterprisePermissions
|
||||||
const list = (res.data && res.data.code === 200 && res.data.data && res.data.data.list) ? res.data.data.list : []
|
if (this.data.reviewMode || (ep && ep.face === false)) {
|
||||||
if (!list.length) {
|
tt.navigateTo({ url: '/pages/test-select/index' })
|
||||||
tt.showModal({
|
return
|
||||||
title: '提示',
|
}
|
||||||
content: '需要先上传简历后再开始测试,请到「我的」-「我的简历」中上传',
|
const eid = (app.globalData && app.globalData.enterpriseIdFromScene) || (app.globalData && app.globalData.userInfo && app.globalData.userInfo.enterpriseId) || (tt.getStorageSync('userInfo') || {}).enterpriseId || null
|
||||||
showCancel: true,
|
const query = eid ? `?enterpriseId=${eid}&pageSize=1` : '?pageSize=1'
|
||||||
confirmText: '去上传',
|
request({
|
||||||
success: (r) => {
|
url: '/api/enterprise/resume-uploads' + query,
|
||||||
if (r.confirm) {
|
method: 'GET',
|
||||||
tt.navigateTo({ url: '/pages/enterprise/resume-history' })
|
needAuth: true,
|
||||||
}
|
success: (res) => {
|
||||||
}
|
const list = (res.data && res.data.code === 200 && res.data.data && res.data.data.list) ? res.data.data.list : []
|
||||||
})
|
if (!list.length) {
|
||||||
return
|
tt.showModal({
|
||||||
}
|
title: '提示',
|
||||||
tt.switchTab({ url: '/pages/index/camera' })
|
content: '需要先上传简历后再开始测试,请到「我的」-「我的简历」中上传',
|
||||||
},
|
showCancel: true,
|
||||||
fail: () => {
|
confirmText: '去上传',
|
||||||
tt.showModal({
|
success: (r) => {
|
||||||
title: '提示',
|
if (r.confirm) {
|
||||||
content: '需要先上传简历后再开始测试,请到「我的」-「我的简历」中上传',
|
tt.navigateTo({ url: '/pages/enterprise/resume-history' })
|
||||||
showCancel: true,
|
}
|
||||||
confirmText: '去上传',
|
}
|
||||||
success: (r) => {
|
})
|
||||||
if (r.confirm) {
|
return
|
||||||
tt.navigateTo({ url: '/pages/enterprise/resume-history' })
|
}
|
||||||
}
|
tt.switchTab({ url: '/pages/index/camera' })
|
||||||
}
|
},
|
||||||
})
|
fail: () => {
|
||||||
}
|
tt.showModal({
|
||||||
})
|
title: '提示',
|
||||||
},
|
content: '需要先上传简历后再开始测试,请到「我的」-「我的简历」中上传',
|
||||||
|
showCancel: true,
|
||||||
onShareAppMessage() {
|
confirmText: '去上传',
|
||||||
const { getSharePath } = require('../../utils/share')
|
success: (r) => {
|
||||||
const rm = this.data.reviewMode
|
if (r.confirm) {
|
||||||
return {
|
tt.navigateTo({ url: '/pages/enterprise/resume-history' })
|
||||||
title: rm ? '神仙团队性格测试 (企业版) - 团队分析与优化' : '神仙团队性格测试 (企业版) - 团队分析与优化',
|
}
|
||||||
path: getSharePath('/pages/enterprise/index')
|
}
|
||||||
}
|
})
|
||||||
},
|
}
|
||||||
|
})
|
||||||
onShareTimeline() {
|
},
|
||||||
const { buildShareQuery } = require('../../utils/share')
|
|
||||||
const rm = this.data.reviewMode
|
onShareAppMessage() {
|
||||||
return {
|
const { getSharePath } = require('../../utils/share')
|
||||||
title: rm ? '神仙团队性格测试 (企业版) - 团队分析与优化' : '神仙团队性格测试 (企业版) - 团队分析与优化',
|
const rm = this.data.reviewMode
|
||||||
query: buildShareQuery()
|
return {
|
||||||
}
|
title: rm ? '神仙团队性格测试 (企业版) - 团队分析与优化' : '神仙团队性格测试 (企业版) - 团队分析与优化',
|
||||||
}
|
path: getSharePath('/pages/enterprise/index')
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onShareTimeline() {
|
||||||
|
const { buildShareQuery } = require('../../utils/share')
|
||||||
|
const rm = this.data.reviewMode
|
||||||
|
return {
|
||||||
|
title: rm ? '神仙团队性格测试 (企业版) - 团队分析与优化' : '神仙团队性格测试 (企业版) - 团队分析与优化',
|
||||||
|
query: buildShareQuery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,80 +1,80 @@
|
|||||||
<!--pages/enterprise/index.wxml - 企业版首页-->
|
<!--pages/enterprise/index.wxml - 企业版首页-->
|
||||||
<view class="container" style="padding-top: {{navbarHeight}}px;">
|
<view class="container" style="padding-top: {{navbarHeight}}px;">
|
||||||
<!-- 自定义导航栏 -->
|
<!-- 自定义导航栏 -->
|
||||||
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
|
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
|
||||||
<view class="navbar-content">
|
<view class="navbar-content">
|
||||||
<view class="switch-personal-btn" bindtap="switchToPersonal">
|
<view class="switch-personal-btn" bindtap="switchToPersonal">
|
||||||
<text class="personal-icon">👤</text>
|
<text class="personal-icon">👤</text>
|
||||||
<text class="personal-text">个人版</text>
|
<text class="personal-text">个人版</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="navbar-title">{{siteTitle || '神仙团队性格测试'}}</view>
|
<view class="navbar-title">{{siteTitle || '神仙团队性格测试'}}</view>
|
||||||
<view class="navbar-placeholder"></view>
|
<view class="navbar-placeholder"></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 背景装饰 -->
|
<!-- 背景装饰 -->
|
||||||
<view class="bg-decoration bg-top-right"></view>
|
<view class="bg-decoration bg-top-right"></view>
|
||||||
<view class="bg-decoration bg-bottom-left"></view>
|
<view class="bg-decoration bg-bottom-left"></view>
|
||||||
|
|
||||||
<view class="top-image-section">
|
<view class="top-image-section">
|
||||||
<view class="image-container">
|
<view class="image-container">
|
||||||
<view class="image-wrapper">
|
<view class="image-wrapper">
|
||||||
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="float-tag tag-1" tt:if="{{!reviewMode}}">面相分析</view>
|
<view class="float-tag tag-1" tt:if="{{!reviewMode && permFace}}">面相分析</view>
|
||||||
<view class="float-tag tag-2" tt:if="{{!reviewMode}}">骨相分析</view>
|
<view class="float-tag tag-2" tt:if="{{!reviewMode && permFace}}">骨相分析</view>
|
||||||
<view class="float-tag tag-3">性格测评</view>
|
<view class="float-tag tag-3">性格测评</view>
|
||||||
<view class="float-tag tag-1" tt:if="{{reviewMode}}">MBTI</view>
|
<view class="float-tag tag-1" tt:if="{{reviewMode}}">MBTI</view>
|
||||||
<view class="float-tag tag-2" tt:if="{{reviewMode}}">DISC</view>
|
<view class="float-tag tag-2" tt:if="{{reviewMode}}">DISC</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="process-section">
|
<view class="process-section">
|
||||||
<view class="section-title">测试流程</view>
|
<view class="section-title">测试流程</view>
|
||||||
<view class="process-steps" tt:if="{{!reviewMode}}">
|
<view class="process-steps" tt:if="{{!reviewMode && permFace}}">
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle active">1</view>
|
<view class="step-circle active">1</view>
|
||||||
<text class="step-label">STEP1</text>
|
<text class="step-label">STEP1</text>
|
||||||
<text class="step-text">拍摄照片</text>
|
<text class="step-text">拍摄照片</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="step-line"></view>
|
<view class="step-line"></view>
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle">2</view>
|
<view class="step-circle">2</view>
|
||||||
<text class="step-label">STEP2</text>
|
<text class="step-label">STEP2</text>
|
||||||
<text class="step-text">{{aiAnalysisText || '分析'}}</text>
|
<text class="step-text">{{aiAnalysisText || '分析'}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="step-line"></view>
|
<view class="step-line"></view>
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle">3</view>
|
<view class="step-circle">3</view>
|
||||||
<text class="step-label">STEP3</text>
|
<text class="step-label">STEP3</text>
|
||||||
<text class="step-text">生成报告</text>
|
<text class="step-text">生成报告</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="process-steps" tt:if="{{reviewMode}}">
|
<view class="process-steps" tt:if="{{reviewMode || !permFace}}">
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle active">1</view>
|
<view class="step-circle active">1</view>
|
||||||
<text class="step-label">STEP1</text>
|
<text class="step-label">STEP1</text>
|
||||||
<text class="step-text">选择测试</text>
|
<text class="step-text">选择测试</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="step-line"></view>
|
<view class="step-line"></view>
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle">2</view>
|
<view class="step-circle">2</view>
|
||||||
<text class="step-label">STEP2</text>
|
<text class="step-label">STEP2</text>
|
||||||
<text class="step-text">回答问题</text>
|
<text class="step-text">回答问题</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="step-line"></view>
|
<view class="step-line"></view>
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle">3</view>
|
<view class="step-circle">3</view>
|
||||||
<text class="step-label">STEP3</text>
|
<text class="step-label">STEP3</text>
|
||||||
<text class="step-text">查看结果</text>
|
<text class="step-text">查看结果</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="start-button" bindtap="startAITest">
|
<view class="start-button" bindtap="startAITest">
|
||||||
<text class="button-text">{{startButtonEnterprise || '开始性格测试'}}</text>
|
<text class="button-text">{{startButtonEnterprise || '开始性格测试'}}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 自定义底部导航 -->
|
<!-- 自定义底部导航 -->
|
||||||
<custom-tab-bar />
|
<custom-tab-bar />
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -7,9 +7,16 @@ Page({
|
|||||||
tabName: '',
|
tabName: '',
|
||||||
list: [],
|
list: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
hasMore: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
isEnterprise: false,
|
isEnterprise: false,
|
||||||
reviewMode: false
|
reviewMode: false,
|
||||||
|
permMbti: true,
|
||||||
|
permPdp: true,
|
||||||
|
permDisc: true,
|
||||||
|
permFace: true
|
||||||
},
|
},
|
||||||
|
|
||||||
_checkIsEnterprise() {
|
_checkIsEnterprise() {
|
||||||
@@ -23,54 +30,110 @@ Page({
|
|||||||
return scope === 'enterprise' || !!enterpriseId
|
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 isEnterprise = this._checkIsEnterprise()
|
||||||
|
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'
|
||||||
|
if (activeTab === 'resume' && !isEnterprise) activeTab = 'all'
|
||||||
|
this.setData({
|
||||||
|
isEnterprise,
|
||||||
|
permMbti, permPdp, permDisc, permFace, reviewMode,
|
||||||
|
activeTab,
|
||||||
|
tabName: names[activeTab] || ''
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
onLoad() {
|
onLoad() {
|
||||||
this.setData({ isEnterprise: this._checkIsEnterprise() })
|
this._syncPermsAndTab()
|
||||||
this.loadAll()
|
this.loadAll()
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
this.setData({ isEnterprise: this._checkIsEnterprise(), reviewMode: !!app.globalData.reviewMode })
|
this._syncPermsAndTab()
|
||||||
this.loadAll()
|
this.loadAll()
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
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)
|
|
||||||
loadAll() {
|
loadAll() {
|
||||||
if (this.data.loading) return
|
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 || tt.getStorageSync('token')
|
const token = app.globalData.token || tt.getStorageSync('token')
|
||||||
const apiBase = app.globalData.apiBase
|
const apiBase = app.globalData.apiBase
|
||||||
const { activeTab } = this.data
|
const { activeTab } = this.data
|
||||||
const typeParam = activeTab === 'all' ? '' : `&type=${activeTab}`
|
const typeParam = activeTab === 'all' ? '' : `&type=${activeTab}`
|
||||||
|
|
||||||
if (!token || !apiBase) {
|
if (!token || !apiBase) {
|
||||||
this.setData({ loading: false })
|
this.setData({ loading: false, loadingMore: false })
|
||||||
this.loadFromStorage()
|
this.loadFromStorage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tt.request({
|
tt.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',
|
method: 'GET',
|
||||||
header: { Authorization: `Bearer ${token}` },
|
header: { Authorization: `Bearer ${token}` },
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.statusCode === 200 && res.data && res.data.data) {
|
if (res.statusCode === 200 && res.data && res.data.data) {
|
||||||
const payload = res.data.data
|
const payload = res.data.data
|
||||||
const rawList = Array.isArray(payload) ? payload : (payload.list || [])
|
const rawList = payload.list || []
|
||||||
const total = Array.isArray(payload) ? rawList.length : (payload.total || 0)
|
|
||||||
const formatted = this.formatList(rawList)
|
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 {
|
} else {
|
||||||
this.setData({ loading: false })
|
this.setData({ loading: false, loadingMore: false })
|
||||||
this.loadFromStorage()
|
if (!append) this.loadFromStorage()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fail: () => {
|
fail: () => {
|
||||||
this.setData({ loading: false })
|
this.setData({ loading: false, loadingMore: false })
|
||||||
this.loadFromStorage()
|
if (!append) this.loadFromStorage()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -81,7 +144,8 @@ Page({
|
|||||||
|
|
||||||
return rawList.map((item, idx) => {
|
return rawList.map((item, idx) => {
|
||||||
if (item.typeName) {
|
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 testType = (item.testType || item.type || 'mbti').toLowerCase()
|
||||||
const ts = item.createdAt || item.testTime || item.timestamp
|
const ts = item.createdAt || item.testTime || item.timestamp
|
||||||
@@ -99,16 +163,17 @@ Page({
|
|||||||
|
|
||||||
// 本地缓存回退
|
// 本地缓存回退
|
||||||
loadFromStorage() {
|
loadFromStorage() {
|
||||||
|
const { permMbti, permPdp, permDisc, permFace, reviewMode } = this.data
|
||||||
const mbtiResult = tt.getStorageSync('mbtiResult')
|
const mbtiResult = tt.getStorageSync('mbtiResult')
|
||||||
const discResult = tt.getStorageSync('discResult')
|
const discResult = tt.getStorageSync('discResult')
|
||||||
const pdpResult = tt.getStorageSync('pdpResult')
|
const pdpResult = tt.getStorageSync('pdpResult')
|
||||||
const aiResult = tt.getStorageSync('aiResult')
|
const aiResult = tt.getStorageSync('aiResult')
|
||||||
const list = []
|
const list = []
|
||||||
if (mbtiResult) list.push({ type: 'mbti', key: 'mbti', emoji: '🧠', typeName: 'MBTI性格测试', resultText: mbtiResult.mbtiType || '未知', testTime: this.formatTime(mbtiResult.timestamp), data: mbtiResult })
|
if (mbtiResult && permMbti) 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 (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) list.push({ type: 'disc', key: 'disc', emoji: '📊', typeName: 'DISC性格测试', resultText: (discResult.dominantType || '未知') + '型', testTime: this.formatTime(discResult.timestamp || discResult.completedAt), data: discResult })
|
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 && !app.globalData.reviewMode) list.push({ type: 'ai', key: 'ai', emoji: '👁️', typeName: '面相分析', resultText: aiResult.mbti || '未知', testTime: this.formatTime(aiResult.timestamp || aiResult.completedAt), data: aiResult })
|
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, loading: false })
|
this.setData({ list, total: list.length, page: 1, hasMore: false, loading: false, loadingMore: false })
|
||||||
},
|
},
|
||||||
|
|
||||||
changeTab(e) {
|
changeTab(e) {
|
||||||
|
|||||||
@@ -7,16 +7,16 @@
|
|||||||
<view class="tab-item {{activeTab === 'all' ? 'active' : ''}}" bindtap="changeTab" data-tab="all">
|
<view class="tab-item {{activeTab === 'all' ? 'active' : ''}}" bindtap="changeTab" data-tab="all">
|
||||||
<text class="tab-text">全部</text>
|
<text class="tab-text">全部</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tab-item {{activeTab === 'mbti' ? 'active' : ''}}" bindtap="changeTab" data-tab="mbti">
|
<view tt:if="{{permMbti}}" class="tab-item {{activeTab === 'mbti' ? 'active' : ''}}" bindtap="changeTab" data-tab="mbti">
|
||||||
<text class="tab-text">MBTI</text>
|
<text class="tab-text">MBTI</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tab-item {{activeTab === 'pdp' ? 'active' : ''}}" bindtap="changeTab" data-tab="pdp">
|
<view tt:if="{{permPdp}}" class="tab-item {{activeTab === 'pdp' ? 'active' : ''}}" bindtap="changeTab" data-tab="pdp">
|
||||||
<text class="tab-text">PDP</text>
|
<text class="tab-text">PDP</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tab-item {{activeTab === 'disc' ? 'active' : ''}}" bindtap="changeTab" data-tab="disc">
|
<view tt:if="{{permDisc}}" class="tab-item {{activeTab === 'disc' ? 'active' : ''}}" bindtap="changeTab" data-tab="disc">
|
||||||
<text class="tab-text">DISC</text>
|
<text class="tab-text">DISC</text>
|
||||||
</view>
|
</view>
|
||||||
<view tt:if="{{!reviewMode}}" class="tab-item {{activeTab === 'ai' ? 'active' : ''}}" bindtap="changeTab" data-tab="ai">
|
<view tt:if="{{!reviewMode && permFace}}" class="tab-item {{activeTab === 'ai' ? 'active' : ''}}" bindtap="changeTab" data-tab="ai">
|
||||||
<text class="tab-text">面相</text>
|
<text class="tab-text">面相</text>
|
||||||
</view>
|
</view>
|
||||||
<view tt:if="{{isEnterprise}}" class="tab-item {{activeTab === 'resume' ? 'active' : ''}}" bindtap="changeTab" data-tab="resume">
|
<view tt:if="{{isEnterprise}}" class="tab-item {{activeTab === 'resume' ? 'active' : ''}}" bindtap="changeTab" data-tab="resume">
|
||||||
@@ -57,11 +57,12 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 加载中 -->
|
<view class="load-more-row" tt:if="{{loadingMore}}">
|
||||||
<view class="load-more-row" tt:if="{{loading}}">
|
<text class="load-more-text">加载更多...</text>
|
||||||
<text class="load-more-text">加载中...</text>
|
</view>
|
||||||
|
<view tt:elif="{{hasMore && list.length > 0}}" class="load-more-row">
|
||||||
|
<text class="load-more-text">上拉或继续滑动加载更多</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view tt:elif="{{list.length > 0}}" class="load-more-row">
|
<view tt:elif="{{list.length > 0}}" class="load-more-row">
|
||||||
<text class="load-more-text no-more">共 {{total}} 条</text>
|
<text class="load-more-text no-more">共 {{total}} 条</text>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -71,15 +71,21 @@ Page({
|
|||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
const rm = _auditFlag()
|
const rm = _auditFlag()
|
||||||
|
const faceOff = !!(app.globalData.enterprisePermissions && app.globalData.enterprisePermissions.face === false)
|
||||||
this.setData({ reviewMode: rm })
|
this.setData({ reviewMode: rm })
|
||||||
|
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rm) {
|
if (rm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (faceOff) {
|
||||||
|
tt.navigateTo({ url: '/pages/test-select/index' })
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!ensureProfileCompleteAndRedirect()) return
|
if (!ensureProfileCompleteAndRedirect()) return
|
||||||
if (this.data.photos.length < 3) {
|
if (this.data.photos.length < 3) {
|
||||||
this.initCameraContext()
|
this.initCameraContext()
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ Page({
|
|||||||
siteTitle: '神仙团队性格测试',
|
siteTitle: '神仙团队性格测试',
|
||||||
startButtonText: '开始性格测试',
|
startButtonText: '开始性格测试',
|
||||||
aiAnalysisText: '分析',
|
aiAnalysisText: '分析',
|
||||||
reviewMode: true
|
reviewMode: true,
|
||||||
|
permFace: true
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
@@ -25,6 +26,8 @@ Page({
|
|||||||
const userInfo = getApp().globalData.userInfo || tt.getStorageSync('userInfo') || {}
|
const userInfo = getApp().globalData.userInfo || tt.getStorageSync('userInfo') || {}
|
||||||
const gd = getApp().globalData
|
const gd = getApp().globalData
|
||||||
const rm0 = !!(gd.reviewMode || gd.maintenanceMode)
|
const rm0 = !!(gd.reviewMode || gd.maintenanceMode)
|
||||||
|
const ep = gd.enterprisePermissions
|
||||||
|
const pf = !ep || ep.face !== false
|
||||||
this.setData({
|
this.setData({
|
||||||
statusBarHeight: statusBarHeightRpx,
|
statusBarHeight: statusBarHeightRpx,
|
||||||
navbarHeight: navbarHeightRpx,
|
navbarHeight: navbarHeightRpx,
|
||||||
@@ -32,7 +35,8 @@ Page({
|
|||||||
siteTitle: rm0 ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
siteTitle: rm0 ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
||||||
startButtonText: rm0 ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '拍摄'),
|
startButtonText: rm0 ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '拍摄'),
|
||||||
aiAnalysisText: rm0 ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
aiAnalysisText: rm0 ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
||||||
reviewMode: rm0
|
reviewMode: rm0,
|
||||||
|
permFace: pf
|
||||||
})
|
})
|
||||||
// 预加载站点名称与文案配置
|
// 预加载站点名称与文案配置
|
||||||
app.getRuntimeConfig().then((cfg) => {
|
app.getRuntimeConfig().then((cfg) => {
|
||||||
@@ -53,7 +57,8 @@ Page({
|
|||||||
aiAnalysisText: rm ? '分析' : (cfg.textConfig.aiAnalysisText || '分析')
|
aiAnalysisText: rm ? '分析' : (cfg.textConfig.aiAnalysisText || '分析')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.setData({ reviewMode: rm })
|
const ep2 = getApp().globalData.enterprisePermissions
|
||||||
|
this.setData({ reviewMode: rm, permFace: !ep2 || ep2.face !== false })
|
||||||
try {
|
try {
|
||||||
const tb = typeof this.getTabBar === 'function' ? this.getTabBar() : null
|
const tb = typeof this.getTabBar === 'function' ? this.getTabBar() : null
|
||||||
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
|
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
|
||||||
@@ -97,7 +102,8 @@ Page({
|
|||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||||
this.getTabBar().setData({ selected: 0 })
|
const tb = this.getTabBar()
|
||||||
|
if (typeof tb.updateSelected === 'function') tb.updateSelected()
|
||||||
}
|
}
|
||||||
// 个人版首页:固定 scope=personal,并清除企业来源上下文
|
// 个人版首页:固定 scope=personal,并清除企业来源上下文
|
||||||
try {
|
try {
|
||||||
@@ -106,11 +112,13 @@ Page({
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
const g = getApp().globalData
|
const g = getApp().globalData
|
||||||
const rm = !!(g.reviewMode || g.maintenanceMode)
|
const rm = !!(g.reviewMode || g.maintenanceMode)
|
||||||
|
const ep3 = g.enterprisePermissions
|
||||||
this.setData({
|
this.setData({
|
||||||
siteTitle: rm ? (g.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (g.siteTitle || '神仙团队性格测试'),
|
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) || '分析'),
|
aiAnalysisText: rm ? '分析' : ((g.textConfig && g.textConfig.aiAnalysisText) || '分析'),
|
||||||
reviewMode: rm
|
reviewMode: rm,
|
||||||
|
permFace: !ep3 || ep3.face !== false
|
||||||
})
|
})
|
||||||
const userInfo = getApp().globalData.userInfo || tt.getStorageSync('userInfo') || {}
|
const userInfo = getApp().globalData.userInfo || tt.getStorageSync('userInfo') || {}
|
||||||
this.setData({ showEnterpriseEntry: userInfo.hasEnterprise === true })
|
this.setData({ showEnterpriseEntry: userInfo.hasEnterprise === true })
|
||||||
@@ -135,7 +143,9 @@ Page({
|
|||||||
startCamera() {
|
startCamera() {
|
||||||
try { getApp().globalData.appScope = 'personal' } catch (e) {}
|
try { getApp().globalData.appScope = 'personal' } catch (e) {}
|
||||||
const gd = getApp().globalData
|
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) {
|
||||||
tt.navigateTo({ url: '/pages/test-select/index' })
|
tt.navigateTo({ url: '/pages/test-select/index' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- 审核模式:只显示性格测评标签 -->
|
<!-- 审核模式:只显示性格测评标签 -->
|
||||||
<view class="float-tag tag-1" tt:if="{{!reviewMode}}">面相分析</view>
|
<view class="float-tag tag-1" tt:if="{{!reviewMode && permFace}}">面相分析</view>
|
||||||
<view class="float-tag tag-2" tt:if="{{!reviewMode}}">骨相分析</view>
|
<view class="float-tag tag-2" tt:if="{{!reviewMode && permFace}}">骨相分析</view>
|
||||||
<view class="float-tag tag-3">性格测评</view>
|
<view class="float-tag tag-3">性格测评</view>
|
||||||
<view class="float-tag tag-1" tt:if="{{reviewMode}}">MBTI</view>
|
<view class="float-tag tag-1" tt:if="{{reviewMode}}">MBTI</view>
|
||||||
<view class="float-tag tag-2" tt:if="{{reviewMode}}">DISC</view>
|
<view class="float-tag tag-2" tt:if="{{reviewMode}}">DISC</view>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
<view class="process-section">
|
<view class="process-section">
|
||||||
<view class="section-title">测试流程</view>
|
<view class="section-title">测试流程</view>
|
||||||
<view class="process-steps" tt:if="{{!reviewMode}}">
|
<view class="process-steps" tt:if="{{!reviewMode && permFace}}">
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle active">1</view>
|
<view class="step-circle active">1</view>
|
||||||
<text class="step-label">STEP1</text>
|
<text class="step-label">STEP1</text>
|
||||||
@@ -51,8 +51,8 @@
|
|||||||
<text class="step-text">生成报告</text>
|
<text class="step-text">生成报告</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 审核模式:纯问卷流程 -->
|
<!-- 审核模式或 face 权限关闭:纯问卷流程 -->
|
||||||
<view class="process-steps" tt:if="{{reviewMode}}">
|
<view class="process-steps" tt:if="{{reviewMode || !permFace}}">
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle active">1</view>
|
<view class="step-circle active">1</view>
|
||||||
<text class="step-label">STEP1</text>
|
<text class="step-label">STEP1</text>
|
||||||
|
|||||||
@@ -35,13 +35,15 @@ Page({
|
|||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
const audit = !!(app.globalData.reviewMode || app.globalData.maintenanceMode)
|
const audit = !!(app.globalData.reviewMode || app.globalData.maintenanceMode)
|
||||||
if (audit) {
|
const faceOff = !!(app.globalData.enterprisePermissions && app.globalData.enterprisePermissions.face === false)
|
||||||
|
if (audit || faceOff) {
|
||||||
tt.navigateTo({ url: '/pages/test-select/index' })
|
tt.navigateTo({ url: '/pages/test-select/index' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!ensureProfileCompleteAndRedirect()) return
|
if (!ensureProfileCompleteAndRedirect()) return
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
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() })
|
this.setData({ needPhoneAuth: !hasPhone() })
|
||||||
const tc = app.globalData.textConfig
|
const tc = app.globalData.textConfig
|
||||||
|
|||||||
@@ -39,24 +39,62 @@ Page({
|
|||||||
promoTotalEarned: '0.00',
|
promoTotalEarned: '0.00',
|
||||||
promoWithdrawable: '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() {
|
||||||
// 仅在 onLoad 执行一次,onShow 里的 runLoginThenLoad 会导致重复请求
|
// 仅在 onLoad 执行一次,onShow 里的 runLoginThenLoad 会导致重复请求
|
||||||
this.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() {
|
onShow() {
|
||||||
const gd = app.globalData
|
const gd = app.globalData
|
||||||
this.setData({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
|
this._syncPerms({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
|
||||||
// 如果是从其他页面返回,且已经登录,则只刷新数据而不重新执行登录流程
|
// 如果是从其他页面返回,且已经登录,则只刷新数据而不重新执行登录流程
|
||||||
if (this.data.hasLogin) {
|
if (this.data.hasLogin) {
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||||
const audit = !!(gd.reviewMode || gd.maintenanceMode)
|
const tb = this.getTabBar()
|
||||||
this.getTabBar().setData({ selected: 2, reviewMode: audit })
|
if (typeof tb.updateSelected === 'function') tb.updateSelected()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -106,6 +144,9 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadData() {
|
loadData() {
|
||||||
|
const gd = app.globalData
|
||||||
|
this._syncPerms({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
|
||||||
|
|
||||||
const userInfo = app.globalData.userInfo || tt.getStorageSync('userInfo')
|
const userInfo = app.globalData.userInfo || tt.getStorageSync('userInfo')
|
||||||
const token = app.globalData.token || tt.getStorageSync('token')
|
const token = app.globalData.token || tt.getStorageSync('token')
|
||||||
|
|
||||||
@@ -160,7 +201,7 @@ Page({
|
|||||||
const discType = r.disc ? r.disc.resultText.replace(/型$/, '') : ''
|
const discType = r.disc ? r.disc.resultText.replace(/型$/, '') : ''
|
||||||
|
|
||||||
const gallupPreview = (r.ai && r.ai.gallupPreview) ? String(r.ai.gallupPreview) : ''
|
const gallupPreview = (r.ai && r.ai.gallupPreview) ? String(r.ai.gallupPreview) : ''
|
||||||
this.setData({
|
const patch = {
|
||||||
testCount: totalCount,
|
testCount: totalCount,
|
||||||
hasResults: !!(r.mbti || r.disc || r.pdp || r.ai),
|
hasResults: !!(r.mbti || r.disc || r.pdp || r.ai),
|
||||||
mbtiType: r.mbti ? r.mbti.resultText : '',
|
mbtiType: r.mbti ? r.mbti.resultText : '',
|
||||||
@@ -176,6 +217,12 @@ Page({
|
|||||||
discResultId: r.disc ? r.disc.id : null,
|
discResultId: r.disc ? r.disc.id : null,
|
||||||
pdpResultId: r.pdp ? r.pdp.id : null,
|
pdpResultId: r.pdp ? r.pdp.id : null,
|
||||||
aiResultId: r.ai ? r.ai.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: () => {
|
fail: () => {
|
||||||
@@ -230,7 +277,7 @@ Page({
|
|||||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setData({
|
const patch = {
|
||||||
testCount,
|
testCount,
|
||||||
hasResults: testCount > 0,
|
hasResults: testCount > 0,
|
||||||
mbtiType: mbtiResult ? getTypeOnly(mbtiResult, 'mbti') : '',
|
mbtiType: mbtiResult ? getTypeOnly(mbtiResult, 'mbti') : '',
|
||||||
@@ -246,6 +293,12 @@ Page({
|
|||||||
discResultId: null,
|
discResultId: null,
|
||||||
pdpResultId: null,
|
pdpResultId: null,
|
||||||
aiResultId: null,
|
aiResultId: null,
|
||||||
|
}
|
||||||
|
const d = { ...this.data, ...patch }
|
||||||
|
this.setData({
|
||||||
|
...patch,
|
||||||
|
showLatestTestCards: this._computeShowLatestTestCards(d),
|
||||||
|
showEmptyPersonalityTags: this._computeShowEmptyPersonalityTags(d)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -49,16 +49,16 @@
|
|||||||
<!-- 类型标签 -->
|
<!-- 类型标签 -->
|
||||||
<scroll-view scroll-x class="tags-scroll">
|
<scroll-view scroll-x class="tags-scroll">
|
||||||
<view class="tags-row">
|
<view class="tags-row">
|
||||||
<view class="tag tag-purple" tt:if="{{mbtiType}}">
|
<view class="tag tag-purple" tt:if="{{mbtiType && permMbti}}">
|
||||||
<text class="tag-text">{{mbtiType}}</text>
|
<text class="tag-text">{{mbtiType}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag tag-blue" tt:if="{{discType}}">
|
<view class="tag tag-blue" tt:if="{{discType && permDisc}}">
|
||||||
<text class="tag-text">{{discType}}型</text>
|
<text class="tag-text">{{discType}}型</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag tag-orange" tt:if="{{pdpType}}">
|
<view class="tag tag-orange" tt:if="{{pdpType && permPdp}}">
|
||||||
<text class="tag-text">{{pdpType}}</text>
|
<text class="tag-text">{{pdpType}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag tag-gray" tt:if="{{!mbtiType && !discType && !pdpType}}">
|
<view class="tag tag-gray" tt:if="{{showEmptyPersonalityTags}}">
|
||||||
<text class="tag-text">暂无测试记录</text>
|
<text class="tag-text">暂无测试记录</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -78,57 +78,57 @@
|
|||||||
<text class="depth-header-chevron">›</text>
|
<text class="depth-header-chevron">›</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view scroll-x class="cards-scroll cards-scroll--in-card" enhanced show-scrollbar="{{false}}">
|
<scroll-view tt: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="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" tt:if="{{permMbti && mbtiType}}">
|
||||||
<view class="card-deco"></view>
|
<view class="card-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-purple">
|
<view class="card-icon-wrap card-icon-purple">
|
||||||
<text class="card-icon">🧠</text>
|
<text class="card-icon">🧠</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">MBTI性格</text>
|
<text class="card-label">MBTI性格</text>
|
||||||
<text class="card-value">{{mbtiType || '未测评'}}</text>
|
<text class="card-value">{{mbtiType}}</text>
|
||||||
<text class="card-time">{{mbtiType ? mbtiTime : '点击进入问卷'}}</text>
|
<text class="card-time">{{mbtiTime}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="result-card card-orange {{pdpType ? '' : 'result-card--placeholder'}}" bindtap="viewPDP">
|
<view class="result-card card-orange" bindtap="viewPDP" tt:if="{{permPdp && pdpType}}">
|
||||||
<view class="card-deco"></view>
|
<view class="card-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-orange">
|
<view class="card-icon-wrap card-icon-orange">
|
||||||
<text class="card-icon">🦁</text>
|
<text class="card-icon">🦁</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">PDP行为</text>
|
<text class="card-label">PDP行为</text>
|
||||||
<text class="card-value">{{pdpType || '未测评'}}</text>
|
<text class="card-value">{{pdpType}}</text>
|
||||||
<text class="card-time">{{pdpType ? pdpTime : '点击进入问卷'}}</text>
|
<text class="card-time">{{pdpTime}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="result-card card-blue {{discType ? '' : 'result-card--placeholder'}}" bindtap="viewDISC">
|
<view class="result-card card-blue" bindtap="viewDISC" tt:if="{{permDisc && discType}}">
|
||||||
<view class="card-deco"></view>
|
<view class="card-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-blue">
|
<view class="card-icon-wrap card-icon-blue">
|
||||||
<text class="card-icon">📊</text>
|
<text class="card-icon">📊</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">DISC测评</text>
|
<text class="card-label">DISC测评</text>
|
||||||
<text class="card-value">{{discType ? discType + '型' : '未测评'}}</text>
|
<text class="card-value">{{discType + '型'}}</text>
|
||||||
<text class="card-time">{{discType ? discTime : '点击进入问卷'}}</text>
|
<text class="card-time">{{discTime}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="result-card card-teal {{(gallupPreview || aiType) ? '' : 'result-card--placeholder'}}" bindtap="viewGallup" tt:if="{{!reviewMode}}">
|
<view class="result-card card-teal" bindtap="viewGallup" tt:if="{{!reviewMode && permFace && (gallupPreview || aiType)}}">
|
||||||
<view class="card-deco"></view>
|
<view class="card-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-teal">
|
<view class="card-icon-wrap card-icon-teal">
|
||||||
<text class="card-icon">⭐</text>
|
<text class="card-icon">⭐</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">盖洛普优势</text>
|
<text class="card-label">盖洛普优势</text>
|
||||||
<text class="card-value card-value--small">{{gallupPreview || (aiType ? '见面相报告' : '未测评')}}</text>
|
<text class="card-value card-value--small">{{gallupPreview || '见面相报告'}}</text>
|
||||||
<text class="card-time">{{gallupPreview ? aiTime : (aiType ? '已拍面相可查看' : '先完成面相拍摄')}}</text>
|
<text class="card-time">{{aiTime}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="result-card card-rose {{aiType ? '' : 'result-card--placeholder'}}" bindtap="viewAI" tt:if="{{!reviewMode}}">
|
<view class="result-card card-rose" bindtap="viewAI" tt:if="{{!reviewMode && permFace && aiType}}">
|
||||||
<view class="card-deco"></view>
|
<view class="card-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-rose">
|
<view class="card-icon-wrap card-icon-rose">
|
||||||
<text class="card-icon">👁️</text>
|
<text class="card-icon">👁️</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">面相分析</text>
|
<text class="card-label">面相分析</text>
|
||||||
<text class="card-value">{{aiType || '未测评'}}</text>
|
<text class="card-value">{{aiType}}</text>
|
||||||
<text class="card-time">{{aiType ? aiTime : '去拍摄分析'}}</text>
|
<text class="card-time">{{aiTime}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
<view class="depth-empty-hint depth-empty-hint--compact">
|
<view tt:if="{{showLatestTestCards}}" class="depth-empty-hint depth-empty-hint--compact">
|
||||||
<text>灰色为未完成项,点击即可进入测评;右上方可查看全部历史。</text>
|
<text>点击卡片查看详情;右上方可查看全部测试记录。</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="depth-inner-divider"></view>
|
<view class="depth-inner-divider"></view>
|
||||||
<view class="menu-item menu-item--flat" bindtap="goToTestSelect">
|
<view class="menu-item menu-item--flat" bindtap="goToTestSelect">
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 推广中心(根据管理端开关显示/隐藏,标题可配置) -->
|
<!-- 推广中心(根据管理端开关显示/隐藏,标题可配置) -->
|
||||||
<view class="section px-section" tt:if="{{hasLogin && promoDistributionEnabled && !reviewMode}}">
|
<view class="section px-section" tt:if="{{hasLogin && promoDistributionEnabled && !reviewMode && permDistribution}}">
|
||||||
<view class="promo-card" bindtap="goToPromo">
|
<view class="promo-card" bindtap="goToPromo">
|
||||||
<view class="promo-header">
|
<view class="promo-header">
|
||||||
<view class="promo-title-wrap">
|
<view class="promo-title-wrap">
|
||||||
|
|||||||
@@ -1,21 +1,41 @@
|
|||||||
// pages/test-select/index.js - 人脸测试后的详情性格测试入口(MBTI / PDP / DISC)
|
// pages/test-select/index.js
|
||||||
Page({
|
const app = getApp()
|
||||||
data: {},
|
|
||||||
|
Page({
|
||||||
onLoad() {},
|
data: {
|
||||||
|
permFace: true,
|
||||||
// 进入 MBTI 测试
|
permMbti: true,
|
||||||
goMBTI() {
|
permPdp: true,
|
||||||
tt.navigateTo({ url: '/pages/test/mbti' })
|
permDisc: true
|
||||||
},
|
},
|
||||||
|
|
||||||
// 进入 PDP 测试
|
onLoad() {
|
||||||
goPDP() {
|
this._syncPerms()
|
||||||
tt.navigateTo({ url: '/pages/test/pdp' })
|
},
|
||||||
},
|
|
||||||
|
onShow() {
|
||||||
// 进入 DISC 测试
|
this._syncPerms()
|
||||||
goDISC() {
|
},
|
||||||
tt.navigateTo({ url: '/pages/test/disc' })
|
|
||||||
}
|
_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() {
|
||||||
|
tt.navigateTo({ url: '/pages/test/mbti' })
|
||||||
|
},
|
||||||
|
|
||||||
|
goPDP() {
|
||||||
|
tt.navigateTo({ url: '/pages/test/pdp' })
|
||||||
|
},
|
||||||
|
|
||||||
|
goDISC() {
|
||||||
|
tt.navigateTo({ url: '/pages/test/disc' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
<!--pages/test-select/index.wxml - 详细性格测试入口(MBTI / PDP / DISC)-->
|
<!--pages/test-select/index.wxml - 详细性格测试入口(MBTI / PDP / DISC)-->
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<view class="card intro-card">
|
<view class="card intro-card">
|
||||||
<text class="intro-title">选择一项详细性格测试</text>
|
<text class="intro-title">选择一项详细性格测试</text>
|
||||||
<text class="intro-desc">完成问卷即可获得对应维度的专业性格报告</text>
|
<text class="intro-desc">完成问卷即可获得对应维度的专业性格报告</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card entry-card" bindtap="goMBTI">
|
<view class="card entry-card" bindtap="goMBTI" tt:if="{{permMbti}}">
|
||||||
<view class="entry-icon-wrap mbti">
|
<view class="entry-icon-wrap mbti">
|
||||||
<text class="entry-emoji">🧠</text>
|
<text class="entry-emoji">🧠</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="entry-content">
|
<view class="entry-content">
|
||||||
<text class="entry-name">MBTI 性格测试</text>
|
<text class="entry-name">MBTI 性格测试</text>
|
||||||
<text class="entry-brief">16型人格 · 发现你的认知与决策风格</text>
|
<text class="entry-brief">16型人格 · 发现你的认知与决策风格</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="entry-arrow">→</text>
|
<text class="entry-arrow">→</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card entry-card" bindtap="goPDP">
|
<view class="card entry-card" bindtap="goPDP" tt:if="{{permPdp}}">
|
||||||
<view class="entry-icon-wrap pdp">
|
<view class="entry-icon-wrap pdp">
|
||||||
<text class="entry-emoji">🦁</text>
|
<text class="entry-emoji">🦁</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="entry-content">
|
<view class="entry-content">
|
||||||
<text class="entry-name">PDP 行为偏好测试</text>
|
<text class="entry-name">PDP 行为偏好测试</text>
|
||||||
<text class="entry-brief">老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格</text>
|
<text class="entry-brief">老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="entry-arrow">→</text>
|
<text class="entry-arrow">→</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card entry-card" bindtap="goDISC">
|
<view class="card entry-card" bindtap="goDISC" tt:if="{{permDisc}}">
|
||||||
<view class="entry-icon-wrap disc">
|
<view class="entry-icon-wrap disc">
|
||||||
<text class="entry-emoji">📊</text>
|
<text class="entry-emoji">📊</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="entry-content">
|
<view class="entry-content">
|
||||||
<text class="entry-name">DISC 性格测试</text>
|
<text class="entry-name">DISC 性格测试</text>
|
||||||
<text class="entry-brief">D/I/S/C 四维 · 沟通与行为倾向</text>
|
<text class="entry-brief">D/I/S/C 四维 · 沟通与行为倾向</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="entry-arrow">→</text>
|
<text class="entry-arrow">→</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ App({
|
|||||||
// 超管配置的默认企业 ID(无 scene/eid 等入口参数时回落)
|
// 超管配置的默认企业 ID(无 scene/eid 等入口参数时回落)
|
||||||
defaultEnterpriseId: null,
|
defaultEnterpriseId: null,
|
||||||
// API基础地址(开发时用本地,生产环境替换为实际域名)
|
// API基础地址(开发时用本地,生产环境替换为实际域名)
|
||||||
apiBase: 'https://mbtiapi.quwanzhi.com',
|
//apiBase: 'https://mbtiapi.quwanzhi.com',
|
||||||
//apiBase: 'http://mbti.com',
|
apiBase: 'http://mbti.com',
|
||||||
// VIP信息
|
// VIP信息
|
||||||
vipInfo: null,
|
vipInfo: null,
|
||||||
// 测试次数
|
// 测试次数
|
||||||
@@ -59,7 +59,9 @@ App({
|
|||||||
mbtiResult: null,
|
mbtiResult: null,
|
||||||
discResult: null,
|
discResult: null,
|
||||||
pdpResult: null,
|
pdpResult: null,
|
||||||
aiResult: null
|
aiResult: null,
|
||||||
|
// 企业功能权限(超管在企业管理中配置):null=无限制(个人版),对象时按 key 控制
|
||||||
|
enterprisePermissions: null
|
||||||
},
|
},
|
||||||
|
|
||||||
onLaunch(launchOptions) {
|
onLaunch(launchOptions) {
|
||||||
@@ -69,30 +71,85 @@ App({
|
|||||||
// 加载本地存储的数据
|
// 加载本地存储的数据
|
||||||
this.loadStoredData()
|
this.loadStoredData()
|
||||||
|
|
||||||
// 静默登录获取openId
|
|
||||||
this.silentLogin()
|
|
||||||
|
|
||||||
// 上报应用启动事件
|
// 上报应用启动事件
|
||||||
try {
|
try {
|
||||||
const analytics = require('./utils/analytics.js')
|
const analytics = require('./utils/analytics.js')
|
||||||
analytics.reportAppLaunch(launchOptions)
|
analytics.reportAppLaunch(launchOptions)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// 预加载站点/小程序名称、维护模式与面相审核模式(camera/首页文案)
|
// 必须先完成静默登录再拉 runtime:否则无 token 时 enterprisePermissions 永远 null,清除缓存后必现「要二次刷新」
|
||||||
this.getRuntimeConfig().then((cfg) => {
|
this.silentLogin()
|
||||||
if (cfg) {
|
.catch(() => {})
|
||||||
if (cfg.siteTitle) this.globalData.siteTitle = cfg.siteTitle
|
.then(() => this.getRuntimeConfig())
|
||||||
if (cfg.maintenanceMode !== undefined) this.globalData.maintenanceMode = !!cfg.maintenanceMode
|
.then(() => {
|
||||||
if (typeof cfg.reviewMode === 'boolean') {
|
this._afterRuntimeSynced()
|
||||||
this.globalData.reviewMode = cfg.reviewMode
|
})
|
||||||
|
.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) {
|
} else if (route === 'pages/enterprise/index' && typeof top.setData === 'function') {
|
||||||
this.globalData.defaultEnterpriseId = Number(cfg.defaultEnterpriseId)
|
const maintenanceMode = audit
|
||||||
} else {
|
const pf = permFace
|
||||||
this.globalData.defaultEnterpriseId = null
|
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() {
|
onShow() {
|
||||||
@@ -386,6 +443,7 @@ App({
|
|||||||
* @returns {Promise<{pricingType, pricing, aiProviderId, aiProviderName}>}
|
* @returns {Promise<{pricingType, pricing, aiProviderId, aiProviderName}>}
|
||||||
*/
|
*/
|
||||||
getRuntimeConfig() {
|
getRuntimeConfig() {
|
||||||
|
const reqId = (this._runtimeReqSeq = (this._runtimeReqSeq || 0) + 1)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const scope = this.globalData.appScope || 'personal'
|
const scope = this.globalData.appScope || 'personal'
|
||||||
const base = this.globalData.apiBase.replace(/\/$/, '')
|
const base = this.globalData.apiBase.replace(/\/$/, '')
|
||||||
@@ -396,6 +454,10 @@ App({
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
header: token ? { Authorization: 'Bearer ' + token } : {},
|
header: token ? { Authorization: 'Bearer ' + token } : {},
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
|
if (reqId !== this._runtimeReqSeq) {
|
||||||
|
resolve(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||||
const data = res.data.data || {}
|
const data = res.data.data || {}
|
||||||
if (data.siteTitle) this.globalData.siteTitle = data.siteTitle
|
if (data.siteTitle) this.globalData.siteTitle = data.siteTitle
|
||||||
@@ -411,6 +473,11 @@ App({
|
|||||||
} else {
|
} else {
|
||||||
this.globalData.defaultEnterpriseId = null
|
this.globalData.defaultEnterpriseId = null
|
||||||
}
|
}
|
||||||
|
if (data.enterprisePermissions && typeof data.enterprisePermissions === 'object') {
|
||||||
|
this.globalData.enterprisePermissions = data.enterprisePermissions
|
||||||
|
} else {
|
||||||
|
this.globalData.enterprisePermissions = null
|
||||||
|
}
|
||||||
resolve(data)
|
resolve(data)
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(res.data && res.data.message ? res.data.message : '获取配置失败'))
|
reject(new Error(res.data && res.data.message ? res.data.message : '获取配置失败'))
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
Component({
|
Component({
|
||||||
data: {
|
data: {
|
||||||
selected: 0,
|
selected: 0,
|
||||||
/** 审核模式:为 true 时隐藏中间「拍摄」入口(与 globalData.reviewMode / maintenanceMode 一致) */
|
/** 审核模式 */
|
||||||
reviewMode: false,
|
reviewMode: false,
|
||||||
|
/** 为 true 时隐藏中间「拍摄」:审核模式 或 企业权限关闭 face */
|
||||||
|
hideMiddleFab: false,
|
||||||
list: [
|
list: [
|
||||||
{ pagePath: '/pages/index/index', text: '首页', textKey: 'home', icon: 'home' },
|
{ pagePath: '/pages/index/index', text: '首页', textKey: 'home', icon: 'home' },
|
||||||
{ pagePath: '/pages/index/camera', text: '拍摄', textKey: 'camera', icon: 'camera' },
|
{ pagePath: '/pages/index/camera', text: '拍摄', textKey: 'camera', icon: 'camera' },
|
||||||
@@ -30,18 +32,20 @@ Component({
|
|||||||
const url = currentPage.route
|
const url = currentPage.route
|
||||||
const gd = getApp().globalData || {}
|
const gd = getApp().globalData || {}
|
||||||
const reviewMode = !!(gd.reviewMode || gd.maintenanceMode)
|
const reviewMode = !!(gd.reviewMode || gd.maintenanceMode)
|
||||||
|
const ep = gd.enterprisePermissions
|
||||||
|
const hideMiddleFab = reviewMode || !!(ep && ep.face === false)
|
||||||
let selected = 0
|
let selected = 0
|
||||||
if (url === 'pages/index/index' || url === 'pages/enterprise/index') {
|
if (url === 'pages/index/index' || url === 'pages/enterprise/index') {
|
||||||
selected = 0
|
selected = 0
|
||||||
} else if (url === 'pages/index/camera') {
|
} else if (url === 'pages/index/camera') {
|
||||||
selected = 1
|
selected = hideMiddleFab ? 0 : 1
|
||||||
} else if (url === 'pages/profile/index') {
|
} else if (url === 'pages/profile/index') {
|
||||||
selected = 2
|
selected = 2
|
||||||
}
|
}
|
||||||
this.setData({ selected, reviewMode })
|
this.setData({ selected, reviewMode, hideMiddleFab })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('updateSelected error:', error)
|
console.error('updateSelected error:', error)
|
||||||
this.setData({ selected: 0, reviewMode: false })
|
this.setData({ selected: 0, reviewMode: false, hideMiddleFab: false })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
switchTab(e) {
|
switchTab(e) {
|
||||||
@@ -49,7 +53,8 @@ Component({
|
|||||||
let url = e.currentTarget.dataset.path
|
let url = e.currentTarget.dataset.path
|
||||||
|
|
||||||
const gd = getApp().globalData || {}
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!-- 自定义 tabBar:顶部分割线 + 三栏;中间单独一列避免浮钮与左右叠层 -->
|
<!-- 自定义 tabBar:顶部分割线 + 三栏;中间单独一列避免浮钮与左右叠层 -->
|
||||||
<view class="tab-bar">
|
<view class="tab-bar">
|
||||||
<view class="tab-bar-line"></view>
|
<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
|
<view
|
||||||
class="tab-item {{selected === 0 ? 'active' : ''}}"
|
class="tab-item {{selected === 0 ? 'active' : ''}}"
|
||||||
data-index="0"
|
data-index="0"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 中间列:非审核模式显示「拍摄」 -->
|
<!-- 中间列:非审核模式显示「拍摄」 -->
|
||||||
<view class="tab-slot-middle" wx:if="{{!reviewMode}}">
|
<view class="tab-slot-middle" wx:if="{{!hideMiddleFab}}">
|
||||||
<view
|
<view
|
||||||
class="middle-fab {{selected === 1 ? 'active' : ''}}"
|
class="middle-fab {{selected === 1 ? 'active' : ''}}"
|
||||||
data-index="1"
|
data-index="1"
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ Page({
|
|||||||
siteTitle: '神仙团队AI性格测试',
|
siteTitle: '神仙团队AI性格测试',
|
||||||
startButtonEnterprise: '开始面部测试',
|
startButtonEnterprise: '开始面部测试',
|
||||||
aiAnalysisText: '智能分析',
|
aiAnalysisText: '智能分析',
|
||||||
maintenanceMode: false
|
maintenanceMode: false,
|
||||||
|
reviewMode: false,
|
||||||
|
permFace: true
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
@@ -62,13 +64,17 @@ Page({
|
|||||||
const navbarHeightRpx = statusBarHeightRpx + 88
|
const navbarHeightRpx = statusBarHeightRpx + 88
|
||||||
const gd = app.globalData
|
const gd = app.globalData
|
||||||
const maintenanceMode = !!(gd.reviewMode || gd.maintenanceMode)
|
const maintenanceMode = !!(gd.reviewMode || gd.maintenanceMode)
|
||||||
|
const ep = gd.enterprisePermissions
|
||||||
|
const pf = !ep || ep.face !== false
|
||||||
this.setData({
|
this.setData({
|
||||||
statusBarHeight: statusBarHeightRpx,
|
statusBarHeight: statusBarHeightRpx,
|
||||||
navbarHeight: navbarHeightRpx,
|
navbarHeight: navbarHeightRpx,
|
||||||
siteTitle: gd.siteTitle || '神仙团队AI性格测试',
|
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) || '智能分析',
|
aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析',
|
||||||
maintenanceMode
|
maintenanceMode,
|
||||||
|
reviewMode: maintenanceMode,
|
||||||
|
permFace: pf
|
||||||
})
|
})
|
||||||
|
|
||||||
// 未绑定企业的用户:扫码带 eid 或超管配置了默认企业时也允许使用企业版
|
// 未绑定企业的用户:扫码带 eid 或超管配置了默认企业时也允许使用企业版
|
||||||
@@ -92,10 +98,14 @@ Page({
|
|||||||
if (cfg.reviewMode !== undefined || cfg.maintenanceMode !== undefined) {
|
if (cfg.reviewMode !== undefined || cfg.maintenanceMode !== undefined) {
|
||||||
app.globalData.reviewMode = !!(cfg.reviewMode || cfg.maintenanceMode)
|
app.globalData.reviewMode = !!(cfg.reviewMode || cfg.maintenanceMode)
|
||||||
}
|
}
|
||||||
|
const ep2 = app.globalData.enterprisePermissions
|
||||||
|
const pf2 = !ep2 || ep2.face !== false
|
||||||
this.setData({
|
this.setData({
|
||||||
startButtonEnterprise: maintenanceMode ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonEnterprise || '开始面部测试'),
|
startButtonEnterprise: (maintenanceMode || !pf2) ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonEnterprise || '开始面部测试'),
|
||||||
aiAnalysisText: (cfg.textConfig && cfg.textConfig.aiAnalysisText) || '智能分析',
|
aiAnalysisText: (cfg.textConfig && cfg.textConfig.aiAnalysisText) || '智能分析',
|
||||||
maintenanceMode
|
maintenanceMode,
|
||||||
|
reviewMode: maintenanceMode,
|
||||||
|
permFace: pf2
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
@@ -120,10 +130,14 @@ Page({
|
|||||||
if (cfg.reviewMode !== undefined || cfg.maintenanceMode !== undefined) {
|
if (cfg.reviewMode !== undefined || cfg.maintenanceMode !== undefined) {
|
||||||
app.globalData.reviewMode = !!(cfg.reviewMode || cfg.maintenanceMode)
|
app.globalData.reviewMode = !!(cfg.reviewMode || cfg.maintenanceMode)
|
||||||
}
|
}
|
||||||
|
const ep3 = app.globalData.enterprisePermissions
|
||||||
|
const pf3 = !ep3 || ep3.face !== false
|
||||||
this.setData({
|
this.setData({
|
||||||
startButtonEnterprise: maintenanceMode ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonEnterprise || '开始面部测试'),
|
startButtonEnterprise: (maintenanceMode || !pf3) ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonEnterprise || '开始面部测试'),
|
||||||
aiAnalysisText: (cfg.textConfig && cfg.textConfig.aiAnalysisText) || '智能分析',
|
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)) {
|
if ((cfg && cfg.pricingType) !== 'enterprise' && !app.globalData.enterpriseIdFromScene && !(app.globalData.defaultEnterpriseId && Number(app.globalData.defaultEnterpriseId) > 0)) {
|
||||||
@@ -157,18 +171,20 @@ Page({
|
|||||||
onShow() {
|
onShow() {
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||||
const tabBar = this.getTabBar()
|
const tabBar = this.getTabBar()
|
||||||
const gd = getApp().globalData || {}
|
if (typeof tabBar.updateSelected === 'function') tabBar.updateSelected()
|
||||||
const audit = !!(gd.reviewMode || gd.maintenanceMode)
|
|
||||||
tabBar.setData({ selected: 0, reviewMode: audit })
|
|
||||||
}
|
}
|
||||||
try { getApp().globalData.appScope = 'enterprise' } catch (e) {}
|
try { getApp().globalData.appScope = 'enterprise' } catch (e) {}
|
||||||
const gd = getApp().globalData
|
const gd = getApp().globalData
|
||||||
const maintenanceMode = !!(gd.reviewMode || gd.maintenanceMode)
|
const maintenanceMode = !!(gd.reviewMode || gd.maintenanceMode)
|
||||||
|
const ep4 = gd.enterprisePermissions
|
||||||
|
const pf4 = !ep4 || ep4.face !== false
|
||||||
this.setData({
|
this.setData({
|
||||||
siteTitle: gd.siteTitle || '神仙团队AI性格测试',
|
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) || '智能分析',
|
aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析',
|
||||||
maintenanceMode
|
maintenanceMode,
|
||||||
|
reviewMode: maintenanceMode,
|
||||||
|
permFace: pf4
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -181,7 +197,8 @@ Page({
|
|||||||
|
|
||||||
// 开始AI面部测试(先校验是否已上传简历,再跳转相机);审核模式下直接跳转 test-select
|
// 开始AI面部测试(先校验是否已上传简历,再跳转相机);审核模式下直接跳转 test-select
|
||||||
startAITest() {
|
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' })
|
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +1,80 @@
|
|||||||
<!--pages/enterprise/index.wxml - 企业版首页-->
|
<!--pages/enterprise/index.wxml - 企业版首页-->
|
||||||
<view class="container" style="padding-top: {{navbarHeight}}px;">
|
<view class="container" style="padding-top: {{navbarHeight}}px;">
|
||||||
<!-- 自定义导航栏 -->
|
<!-- 自定义导航栏 -->
|
||||||
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
|
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
|
||||||
<view class="navbar-content">
|
<view class="navbar-content">
|
||||||
<view class="switch-personal-btn" bindtap="switchToPersonal">
|
<view class="switch-personal-btn" bindtap="switchToPersonal">
|
||||||
<text class="personal-icon">👤</text>
|
<text class="personal-icon">👤</text>
|
||||||
<text class="personal-text">个人版</text>
|
<text class="personal-text">个人版</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="navbar-title">{{siteTitle || '神仙团队性格测试'}}</view>
|
<view class="navbar-title">{{siteTitle || '神仙团队性格测试'}}</view>
|
||||||
<view class="navbar-placeholder"></view>
|
<view class="navbar-placeholder"></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 背景装饰 -->
|
<!-- 背景装饰 -->
|
||||||
<view class="bg-decoration bg-top-right"></view>
|
<view class="bg-decoration bg-top-right"></view>
|
||||||
<view class="bg-decoration bg-bottom-left"></view>
|
<view class="bg-decoration bg-bottom-left"></view>
|
||||||
|
|
||||||
<view class="top-image-section">
|
<view class="top-image-section">
|
||||||
<view class="image-container">
|
<view class="image-container">
|
||||||
<view class="image-wrapper">
|
<view class="image-wrapper">
|
||||||
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="float-tag tag-1" wx:if="{{!reviewMode}}">面相分析</view>
|
<view class="float-tag tag-1" wx:if="{{!reviewMode && permFace}}">面相分析</view>
|
||||||
<view class="float-tag tag-2" wx:if="{{!reviewMode}}">骨相分析</view>
|
<view class="float-tag tag-2" wx:if="{{!reviewMode && permFace}}">骨相分析</view>
|
||||||
<view class="float-tag tag-3">性格测评</view>
|
<view class="float-tag tag-3">性格测评</view>
|
||||||
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
|
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
|
||||||
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
|
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="process-section">
|
<view class="process-section">
|
||||||
<view class="section-title">测试流程</view>
|
<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-item">
|
||||||
<view class="step-circle active">1</view>
|
<view class="step-circle active">1</view>
|
||||||
<text class="step-label">STEP1</text>
|
<text class="step-label">STEP1</text>
|
||||||
<text class="step-text">拍摄照片</text>
|
<text class="step-text">拍摄照片</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="step-line"></view>
|
<view class="step-line"></view>
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle">2</view>
|
<view class="step-circle">2</view>
|
||||||
<text class="step-label">STEP2</text>
|
<text class="step-label">STEP2</text>
|
||||||
<text class="step-text">{{aiAnalysisText || '分析'}}</text>
|
<text class="step-text">{{aiAnalysisText || '分析'}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="step-line"></view>
|
<view class="step-line"></view>
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle">3</view>
|
<view class="step-circle">3</view>
|
||||||
<text class="step-label">STEP3</text>
|
<text class="step-label">STEP3</text>
|
||||||
<text class="step-text">生成报告</text>
|
<text class="step-text">生成报告</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="process-steps" wx:if="{{reviewMode}}">
|
<view class="process-steps" wx:if="{{reviewMode || !permFace}}">
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle active">1</view>
|
<view class="step-circle active">1</view>
|
||||||
<text class="step-label">STEP1</text>
|
<text class="step-label">STEP1</text>
|
||||||
<text class="step-text">选择测试</text>
|
<text class="step-text">选择测试</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="step-line"></view>
|
<view class="step-line"></view>
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle">2</view>
|
<view class="step-circle">2</view>
|
||||||
<text class="step-label">STEP2</text>
|
<text class="step-label">STEP2</text>
|
||||||
<text class="step-text">回答问题</text>
|
<text class="step-text">回答问题</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="step-line"></view>
|
<view class="step-line"></view>
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle">3</view>
|
<view class="step-circle">3</view>
|
||||||
<text class="step-label">STEP3</text>
|
<text class="step-label">STEP3</text>
|
||||||
<text class="step-text">查看结果</text>
|
<text class="step-text">查看结果</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="start-button" bindtap="startAITest">
|
<view class="start-button" bindtap="startAITest">
|
||||||
<text class="button-text">{{startButtonEnterprise || '开始性格测试'}}</text>
|
<text class="button-text">{{startButtonEnterprise || '开始性格测试'}}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 自定义底部导航 -->
|
<!-- 自定义底部导航 -->
|
||||||
<custom-tab-bar />
|
<custom-tab-bar />
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -8,9 +8,16 @@ Page({
|
|||||||
tabName: '',
|
tabName: '',
|
||||||
list: [],
|
list: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
hasMore: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
isEnterprise: false,
|
isEnterprise: false,
|
||||||
reviewMode: false
|
reviewMode: false,
|
||||||
|
permMbti: true,
|
||||||
|
permPdp: true,
|
||||||
|
permDisc: true,
|
||||||
|
permFace: true
|
||||||
},
|
},
|
||||||
|
|
||||||
_checkIsEnterprise() {
|
_checkIsEnterprise() {
|
||||||
@@ -21,54 +28,111 @@ Page({
|
|||||||
return scope === 'enterprise' || !!enterpriseId
|
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() {
|
onLoad() {
|
||||||
this.setData({ isEnterprise: this._checkIsEnterprise() })
|
this.setData({ isEnterprise: this._checkIsEnterprise() })
|
||||||
|
this._syncPermsAndTab()
|
||||||
this.loadAll()
|
this.loadAll()
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
this.setData({ isEnterprise: this._checkIsEnterprise(), reviewMode: !!app.globalData.reviewMode })
|
this.setData({ isEnterprise: this._checkIsEnterprise() })
|
||||||
|
this._syncPermsAndTab()
|
||||||
this.loadAll()
|
this.loadAll()
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
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() {
|
loadAll() {
|
||||||
if (this.data.loading) return
|
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 token = app.globalData.token || wx.getStorageSync('token')
|
||||||
const apiBase = app.globalData.apiBase
|
const apiBase = app.globalData.apiBase
|
||||||
const { activeTab } = this.data
|
const { activeTab } = this.data
|
||||||
const typeParam = activeTab === 'all' ? '' : `&type=${activeTab}`
|
const typeParam = activeTab === 'all' ? '' : `&type=${activeTab}`
|
||||||
|
|
||||||
if (!token || !apiBase) {
|
if (!token || !apiBase) {
|
||||||
this.setData({ loading: false })
|
this.setData({ loading: false, loadingMore: false })
|
||||||
this.loadFromStorage()
|
this.loadFromStorage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wx.request({
|
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',
|
method: 'GET',
|
||||||
header: { Authorization: `Bearer ${token}` },
|
header: { Authorization: `Bearer ${token}` },
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.statusCode === 200 && res.data && res.data.data) {
|
if (res.statusCode === 200 && res.data && res.data.data) {
|
||||||
const payload = res.data.data
|
const payload = res.data.data
|
||||||
const rawList = Array.isArray(payload) ? payload : (payload.list || [])
|
const rawList = payload.list || []
|
||||||
const total = Array.isArray(payload) ? rawList.length : (payload.total || 0)
|
|
||||||
const formatted = this.formatList(rawList)
|
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 {
|
} else {
|
||||||
this.setData({ loading: false })
|
this.setData({ loading: false, loadingMore: false })
|
||||||
this.loadFromStorage()
|
if (!append) this.loadFromStorage()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fail: () => {
|
fail: () => {
|
||||||
this.setData({ loading: false })
|
this.setData({ loading: false, loadingMore: false })
|
||||||
this.loadFromStorage()
|
if (!append) this.loadFromStorage()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -79,7 +143,8 @@ Page({
|
|||||||
|
|
||||||
return rawList.map((item, idx) => {
|
return rawList.map((item, idx) => {
|
||||||
if (item.typeName) {
|
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 testType = (item.testType || item.type || 'mbti').toLowerCase()
|
||||||
const ts = item.createdAt || item.testTime || item.timestamp
|
const ts = item.createdAt || item.testTime || item.timestamp
|
||||||
@@ -97,16 +162,17 @@ Page({
|
|||||||
|
|
||||||
// 本地缓存回退
|
// 本地缓存回退
|
||||||
loadFromStorage() {
|
loadFromStorage() {
|
||||||
|
const { permMbti, permPdp, permDisc, permFace, reviewMode } = this.data
|
||||||
const mbtiResult = wx.getStorageSync('mbtiResult')
|
const mbtiResult = wx.getStorageSync('mbtiResult')
|
||||||
const discResult = wx.getStorageSync('discResult')
|
const discResult = wx.getStorageSync('discResult')
|
||||||
const pdpResult = wx.getStorageSync('pdpResult')
|
const pdpResult = wx.getStorageSync('pdpResult')
|
||||||
const aiResult = wx.getStorageSync('aiResult')
|
const aiResult = wx.getStorageSync('aiResult')
|
||||||
const list = []
|
const list = []
|
||||||
if (mbtiResult) list.push({ type: 'mbti', key: 'mbti', emoji: '🧠', typeName: 'MBTI性格测试', resultText: mbtiResult.mbtiType || '未知', testTime: this.formatTime(mbtiResult.timestamp), data: mbtiResult })
|
if (mbtiResult && permMbti) 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 (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) list.push({ type: 'disc', key: 'disc', emoji: '📊', typeName: 'DISC性格测试', resultText: (discResult.dominantType || '未知') + '型', testTime: this.formatTime(discResult.timestamp || discResult.completedAt), data: discResult })
|
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 && !app.globalData.reviewMode) list.push({ type: 'ai', key: 'ai', emoji: '👁️', typeName: '面相分析', resultText: aiResult.mbti || '未知', testTime: this.formatTime(aiResult.timestamp || aiResult.completedAt), data: aiResult })
|
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, loading: false })
|
this.setData({ list, total: list.length, page: 1, hasMore: false, loading: false, loadingMore: false })
|
||||||
},
|
},
|
||||||
|
|
||||||
changeTab(e) {
|
changeTab(e) {
|
||||||
|
|||||||
@@ -7,21 +7,18 @@
|
|||||||
<view class="tab-item {{activeTab === 'all' ? 'active' : ''}}" bindtap="changeTab" data-tab="all">
|
<view class="tab-item {{activeTab === 'all' ? 'active' : ''}}" bindtap="changeTab" data-tab="all">
|
||||||
<text class="tab-text">全部</text>
|
<text class="tab-text">全部</text>
|
||||||
</view>
|
</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>
|
<text class="tab-text">MBTI</text>
|
||||||
</view>
|
</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>
|
<text class="tab-text">PDP</text>
|
||||||
</view>
|
</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>
|
<text class="tab-text">DISC</text>
|
||||||
</view>
|
</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>
|
<text class="tab-text">面相</text>
|
||||||
</view>
|
</view>
|
||||||
<view wx:if="{{isEnterprise}}" class="tab-item {{activeTab === 'resume' ? 'active' : ''}}" bindtap="changeTab" data-tab="resume">
|
|
||||||
<text class="tab-text">简历</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@@ -57,11 +54,12 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 加载中 -->
|
<view class="load-more-row" wx:if="{{loadingMore}}">
|
||||||
<view class="load-more-row" wx:if="{{loading}}">
|
<text class="load-more-text">加载更多...</text>
|
||||||
<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>
|
||||||
|
|
||||||
<view wx:elif="{{list.length > 0}}" class="load-more-row">
|
<view wx:elif="{{list.length > 0}}" class="load-more-row">
|
||||||
<text class="load-more-text no-more">共 {{total}} 条</text>
|
<text class="load-more-text no-more">共 {{total}} 条</text>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -72,16 +72,22 @@ Page({
|
|||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
const rm = this._audit()
|
const rm = this._audit()
|
||||||
|
const faceOff = !!(app.globalData.enterprisePermissions && app.globalData.enterprisePermissions.face === false)
|
||||||
this.setData({ reviewMode: rm })
|
this.setData({ reviewMode: rm })
|
||||||
|
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
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(失败时曾导致白屏且无拍摄区)
|
// 审核模式:只展示本页引导,不再强跳 navigateTo(失败时曾导致白屏且无拍摄区)
|
||||||
if (rm) {
|
if (rm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (faceOff) {
|
||||||
|
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!ensureProfileCompleteAndRedirect()) {
|
if (!ensureProfileCompleteAndRedirect()) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ Page({
|
|||||||
siteTitle: '神仙团队性格测试',
|
siteTitle: '神仙团队性格测试',
|
||||||
startButtonText: '开始性格测试',
|
startButtonText: '开始性格测试',
|
||||||
aiAnalysisText: '分析',
|
aiAnalysisText: '分析',
|
||||||
reviewMode: true
|
reviewMode: true,
|
||||||
|
permFace: true
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
@@ -25,6 +26,8 @@ Page({
|
|||||||
const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {}
|
const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {}
|
||||||
const gd = getApp().globalData
|
const gd = getApp().globalData
|
||||||
const rm0 = !!(gd.reviewMode || gd.maintenanceMode)
|
const rm0 = !!(gd.reviewMode || gd.maintenanceMode)
|
||||||
|
const ep = gd.enterprisePermissions
|
||||||
|
const pf = !ep || ep.face !== false
|
||||||
this.setData({
|
this.setData({
|
||||||
statusBarHeight: statusBarHeightRpx,
|
statusBarHeight: statusBarHeightRpx,
|
||||||
navbarHeight: navbarHeightRpx,
|
navbarHeight: navbarHeightRpx,
|
||||||
@@ -32,7 +35,8 @@ Page({
|
|||||||
siteTitle: rm0 ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
siteTitle: rm0 ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
||||||
startButtonText: rm0 ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '拍摄'),
|
startButtonText: rm0 ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '拍摄'),
|
||||||
aiAnalysisText: rm0 ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
aiAnalysisText: rm0 ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
||||||
reviewMode: rm0
|
reviewMode: rm0,
|
||||||
|
permFace: pf
|
||||||
})
|
})
|
||||||
// 预加载站点名称与文案配置
|
// 预加载站点名称与文案配置
|
||||||
app.getRuntimeConfig().then((cfg) => {
|
app.getRuntimeConfig().then((cfg) => {
|
||||||
@@ -53,7 +57,8 @@ Page({
|
|||||||
aiAnalysisText: rm ? '分析' : (cfg.textConfig.aiAnalysisText || '分析')
|
aiAnalysisText: rm ? '分析' : (cfg.textConfig.aiAnalysisText || '分析')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.setData({ reviewMode: rm })
|
const ep2 = getApp().globalData.enterprisePermissions
|
||||||
|
this.setData({ reviewMode: rm, permFace: !ep2 || ep2.face !== false })
|
||||||
try {
|
try {
|
||||||
const tb = typeof this.getTabBar === 'function' ? this.getTabBar() : null
|
const tb = typeof this.getTabBar === 'function' ? this.getTabBar() : null
|
||||||
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
|
if (tb && typeof tb.updateSelected === 'function') tb.updateSelected()
|
||||||
@@ -97,9 +102,9 @@ Page({
|
|||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
const gd0 = getApp().globalData || {}
|
const gd0 = getApp().globalData || {}
|
||||||
const audit = !!(gd0.reviewMode || gd0.maintenanceMode)
|
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
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,并清除企业来源上下文
|
// 个人版首页:固定 scope=personal,并清除企业来源上下文
|
||||||
try {
|
try {
|
||||||
@@ -108,11 +113,13 @@ Page({
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
const g = getApp().globalData
|
const g = getApp().globalData
|
||||||
const rm = !!(g.reviewMode || g.maintenanceMode)
|
const rm = !!(g.reviewMode || g.maintenanceMode)
|
||||||
|
const ep3 = g.enterprisePermissions
|
||||||
this.setData({
|
this.setData({
|
||||||
siteTitle: rm ? (g.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (g.siteTitle || '神仙团队性格测试'),
|
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) || '分析'),
|
aiAnalysisText: rm ? '分析' : ((g.textConfig && g.textConfig.aiAnalysisText) || '分析'),
|
||||||
reviewMode: rm
|
reviewMode: rm,
|
||||||
|
permFace: !ep3 || ep3.face !== false
|
||||||
})
|
})
|
||||||
const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {}
|
const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {}
|
||||||
this.setData({ showEnterpriseEntry: userInfo.hasEnterprise === true })
|
this.setData({ showEnterpriseEntry: userInfo.hasEnterprise === true })
|
||||||
@@ -138,7 +145,9 @@ Page({
|
|||||||
startCamera() {
|
startCamera() {
|
||||||
try { getApp().globalData.appScope = 'personal' } catch (e) {}
|
try { getApp().globalData.appScope = 'personal' } catch (e) {}
|
||||||
const gd = getApp().globalData
|
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' })
|
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- 审核模式:只显示性格测评标签 -->
|
<!-- 审核模式:只显示性格测评标签 -->
|
||||||
<view class="float-tag tag-1" wx:if="{{!reviewMode}}">面相分析</view>
|
<view class="float-tag tag-1" wx:if="{{!reviewMode && permFace}}">面相分析</view>
|
||||||
<view class="float-tag tag-2" wx:if="{{!reviewMode}}">骨相分析</view>
|
<view class="float-tag tag-2" wx:if="{{!reviewMode && permFace}}">骨相分析</view>
|
||||||
<view class="float-tag tag-3">性格测评</view>
|
<view class="float-tag tag-3">性格测评</view>
|
||||||
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
|
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
|
||||||
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
|
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
<view class="process-section">
|
<view class="process-section">
|
||||||
<view class="section-title">测试流程</view>
|
<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-item">
|
||||||
<view class="step-circle active">1</view>
|
<view class="step-circle active">1</view>
|
||||||
<text class="step-label">STEP1</text>
|
<text class="step-label">STEP1</text>
|
||||||
@@ -51,8 +51,8 @@
|
|||||||
<text class="step-text">生成报告</text>
|
<text class="step-text">生成报告</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 审核模式:纯问卷流程 -->
|
<!-- 审核模式或 face 权限关闭:纯问卷流程 -->
|
||||||
<view class="process-steps" wx:if="{{reviewMode}}">
|
<view class="process-steps" wx:if="{{reviewMode || !permFace}}">
|
||||||
<view class="step-item">
|
<view class="step-item">
|
||||||
<view class="step-circle active">1</view>
|
<view class="step-circle active">1</view>
|
||||||
<text class="step-label">STEP1</text>
|
<text class="step-label">STEP1</text>
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ Page({
|
|||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
const audit = !!(app.globalData.reviewMode || app.globalData.maintenanceMode)
|
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' })
|
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -43,7 +44,8 @@ Page({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
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() })
|
this.setData({ needPhoneAuth: !hasPhone() })
|
||||||
const tc = app.globalData.textConfig
|
const tc = app.globalData.textConfig
|
||||||
|
|||||||
@@ -40,25 +40,65 @@ Page({
|
|||||||
promoTotalEarned: '0.00',
|
promoTotalEarned: '0.00',
|
||||||
promoWithdrawable: '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() {
|
||||||
// 仅在 onLoad 执行一次,onShow 里的 runLoginThenLoad 会导致重复请求
|
// 仅在 onLoad 执行一次,onShow 里的 runLoginThenLoad 会导致重复请求
|
||||||
this.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() {
|
onShow() {
|
||||||
// 同步审核模式(与 runtime maintenanceMode / reviewMode 一致)
|
|
||||||
const gd = app.globalData
|
const gd = app.globalData
|
||||||
this.setData({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
|
this._syncPerms({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
|
||||||
// 如果是从其他页面返回,且已经登录,则只刷新数据而不重新执行登录流程
|
// 如果是从其他页面返回,且已经登录,则只刷新数据而不重新执行登录流程
|
||||||
if (this.data.hasLogin) {
|
if (this.data.hasLogin) {
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||||
const audit = !!(gd.reviewMode || gd.maintenanceMode)
|
const tb = this.getTabBar()
|
||||||
this.getTabBar().setData({ selected: 2, reviewMode: audit })
|
if (typeof tb.updateSelected === 'function') tb.updateSelected()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -108,6 +148,9 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadData() {
|
loadData() {
|
||||||
|
const gd = app.globalData
|
||||||
|
this._syncPerms({ reviewMode: !!(gd.reviewMode || gd.maintenanceMode) })
|
||||||
|
|
||||||
const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo')
|
const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo')
|
||||||
const token = app.globalData.token || wx.getStorageSync('token')
|
const token = app.globalData.token || wx.getStorageSync('token')
|
||||||
|
|
||||||
@@ -164,7 +207,7 @@ Page({
|
|||||||
const discType = r.disc ? r.disc.resultText.replace(/型$/, '') : ''
|
const discType = r.disc ? r.disc.resultText.replace(/型$/, '') : ''
|
||||||
|
|
||||||
const gallupPreview = (r.ai && r.ai.gallupPreview) ? String(r.ai.gallupPreview) : ''
|
const gallupPreview = (r.ai && r.ai.gallupPreview) ? String(r.ai.gallupPreview) : ''
|
||||||
this.setData({
|
const patch = {
|
||||||
testCount: totalCount,
|
testCount: totalCount,
|
||||||
hasResults: !!(r.mbti || r.disc || r.pdp || r.ai),
|
hasResults: !!(r.mbti || r.disc || r.pdp || r.ai),
|
||||||
mbtiType: r.mbti ? r.mbti.resultText : '',
|
mbtiType: r.mbti ? r.mbti.resultText : '',
|
||||||
@@ -180,6 +223,12 @@ Page({
|
|||||||
discResultId: r.disc ? r.disc.id : null,
|
discResultId: r.disc ? r.disc.id : null,
|
||||||
pdpResultId: r.pdp ? r.pdp.id : null,
|
pdpResultId: r.pdp ? r.pdp.id : null,
|
||||||
aiResultId: r.ai ? r.ai.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: () => {
|
fail: () => {
|
||||||
@@ -234,7 +283,7 @@ Page({
|
|||||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setData({
|
const patch = {
|
||||||
testCount,
|
testCount,
|
||||||
hasResults: testCount > 0,
|
hasResults: testCount > 0,
|
||||||
mbtiType: mbtiResult ? getTypeOnly(mbtiResult, 'mbti') : '',
|
mbtiType: mbtiResult ? getTypeOnly(mbtiResult, 'mbti') : '',
|
||||||
@@ -250,6 +299,12 @@ Page({
|
|||||||
discResultId: null,
|
discResultId: null,
|
||||||
pdpResultId: null,
|
pdpResultId: null,
|
||||||
aiResultId: null,
|
aiResultId: null,
|
||||||
|
}
|
||||||
|
const d = { ...this.data, ...patch }
|
||||||
|
this.setData({
|
||||||
|
...patch,
|
||||||
|
showLatestTestCards: this._computeShowLatestTestCards(d),
|
||||||
|
showEmptyPersonalityTags: this._computeShowEmptyPersonalityTags(d)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -49,16 +49,16 @@
|
|||||||
<!-- 类型标签 -->
|
<!-- 类型标签 -->
|
||||||
<scroll-view scroll-x class="tags-scroll">
|
<scroll-view scroll-x class="tags-scroll">
|
||||||
<view class="tags-row">
|
<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>
|
<text class="tag-text">{{mbtiType}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag tag-blue" wx:if="{{discType}}">
|
<view class="tag tag-blue" wx:if="{{discType && permDisc}}">
|
||||||
<text class="tag-text">{{discType}}型</text>
|
<text class="tag-text">{{discType}}型</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag tag-orange" wx:if="{{pdpType}}">
|
<view class="tag tag-orange" wx:if="{{pdpType && permPdp}}">
|
||||||
<text class="tag-text">{{pdpType}}</text>
|
<text class="tag-text">{{pdpType}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tag tag-gray" wx:if="{{!mbtiType && !discType && !pdpType}}">
|
<view class="tag tag-gray" wx:if="{{showEmptyPersonalityTags}}">
|
||||||
<text class="tag-text">暂无测试记录</text>
|
<text class="tag-text">暂无测试记录</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -79,57 +79,57 @@
|
|||||||
<text class="depth-header-chevron">›</text>
|
<text class="depth-header-chevron">›</text>
|
||||||
</view>
|
</view>
|
||||||
</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="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-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-purple">
|
<view class="card-icon-wrap card-icon-purple">
|
||||||
<text class="card-icon">🧠</text>
|
<text class="card-icon">🧠</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">MBTI性格</text>
|
<text class="card-label">MBTI性格</text>
|
||||||
<text class="card-value">{{mbtiType || '未测评'}}</text>
|
<text class="card-value">{{mbtiType}}</text>
|
||||||
<text class="card-time">{{mbtiType ? mbtiTime : '点击进入问卷'}}</text>
|
<text class="card-time">{{mbtiTime}}</text>
|
||||||
</view>
|
</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-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-orange">
|
<view class="card-icon-wrap card-icon-orange">
|
||||||
<text class="card-icon">🦁</text>
|
<text class="card-icon">🦁</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">PDP行为</text>
|
<text class="card-label">PDP行为</text>
|
||||||
<text class="card-value">{{pdpType || '未测评'}}</text>
|
<text class="card-value">{{pdpType}}</text>
|
||||||
<text class="card-time">{{pdpType ? pdpTime : '点击进入问卷'}}</text>
|
<text class="card-time">{{pdpTime}}</text>
|
||||||
</view>
|
</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-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-blue">
|
<view class="card-icon-wrap card-icon-blue">
|
||||||
<text class="card-icon">📊</text>
|
<text class="card-icon">📊</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">DISC测评</text>
|
<text class="card-label">DISC测评</text>
|
||||||
<text class="card-value">{{discType ? discType + '型' : '未测评'}}</text>
|
<text class="card-value">{{discType + '型'}}</text>
|
||||||
<text class="card-time">{{discType ? discTime : '点击进入问卷'}}</text>
|
<text class="card-time">{{discTime}}</text>
|
||||||
</view>
|
</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-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-teal">
|
<view class="card-icon-wrap card-icon-teal">
|
||||||
<text class="card-icon">⭐</text>
|
<text class="card-icon">⭐</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">盖洛普优势</text>
|
<text class="card-label">盖洛普优势</text>
|
||||||
<text class="card-value card-value--small">{{gallupPreview || (aiType ? '见面相报告' : '未测评')}}</text>
|
<text class="card-value card-value--small">{{gallupPreview || '见面相报告'}}</text>
|
||||||
<text class="card-time">{{gallupPreview ? aiTime : (aiType ? '已拍面相可查看' : '先完成面相拍摄')}}</text>
|
<text class="card-time">{{aiTime}}</text>
|
||||||
</view>
|
</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-deco"></view>
|
||||||
<view class="card-icon-wrap card-icon-rose">
|
<view class="card-icon-wrap card-icon-rose">
|
||||||
<text class="card-icon">👁️</text>
|
<text class="card-icon">👁️</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="card-label">面相分析</text>
|
<text class="card-label">面相分析</text>
|
||||||
<text class="card-value">{{aiType || '未测评'}}</text>
|
<text class="card-value">{{aiType}}</text>
|
||||||
<text class="card-time">{{aiType ? aiTime : '去拍摄分析'}}</text>
|
<text class="card-time">{{aiTime}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
<view class="depth-empty-hint depth-empty-hint--compact">
|
<view wx:if="{{showLatestTestCards}}" class="depth-empty-hint depth-empty-hint--compact">
|
||||||
<text>灰色为未完成项,点击即可进入测评;右上方可查看全部历史。</text>
|
<text>点击卡片查看详情;右上方可查看全部测试记录。</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="depth-inner-divider"></view>
|
<view class="depth-inner-divider"></view>
|
||||||
<view class="menu-item menu-item--flat" bindtap="goToTestSelect">
|
<view class="menu-item menu-item--flat" bindtap="goToTestSelect">
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
</view>
|
</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-card" bindtap="goToPromo">
|
||||||
<view class="promo-header">
|
<view class="promo-header">
|
||||||
<view class="promo-title-wrap">
|
<view class="promo-title-wrap">
|
||||||
|
|||||||
@@ -1,21 +1,41 @@
|
|||||||
// pages/test-select/index.js - 人脸测试后的详情性格测试入口(MBTI / PDP / DISC)
|
// pages/test-select/index.js
|
||||||
Page({
|
const app = getApp()
|
||||||
data: {},
|
|
||||||
|
Page({
|
||||||
onLoad() {},
|
data: {
|
||||||
|
permFace: true,
|
||||||
// 进入 MBTI 测试
|
permMbti: true,
|
||||||
goMBTI() {
|
permPdp: true,
|
||||||
wx.navigateTo({ url: '/pages/test/mbti' })
|
permDisc: true
|
||||||
},
|
},
|
||||||
|
|
||||||
// 进入 PDP 测试
|
onLoad() {
|
||||||
goPDP() {
|
this._syncPerms()
|
||||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
},
|
||||||
},
|
|
||||||
|
onShow() {
|
||||||
// 进入 DISC 测试
|
this._syncPerms()
|
||||||
goDISC() {
|
},
|
||||||
wx.navigateTo({ url: '/pages/test/disc' })
|
|
||||||
}
|
_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' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
<!--pages/test-select/index.wxml - 详细性格测试入口(MBTI / PDP / DISC)-->
|
<!--pages/test-select/index.wxml - 详细性格测试入口(MBTI / PDP / DISC)-->
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<view class="card intro-card">
|
<view class="card intro-card">
|
||||||
<text class="intro-title">选择一项详细性格测试</text>
|
<text class="intro-title">选择一项详细性格测试</text>
|
||||||
<text class="intro-desc">完成问卷即可获得对应维度的专业性格报告</text>
|
<text class="intro-desc">完成问卷即可获得对应维度的专业性格报告</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card entry-card" bindtap="goMBTI">
|
<view class="card entry-card" bindtap="goMBTI" wx:if="{{permMbti}}">
|
||||||
<view class="entry-icon-wrap mbti">
|
<view class="entry-icon-wrap mbti">
|
||||||
<text class="entry-emoji">🧠</text>
|
<text class="entry-emoji">🧠</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="entry-content">
|
<view class="entry-content">
|
||||||
<text class="entry-name">MBTI 性格测试</text>
|
<text class="entry-name">MBTI 性格测试</text>
|
||||||
<text class="entry-brief">16型人格 · 发现你的认知与决策风格</text>
|
<text class="entry-brief">16型人格 · 发现你的认知与决策风格</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="entry-arrow">→</text>
|
<text class="entry-arrow">→</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card entry-card" bindtap="goPDP">
|
<view class="card entry-card" bindtap="goPDP" wx:if="{{permPdp}}">
|
||||||
<view class="entry-icon-wrap pdp">
|
<view class="entry-icon-wrap pdp">
|
||||||
<text class="entry-emoji">🦁</text>
|
<text class="entry-emoji">🦁</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="entry-content">
|
<view class="entry-content">
|
||||||
<text class="entry-name">PDP 行为偏好测试</text>
|
<text class="entry-name">PDP 行为偏好测试</text>
|
||||||
<text class="entry-brief">老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格</text>
|
<text class="entry-brief">老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="entry-arrow">→</text>
|
<text class="entry-arrow">→</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card entry-card" bindtap="goDISC">
|
<view class="card entry-card" bindtap="goDISC" wx:if="{{permDisc}}">
|
||||||
<view class="entry-icon-wrap disc">
|
<view class="entry-icon-wrap disc">
|
||||||
<text class="entry-emoji">📊</text>
|
<text class="entry-emoji">📊</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="entry-content">
|
<view class="entry-content">
|
||||||
<text class="entry-name">DISC 性格测试</text>
|
<text class="entry-name">DISC 性格测试</text>
|
||||||
<text class="entry-brief">D/I/S/C 四维 · 沟通与行为倾向</text>
|
<text class="entry-brief">D/I/S/C 四维 · 沟通与行为倾向</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="entry-arrow">→</text>
|
<text class="entry-arrow">→</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
Reference in New Issue
Block a user