Merge branch 'main' of https://github.com/fnvtk/MBTI_wang
# Conflicts: # api/app/controller/api/Analyze.php # api/app/controller/api/AppConfig.php # api/app/controller/api/Payment.php # api/app/controller/api/Test.php # miniprogram/app.js # miniprogram/pages/enterprise/index.js # miniprogram/pages/enterprise/resume-history.js # miniprogram/pages/index/result.js # miniprogram/pages/index/result.wxml # miniprogram/pages/index/result.wxss # miniprogram/pages/promo/poster.js # miniprogram/pages/recharge/index.js # miniprogram/pages/result/disc.wxss # miniprogram/pages/result/mbti.wxss # miniprogram/pages/result/pdp.wxss # miniprogram/pages/result/resume.js # miniprogram/utils/payment.js # miniprogram/utils/share.js
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"custom-tab-bar": "/custom-tab-bar/index"
|
||||
},
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"custom-tab-bar": "/custom-tab-bar/index"
|
||||
},
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
|
||||
@@ -1,59 +1,80 @@
|
||||
<!--pages/enterprise/index.wxml - 企业版首页-->
|
||||
<view class="container" style="padding-top: {{navbarHeight}}px;">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
|
||||
<view class="navbar-content">
|
||||
<view class="switch-personal-btn" bindtap="switchToPersonal">
|
||||
<text class="personal-icon">👤</text>
|
||||
<text class="personal-text">个人版</text>
|
||||
</view>
|
||||
<view class="navbar-title">{{siteTitle || '神仙团队AI性格测试'}}</view>
|
||||
<view class="navbar-placeholder"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 背景装饰 -->
|
||||
<view class="bg-decoration bg-top-right"></view>
|
||||
<view class="bg-decoration bg-bottom-left"></view>
|
||||
|
||||
<view class="top-image-section">
|
||||
<view class="image-container">
|
||||
<view class="image-wrapper">
|
||||
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="float-tag tag-1">面相分析</view>
|
||||
<view class="float-tag tag-2">骨相分析</view>
|
||||
<view class="float-tag tag-3">性格测评</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="process-section">
|
||||
<view class="section-title">测试流程</view>
|
||||
<view class="process-steps">
|
||||
<view class="step-item">
|
||||
<view class="step-circle active">1</view>
|
||||
<text class="step-label">STEP1</text>
|
||||
<text class="step-text">{{maintenanceMode ? '选择测试' : '拍摄照片'}}</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">2</view>
|
||||
<text class="step-label">STEP2</text>
|
||||
<text class="step-text">{{maintenanceMode ? '回答题目' : (aiAnalysisText || '智能分析')}}</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">3</view>
|
||||
<text class="step-label">STEP3</text>
|
||||
<text class="step-text">生成报告</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="start-button" bindtap="startAITest">
|
||||
<text class="button-text">{{startButtonEnterprise || '开始面部测试'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 自定义底部导航 -->
|
||||
<custom-tab-bar />
|
||||
</view>
|
||||
<!--pages/enterprise/index.wxml - 企业版首页-->
|
||||
<view class="container" style="padding-top: {{navbarHeight}}px;">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
|
||||
<view class="navbar-content">
|
||||
<view class="switch-personal-btn" bindtap="switchToPersonal">
|
||||
<text class="personal-icon">👤</text>
|
||||
<text class="personal-text">个人版</text>
|
||||
</view>
|
||||
<view class="navbar-title">{{siteTitle || '神仙团队性格测试'}}</view>
|
||||
<view class="navbar-placeholder"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 背景装饰 -->
|
||||
<view class="bg-decoration bg-top-right"></view>
|
||||
<view class="bg-decoration bg-bottom-left"></view>
|
||||
|
||||
<view class="top-image-section">
|
||||
<view class="image-container">
|
||||
<view class="image-wrapper">
|
||||
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="float-tag tag-1" wx:if="{{!reviewMode}}">面相分析</view>
|
||||
<view class="float-tag tag-2" wx:if="{{!reviewMode}}">骨相分析</view>
|
||||
<view class="float-tag tag-3">性格测评</view>
|
||||
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
|
||||
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="process-section">
|
||||
<view class="section-title">测试流程</view>
|
||||
<view class="process-steps" wx:if="{{!reviewMode}}">
|
||||
<view class="step-item">
|
||||
<view class="step-circle active">1</view>
|
||||
<text class="step-label">STEP1</text>
|
||||
<text class="step-text">拍摄照片</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">2</view>
|
||||
<text class="step-label">STEP2</text>
|
||||
<text class="step-text">{{aiAnalysisText || '分析'}}</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">3</view>
|
||||
<text class="step-label">STEP3</text>
|
||||
<text class="step-text">生成报告</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="process-steps" wx:if="{{reviewMode}}">
|
||||
<view class="step-item">
|
||||
<view class="step-circle active">1</view>
|
||||
<text class="step-label">STEP1</text>
|
||||
<text class="step-text">选择测试</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">2</view>
|
||||
<text class="step-label">STEP2</text>
|
||||
<text class="step-text">回答问题</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">3</view>
|
||||
<text class="step-label">STEP3</text>
|
||||
<text class="step-text">查看结果</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="start-button" bindtap="startAITest">
|
||||
<text class="button-text">{{startButtonEnterprise || '开始性格测试'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 自定义底部导航 -->
|
||||
<custom-tab-bar />
|
||||
</view>
|
||||
|
||||
@@ -1,284 +1,283 @@
|
||||
/* pages/enterprise/index.wxss - 企业版首页样式(与个人版相同,仅背景不同) */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
/* 底部留足空间,避免开始按钮遮挡 tabBar 中间浮起圆钮(上浮 56rpx) */
|
||||
padding-bottom: calc(156rpx + env(safe-area-inset-bottom) + 40rpx);
|
||||
}
|
||||
|
||||
/* 背景装饰圆形 */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-top-right {
|
||||
top: -200rpx;
|
||||
right: -200rpx;
|
||||
background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.bg-bottom-left {
|
||||
/* bottom: -200rpx;
|
||||
left: -200rpx; */
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
/* 自定义导航栏 */
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
z-index: 10000;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 40rpx;
|
||||
position: relative;
|
||||
background: #ffffff;
|
||||
min-height: 88rpx;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.navbar-placeholder {
|
||||
width: 140rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.switch-personal-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
border-radius: 30rpx;
|
||||
border: 1rpx solid rgba(99, 102, 241, 0.2);
|
||||
flex-shrink: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.personal-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.personal-text {
|
||||
font-size: 24rpx;
|
||||
color: #6366f1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.top-image-section {
|
||||
width: 100%;
|
||||
padding: 15rpx 40rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.main-image {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 85%;
|
||||
height: 85%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.float-tag {
|
||||
position: absolute;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #e63946;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.2);
|
||||
white-space: nowrap;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tag-1 {
|
||||
top: 15%;
|
||||
right: 10rpx;
|
||||
}
|
||||
|
||||
.tag-2 {
|
||||
bottom: 25%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tag-3 {
|
||||
bottom: 25%;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.process-section {
|
||||
padding: 15rpx 40rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
text-align: center;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.process-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
background: #b8b9bc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.step-circle.active {
|
||||
background: #e63946;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
width: 50rpx;
|
||||
height: 4rpx;
|
||||
background: #e0e0e0;
|
||||
margin: 0 6rpx 50rpx;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
/* 顶部与中间按钮的间距:上 20,下 10 */
|
||||
margin: 20rpx 40rpx 0;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
border-radius: 50rpx;
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 8rpx 30rpx rgba(99, 102, 241, 0.3);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
margin: 10rpx 40rpx 0;
|
||||
border: 2rpx solid #8b5cf6;
|
||||
border-radius: 50rpx;
|
||||
padding: 20rpx;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.upload-button-primary {
|
||||
margin: 20rpx 40rpx 0;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.upload-button-primary .upload-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 26rpx;
|
||||
color: #8b5cf6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 确保底部导航显示 */
|
||||
custom-tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
/* pages/enterprise/index.wxss - 企业版首页样式(与个人版相同,仅背景不同) */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(100rpx + env(safe-area-inset-bottom) + 40rpx);
|
||||
}
|
||||
|
||||
/* 背景装饰圆形 */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-top-right {
|
||||
top: -200rpx;
|
||||
right: -200rpx;
|
||||
background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.bg-bottom-left {
|
||||
/* bottom: -200rpx;
|
||||
left: -200rpx; */
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
/* 自定义导航栏 */
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
z-index: 10000;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 40rpx;
|
||||
position: relative;
|
||||
background: #ffffff;
|
||||
min-height: 88rpx;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.navbar-placeholder {
|
||||
width: 140rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.switch-personal-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
border-radius: 30rpx;
|
||||
border: 1rpx solid rgba(99, 102, 241, 0.2);
|
||||
flex-shrink: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.personal-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.personal-text {
|
||||
font-size: 24rpx;
|
||||
color: #6366f1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.top-image-section {
|
||||
width: 100%;
|
||||
padding: 15rpx 40rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.main-image {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 85%;
|
||||
height: 85%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.float-tag {
|
||||
position: absolute;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #e63946;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.2);
|
||||
white-space: nowrap;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tag-1 {
|
||||
top: 15%;
|
||||
right: 10rpx;
|
||||
}
|
||||
|
||||
.tag-2 {
|
||||
bottom: 25%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tag-3 {
|
||||
bottom: 25%;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.process-section {
|
||||
padding: 15rpx 40rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
text-align: center;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.process-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
background: #b8b9bc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.step-circle.active {
|
||||
background: #e63946;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
width: 50rpx;
|
||||
height: 4rpx;
|
||||
background: #e0e0e0;
|
||||
margin: 0 6rpx 50rpx;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
/* 顶部与中间按钮的间距:上 20,下 10 */
|
||||
margin: 20rpx 40rpx 0;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
border-radius: 50rpx;
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 8rpx 30rpx rgba(99, 102, 241, 0.3);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
margin: 10rpx 40rpx 0;
|
||||
border: 2rpx solid #8b5cf6;
|
||||
border-radius: 50rpx;
|
||||
padding: 20rpx;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.upload-button-primary {
|
||||
margin: 20rpx 40rpx 0;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.upload-button-primary .upload-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 26rpx;
|
||||
color: #8b5cf6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 确保底部导航显示 */
|
||||
custom-tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的简历",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "我的简历",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
<!--pages/enterprise/resume-history.wxml-->
|
||||
<view class="page">
|
||||
<!-- 背景装饰 -->
|
||||
<view class="bg-decoration bg-top-right"></view>
|
||||
<view class="bg-decoration bg-bottom-left"></view>
|
||||
|
||||
<view class="container">
|
||||
<!-- 提示语 + 上传入口 -->
|
||||
<view class="tip-section" wx:if="{{!loading}}">
|
||||
<text class="tip-text" wx:if="{{list.length > 0}}">仅展示当前绑定企业的简历上传记录</text>
|
||||
</view>
|
||||
|
||||
<view class="content" wx:if="{{!loading && list.length > 0}}">
|
||||
<view class="resume-card" wx:for="{{list}}" wx:key="id">
|
||||
<view class="card-body">
|
||||
<view class="file-info">
|
||||
<view class="file-icon-wrap">
|
||||
<text class="file-icon">📄</text>
|
||||
</view>
|
||||
<view class="file-meta">
|
||||
<text class="name">{{item.fileName}}</text>
|
||||
<text class="time">{{item.uploadedAtStr}}</text>
|
||||
</view>
|
||||
<view class="top-right-actions">
|
||||
<view class="default-badge" wx:if="{{item.isDefault}}">默认</view>
|
||||
<view class="set-default-text" wx:if="{{!item.isDefault}}" bindtap="setDefault" data-id="{{item.id}}">设为默认</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-footer">
|
||||
<view class="secondary-actions">
|
||||
<view class="action-btn text-btn delete-btn" bindtap="deleteResume" data-id="{{item.id}}">删除</view>
|
||||
</view>
|
||||
<view class="primary-actions">
|
||||
<view class="action-btn primary-btn" bindtap="previewResume" data-url="{{item.url}}" data-file-name="{{item.fileName}}">
|
||||
预览查看
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state" wx:if="{{!loading && empty}}">
|
||||
<view class="empty-icon-wrap">
|
||||
<text class="empty-icon">📁</text>
|
||||
</view>
|
||||
<text class="empty-text">暂无上传记录</text>
|
||||
<text class="empty-tip">点击下方按钮上传简历,或返回企业版首页上传</text>
|
||||
<view class="go-upload-btn" bindtap="uploadResume">上传简历</view>
|
||||
<view class="back-home-btn" bindtap="goBack">返回首页</view>
|
||||
</view>
|
||||
|
||||
<view class="loading-wrap" wx:if="{{loading}}">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部悬浮上传:有数据时显示 -->
|
||||
<view class="float-upload-wrap" wx:if="{{!loading && list.length > 0}}" bindtap="uploadResume">
|
||||
<view class="float-upload-btn">
|
||||
<text class="float-upload-text">上传简历</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/enterprise/resume-history.wxml-->
|
||||
<view class="page">
|
||||
<!-- 背景装饰 -->
|
||||
<view class="bg-decoration bg-top-right"></view>
|
||||
<view class="bg-decoration bg-bottom-left"></view>
|
||||
|
||||
<view class="container">
|
||||
<!-- 提示语 + 上传入口 -->
|
||||
<view class="tip-section" wx:if="{{!loading}}">
|
||||
<text class="tip-text" wx:if="{{list.length > 0}}">仅展示当前绑定企业的简历上传记录</text>
|
||||
</view>
|
||||
|
||||
<view class="content" wx:if="{{!loading && list.length > 0}}">
|
||||
<view class="resume-card" wx:for="{{list}}" wx:key="id">
|
||||
<view class="card-body">
|
||||
<view class="file-info">
|
||||
<view class="file-icon-wrap">
|
||||
<text class="file-icon">📄</text>
|
||||
</view>
|
||||
<view class="file-meta">
|
||||
<text class="name">{{item.fileName}}</text>
|
||||
<text class="time">{{item.uploadedAtStr}}</text>
|
||||
</view>
|
||||
<view class="top-right-actions">
|
||||
<view class="default-badge" wx:if="{{item.isDefault}}">默认</view>
|
||||
<view class="set-default-text" wx:if="{{!item.isDefault}}" bindtap="setDefault" data-id="{{item.id}}">设为默认</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-footer">
|
||||
<view class="secondary-actions">
|
||||
<view class="action-btn text-btn delete-btn" bindtap="deleteResume" data-id="{{item.id}}">删除</view>
|
||||
</view>
|
||||
<view class="primary-actions">
|
||||
<view class="action-btn primary-btn" bindtap="previewResume" data-url="{{item.url}}" data-file-name="{{item.fileName}}">
|
||||
预览查看
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state" wx:if="{{!loading && empty}}">
|
||||
<view class="empty-icon-wrap">
|
||||
<text class="empty-icon">📁</text>
|
||||
</view>
|
||||
<text class="empty-text">暂无上传记录</text>
|
||||
<text class="empty-tip">点击下方按钮上传简历,或返回企业版首页上传</text>
|
||||
<view class="go-upload-btn" bindtap="uploadResume">上传简历</view>
|
||||
<view class="back-home-btn" bindtap="goBack">返回首页</view>
|
||||
</view>
|
||||
|
||||
<view class="loading-wrap" wx:if="{{loading}}">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部悬浮上传:有数据时显示 -->
|
||||
<view class="float-upload-wrap" wx:if="{{!loading && list.length > 0}}" bindtap="uploadResume">
|
||||
<view class="float-upload-btn">
|
||||
<text class="float-upload-text">上传简历</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,342 +1,342 @@
|
||||
/* pages/enterprise/resume-history.wxss */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 背景装饰圆形 */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-top-right {
|
||||
top: -200rpx;
|
||||
right: -200rpx;
|
||||
background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.bg-bottom-left {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
padding: 24rpx 32rpx 60rpx;
|
||||
padding-bottom: calc(60rpx + 120rpx);
|
||||
}
|
||||
|
||||
/* 底部悬浮上传按钮(有数据时显示) */
|
||||
.float-upload-wrap {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
padding: 24rpx 32rpx;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
background: linear-gradient(to top, rgba(255,255,255,0.98) 0%, rgba(255,255,255,0.95) 70%, transparent);
|
||||
}
|
||||
|
||||
.float-upload-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
border-radius: 48rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.35);
|
||||
}
|
||||
|
||||
.float-upload-btn:active {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.float-upload-icon {
|
||||
font-size: 36rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.float-upload-text {
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.tip-section {
|
||||
margin-bottom: 24rpx;
|
||||
padding: 0 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.upload-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 14rpx 28rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 6rpx 16rpx rgba(99, 102, 241, 0.25);
|
||||
}
|
||||
|
||||
.upload-entry:active {
|
||||
opacity: 0.9;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.upload-entry-icon {
|
||||
font-size: 28rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 简历卡片 */
|
||||
.resume-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 28rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.file-icon-wrap {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: #f0f2ff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
width: 80%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding-right: 16rpx;
|
||||
}
|
||||
|
||||
.top-right-actions {
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.set-default-text {
|
||||
font-size: 24rpx;
|
||||
color: #6366f1;
|
||||
padding: 4rpx 0;
|
||||
}
|
||||
|
||||
.set-default-text:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 6rpx;
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
.time {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.default-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
/* 卡片操作区 */
|
||||
.card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 24rpx;
|
||||
border-top: 1rpx solid #f5f6fa;
|
||||
}
|
||||
|
||||
.secondary-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 64rpx;
|
||||
border-radius: 32rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
opacity: 0.7;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.text-btn {
|
||||
padding: 0 16rpx;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
margin-right: 6rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
color: #f43f5e;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
|
||||
.primary-btn {
|
||||
padding: 0 32rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(99, 102, 241, 0.2);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
padding: 120rpx 60rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon-wrap {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background: #fff;
|
||||
border-radius: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.go-upload-btn {
|
||||
padding: 24rpx 64rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
border-radius: 50rpx;
|
||||
box-shadow: 0 10rpx 20rpx rgba(99, 102, 241, 0.2);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.back-home-btn {
|
||||
padding: 20rpx 48rpx;
|
||||
font-size: 26rpx;
|
||||
color: #6366f1;
|
||||
font-weight: 600;
|
||||
border: 2rpx solid #6366f1;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
/* 加载中 */
|
||||
.loading-wrap {
|
||||
padding: 100rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 4rpx solid rgba(99, 102, 241, 0.1);
|
||||
border-top-color: #6366f1;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
/* pages/enterprise/resume-history.wxss */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 背景装饰圆形 */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-top-right {
|
||||
top: -200rpx;
|
||||
right: -200rpx;
|
||||
background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.bg-bottom-left {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
padding: 24rpx 32rpx 60rpx;
|
||||
padding-bottom: calc(60rpx + 120rpx);
|
||||
}
|
||||
|
||||
/* 底部悬浮上传按钮(有数据时显示) */
|
||||
.float-upload-wrap {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
padding: 24rpx 32rpx;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
background: linear-gradient(to top, rgba(255,255,255,0.98) 0%, rgba(255,255,255,0.95) 70%, transparent);
|
||||
}
|
||||
|
||||
.float-upload-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
border-radius: 48rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.35);
|
||||
}
|
||||
|
||||
.float-upload-btn:active {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.float-upload-icon {
|
||||
font-size: 36rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.float-upload-text {
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.tip-section {
|
||||
margin-bottom: 24rpx;
|
||||
padding: 0 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.upload-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 14rpx 28rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 6rpx 16rpx rgba(99, 102, 241, 0.25);
|
||||
}
|
||||
|
||||
.upload-entry:active {
|
||||
opacity: 0.9;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.upload-entry-icon {
|
||||
font-size: 28rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 简历卡片 */
|
||||
.resume-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 28rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.file-icon-wrap {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: #f0f2ff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
width: 80%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding-right: 16rpx;
|
||||
}
|
||||
|
||||
.top-right-actions {
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.set-default-text {
|
||||
font-size: 24rpx;
|
||||
color: #6366f1;
|
||||
padding: 4rpx 0;
|
||||
}
|
||||
|
||||
.set-default-text:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 6rpx;
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
.time {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.default-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
/* 卡片操作区 */
|
||||
.card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 24rpx;
|
||||
border-top: 1rpx solid #f5f6fa;
|
||||
}
|
||||
|
||||
.secondary-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 64rpx;
|
||||
border-radius: 32rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
opacity: 0.7;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.text-btn {
|
||||
padding: 0 16rpx;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
margin-right: 6rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
color: #f43f5e;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
|
||||
.primary-btn {
|
||||
padding: 0 32rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(99, 102, 241, 0.2);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
padding: 120rpx 60rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon-wrap {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background: #fff;
|
||||
border-radius: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.go-upload-btn {
|
||||
padding: 24rpx 64rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
border-radius: 50rpx;
|
||||
box-shadow: 0 10rpx 20rpx rgba(99, 102, 241, 0.2);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.back-home-btn {
|
||||
padding: 20rpx 48rpx;
|
||||
font-size: 26rpx;
|
||||
color: #6366f1;
|
||||
font-weight: 600;
|
||||
border: 2rpx solid #6366f1;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
/* 加载中 */
|
||||
.loading-wrap {
|
||||
padding: 100rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 4rpx solid rgba(99, 102, 241, 0.1);
|
||||
border-top-color: #6366f1;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ Page({
|
||||
list: [],
|
||||
total: 0,
|
||||
loading: false,
|
||||
isEnterprise: false
|
||||
isEnterprise: false,
|
||||
reviewMode: false
|
||||
},
|
||||
|
||||
_checkIsEnterprise() {
|
||||
@@ -26,7 +27,7 @@ Page({
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.setData({ isEnterprise: this._checkIsEnterprise() })
|
||||
this.setData({ isEnterprise: this._checkIsEnterprise(), reviewMode: !!app.globalData.reviewMode })
|
||||
this.loadAll()
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
this.getTabBar().setData({ selected: 1 })
|
||||
@@ -104,7 +105,7 @@ Page({
|
||||
if (mbtiResult) list.push({ type: 'mbti', key: 'mbti', emoji: '🧠', typeName: 'MBTI性格测试', resultText: mbtiResult.mbtiType || '未知', testTime: this.formatTime(mbtiResult.timestamp), data: mbtiResult })
|
||||
if (pdpResult) list.push({ type: 'pdp', key: 'pdp', emoji: pdpResult.description?.emoji || '🦁', typeName: 'PDP行为偏好测试', resultText: pdpResult.description?.type || '未知', testTime: this.formatTime(pdpResult.timestamp || pdpResult.completedAt), data: pdpResult })
|
||||
if (discResult) list.push({ type: 'disc', key: 'disc', emoji: '📊', typeName: 'DISC性格测试', resultText: (discResult.dominantType || '未知') + '型', testTime: this.formatTime(discResult.timestamp || discResult.completedAt), data: discResult })
|
||||
if (aiResult) list.push({ type: 'ai', key: 'ai', emoji: '👁️', typeName: '面相分析', resultText: aiResult.mbti || '未知', testTime: this.formatTime(aiResult.timestamp || aiResult.completedAt), data: aiResult })
|
||||
if (aiResult && !app.globalData.reviewMode) list.push({ type: 'ai', key: 'ai', emoji: '👁️', typeName: '面相分析', resultText: aiResult.mbti || '未知', testTime: this.formatTime(aiResult.timestamp || aiResult.completedAt), data: aiResult })
|
||||
this.setData({ list, total: list.length, loading: false })
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "测试记录",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "测试记录",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<view class="tab-item {{activeTab === 'disc' ? 'active' : ''}}" bindtap="changeTab" data-tab="disc">
|
||||
<text class="tab-text">DISC</text>
|
||||
</view>
|
||||
<view class="tab-item {{activeTab === 'ai' ? 'active' : ''}}" bindtap="changeTab" data-tab="ai">
|
||||
<view wx:if="{{!reviewMode}}" class="tab-item {{activeTab === 'ai' ? 'active' : ''}}" bindtap="changeTab" data-tab="ai">
|
||||
<text class="tab-text">面相</text>
|
||||
</view>
|
||||
<view wx:if="{{isEnterprise}}" class="tab-item {{activeTab === 'resume' ? 'active' : ''}}" bindtap="changeTab" data-tab="resume">
|
||||
|
||||
@@ -1,208 +1,208 @@
|
||||
/* pages/history/index.wxss - 测试历史记录(按旧版模板重构) */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.tabs-container {
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.tabs-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12rpx 32rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #f5f5f5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background-color: #e63946;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 200rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 100rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border-radius: 40rpx;
|
||||
padding: 20rpx 60rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3);
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 测试记录列表 */
|
||||
.test-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.test-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.test-card:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-circle {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.icon-mbti { background-color: #f3e8ff; }
|
||||
.icon-pdp { background-color: #dbeafe; }
|
||||
.icon-disc { background-color: #dcfce7; }
|
||||
.icon-ai { background-color: #fce7f3; }
|
||||
|
||||
.icon-emoji {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.test-details {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.test-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.test-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.result-badge {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.badge-mbti { background-color: #f3e8ff; }
|
||||
.badge-mbti .badge-text { color: #7c3aed; }
|
||||
|
||||
.badge-pdp { background-color: #dbeafe; }
|
||||
.badge-pdp .badge-text { color: #2563eb; }
|
||||
|
||||
.badge-disc { background-color: #dcfce7; }
|
||||
.badge-disc .badge-text { color: #16a34a; }
|
||||
|
||||
.badge-ai { background-color: #fce7f3; }
|
||||
.badge-ai .badge-text { color: #db2777; }
|
||||
|
||||
.badge-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.test-time-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.test-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.test-enterprise {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
max-width: 280rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.load-more-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 32rpx 0 20rpx;
|
||||
}
|
||||
|
||||
.load-more-text {
|
||||
font-size: 26rpx;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
color: #bbb;
|
||||
}
|
||||
/* pages/history/index.wxss - 测试历史记录(按旧版模板重构) */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.tabs-container {
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.tabs-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12rpx 32rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #f5f5f5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background-color: #e63946;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 200rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 100rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border-radius: 40rpx;
|
||||
padding: 20rpx 60rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3);
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 测试记录列表 */
|
||||
.test-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.test-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.test-card:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-circle {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.icon-mbti { background-color: #f3e8ff; }
|
||||
.icon-pdp { background-color: #dbeafe; }
|
||||
.icon-disc { background-color: #dcfce7; }
|
||||
.icon-ai { background-color: #fce7f3; }
|
||||
|
||||
.icon-emoji {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.test-details {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.test-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.test-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.result-badge {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.badge-mbti { background-color: #f3e8ff; }
|
||||
.badge-mbti .badge-text { color: #7c3aed; }
|
||||
|
||||
.badge-pdp { background-color: #dbeafe; }
|
||||
.badge-pdp .badge-text { color: #2563eb; }
|
||||
|
||||
.badge-disc { background-color: #dcfce7; }
|
||||
.badge-disc .badge-text { color: #16a34a; }
|
||||
|
||||
.badge-ai { background-color: #fce7f3; }
|
||||
.badge-ai .badge-text { color: #db2777; }
|
||||
|
||||
.badge-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.test-time-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.test-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.test-enterprise {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
max-width: 280rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.load-more-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 32rpx 0 20rpx;
|
||||
}
|
||||
|
||||
.load-more-text {
|
||||
font-size: 26rpx;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// pages/index/camera.js - 面相分析拍照页,拍完后上传到服务器再跳转结果页
|
||||
// pages/index/camera.js - 拍照页,拍完后上传到服务器再跳转结果页
|
||||
const app = getApp()
|
||||
const { hasPhone, bindPhoneByCode } = require('../../utils/phoneAuth.js')
|
||||
const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -10,7 +10,8 @@ Page({
|
||||
guideText: '请正对镜头',
|
||||
uploading: false,
|
||||
needPhoneAuth: false,
|
||||
aiAnalysisText: '智能分析'
|
||||
aiAnalysisText: '分析',
|
||||
reviewMode: true
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -22,15 +23,22 @@ Page({
|
||||
app.getRuntimeConfig().then((cfg) => {
|
||||
if (cfg && cfg.textConfig) {
|
||||
app.globalData.textConfig = cfg.textConfig
|
||||
this.setData({ aiAnalysisText: cfg.textConfig.aiAnalysisText || '智能分析' })
|
||||
this.setData({ aiAnalysisText: cfg.textConfig.aiAnalysisText || '分析' })
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const tabBar = typeof this.getTabBar === 'function' && this.getTabBar()
|
||||
if (tabBar) tabBar.setData({ selected: 1, maintenanceMode: !!app.globalData.maintenanceMode })
|
||||
// 审核模式下重定向到测试选择页
|
||||
if (app.globalData.reviewMode) {
|
||||
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||
return
|
||||
}
|
||||
if (!ensureProfileCompleteAndRedirect()) return
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
this.getTabBar().setData({ selected: 1 })
|
||||
}
|
||||
this.setData({ needPhoneAuth: !hasPhone() })
|
||||
const tc = app.globalData.textConfig
|
||||
if (tc && tc.aiAnalysisText) {
|
||||
@@ -113,8 +121,14 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 完成拍照:先上传 3 张图到服务器,拿到 URL 后再跳转结果页(不强制完善资料)
|
||||
// 完成拍照:先上传 3 张图到服务器,拿到 URL 后再跳转结果页
|
||||
completeCapture() {
|
||||
if (!ensureProfileCompleteAndRedirect()) return
|
||||
if (!hasPhone()) {
|
||||
wx.showToast({ title: '请先授权手机号', icon: 'none' })
|
||||
this.setData({ needPhoneAuth: true })
|
||||
return
|
||||
}
|
||||
const photos = this.data.photos
|
||||
if (!photos || photos.length === 0) {
|
||||
wx.showToast({ title: '请先拍摄照片', icon: 'none' })
|
||||
@@ -176,7 +190,7 @@ Page({
|
||||
console.error('相机错误:', e)
|
||||
wx.showModal({
|
||||
title: '相机权限',
|
||||
content: '请允许使用相机权限以进行AI人脸分析',
|
||||
content: '请允许使用相机权限以进行性格分析',
|
||||
confirmText: '去设置',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "拍照",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "拍照",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,69 @@
|
||||
<!--pages/index/camera.wxml - AI拍照页面(按旧版模板重构)-->
|
||||
<view class="container">
|
||||
<view class="progress-section">
|
||||
<view class="progress-info">
|
||||
<text class="step-text">步骤 {{photoIndex + 1}}/3</text>
|
||||
<text class="photo-count">{{photos.length}}/3 张照片已完成</text>
|
||||
</view>
|
||||
<view class="progress-bars">
|
||||
<view class="progress-bar {{photos.length >= 1 ? 'completed' : 'pending'}}"></view>
|
||||
<view class="progress-bar {{photos.length >= 2 ? 'completed' : 'pending'}}"></view>
|
||||
<view class="progress-bar {{photos.length >= 3 ? 'completed' : 'pending'}}"></view>
|
||||
</view>
|
||||
<view class="instruction-card">
|
||||
<view class="instruction-content">
|
||||
<view class="step-number">{{photoIndex + 1}}</view>
|
||||
<view class="instruction-text">
|
||||
<text class="angle-text">{{guideText}}</text>
|
||||
<text class="tip-text">请保持自然表情,确保光线充足</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="camera-container">
|
||||
<view wx:if="{{photos.length < 3}}" class="camera-preview">
|
||||
<camera
|
||||
class="camera"
|
||||
device-position="front"
|
||||
flash="off"
|
||||
binderror="onCameraError"
|
||||
></camera>
|
||||
</view>
|
||||
|
||||
<view wx:else class="photos-preview">
|
||||
<view class="photo-item" wx:for="{{photos}}" wx:key="index">
|
||||
<image class="photo-image" src="{{item}}" mode="aspectFill"></image>
|
||||
<view class="photo-label">{{index === 0 ? '正面' : (index === 1 ? '左侧' : '右侧')}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="button-container">
|
||||
<view wx:if="{{photos.length < 3}}" class="capture-actions">
|
||||
<view class="capture-button" bindtap="takePhoto">
|
||||
<text class="button-text">拍摄{{guideText}}照片</text>
|
||||
</view>
|
||||
<view class="album-button" bindtap="goToUpload">
|
||||
<text class="album-button-text">从相册选择</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="action-buttons">
|
||||
<view class="action-button secondary" bindtap="retakeAll">
|
||||
<text class="action-button-text">重新拍摄</text>
|
||||
</view>
|
||||
<view class="action-button primary" bindtap="completeCapture">
|
||||
<text class="action-button-text">立即{{aiAnalysisText || '智能分析'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<!--pages/index/camera.wxml - 拍照页面-->
|
||||
<view class="container" wx:if="{{!reviewMode}}">
|
||||
<view class="progress-section">
|
||||
<view class="progress-info">
|
||||
<text class="step-text">步骤 {{photoIndex + 1}}/3</text>
|
||||
<text class="photo-count">{{photos.length}}/3 张照片已完成</text>
|
||||
</view>
|
||||
<view class="progress-bars">
|
||||
<view class="progress-bar {{photos.length >= 1 ? 'completed' : 'pending'}}"></view>
|
||||
<view class="progress-bar {{photos.length >= 2 ? 'completed' : 'pending'}}"></view>
|
||||
<view class="progress-bar {{photos.length >= 3 ? 'completed' : 'pending'}}"></view>
|
||||
</view>
|
||||
<view class="instruction-card">
|
||||
<view class="instruction-content">
|
||||
<view class="step-number">{{photoIndex + 1}}</view>
|
||||
<view class="instruction-text">
|
||||
<text class="angle-text">{{guideText}}</text>
|
||||
<text class="tip-text">请保持自然表情,确保光线充足</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="camera-container">
|
||||
<view wx:if="{{photos.length < 3}}" class="camera-preview">
|
||||
<camera
|
||||
class="camera"
|
||||
device-position="front"
|
||||
flash="off"
|
||||
binderror="onCameraError"
|
||||
></camera>
|
||||
</view>
|
||||
|
||||
<view wx:else class="photos-preview">
|
||||
<view class="photo-item" wx:for="{{photos}}" wx:key="index">
|
||||
<image class="photo-image" src="{{item}}" mode="aspectFill"></image>
|
||||
<view class="photo-label">{{index === 0 ? '正面' : (index === 1 ? '左侧' : '右侧')}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="button-container">
|
||||
<view wx:if="{{photos.length < 3}}" class="capture-actions">
|
||||
<view class="capture-button" bindtap="takePhoto">
|
||||
<text class="button-text">拍摄{{guideText}}照片</text>
|
||||
</view>
|
||||
<view class="album-button" bindtap="goToUpload">
|
||||
<text class="album-button-text">从相册选择</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="action-buttons">
|
||||
<view class="action-button secondary" bindtap="retakeAll">
|
||||
<text class="action-button-text">重新拍摄</text>
|
||||
</view>
|
||||
<view class="action-button primary" bindtap="completeCapture">
|
||||
<text class="action-button-text">立即{{aiAnalysisText || '分析'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手机号授权入口:仅在未授权时显示,就地弹出微信系统手机号授权 -->
|
||||
<view class="phone-auth-section" wx:if="{{needPhoneAuth}}">
|
||||
<text class="phone-auth-tip">为保障服务与联系,请先授权手机号。</text>
|
||||
<button class="phone-auth-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">
|
||||
授权手机号后开始查看报告
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,312 +1,312 @@
|
||||
/* pages/index/camera.wxss - 一屏内展示,为底部自定义 tabBar(含中间浮起圆钮)预留空间 */
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
/* 预留底部空间,避免与自定义 tabBar(约 100rpx 高 + 中间圆钮上浮约 56rpx)重叠 */
|
||||
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
flex-shrink: 0;
|
||||
margin: 16rpx 24rpx 0;
|
||||
padding: 20rpx 32rpx 16rpx;
|
||||
border-radius: 10rpx;
|
||||
background: linear-gradient(to right, #fff5f5, #ffe5e8);
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #e63946;
|
||||
}
|
||||
|
||||
.photo-count {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bars {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.progress-bar.completed {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.progress-bar.pending {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.instruction-card {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 12rpx;
|
||||
padding: 14rpx 18rpx;
|
||||
border: 1rpx solid rgba(230, 57, 70, 0.1);
|
||||
}
|
||||
|
||||
.instruction-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background-color: #e63946;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.instruction-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rpx;
|
||||
}
|
||||
|
||||
.angle-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #c41d2a;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.camera-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.camera-preview {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 32rpx;
|
||||
overflow: hidden;
|
||||
border: 6rpx solid #e5e5e5;
|
||||
background-color: #000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.camera {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.photos-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.photo-item {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
border: 4rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.photo-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.photo-label {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
left: 8rpx;
|
||||
background: rgba(230, 57, 70, 0.9);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 24rpx 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.capture-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.capture-actions .capture-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.album-button {
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
border-radius: 50rpx;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border: 2rpx solid #e63946;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.album-button:active {
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.album-button-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #e63946;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.phone-auth-section {
|
||||
padding: 24rpx 24rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.phone-auth-tip {
|
||||
font-size: 26rpx;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.phone-auth-btn {
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%);
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.phone-auth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.capture-button {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 22rpx;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border-radius: 50rpx;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3);
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
flex: 1;
|
||||
padding: 28rpx 24rpx;
|
||||
border-radius: 50rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-button.secondary {
|
||||
background: #fff;
|
||||
border: 2rpx solid #e63946;
|
||||
}
|
||||
|
||||
.action-button.secondary:active {
|
||||
background: #fff5f5;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.action-button.primary {
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.4);
|
||||
}
|
||||
|
||||
.action-button.primary:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.3);
|
||||
}
|
||||
|
||||
.action-button-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.action-button.secondary .action-button-text {
|
||||
color: #e63946;
|
||||
}
|
||||
|
||||
.action-button.primary .action-button-text {
|
||||
color: #fff;
|
||||
}
|
||||
/* pages/index/camera.wxss - 一屏内展示,为底部自定义 tabBar(含中间浮起圆钮)预留空间 */
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
/* 预留底部空间,避免与自定义 tabBar(约 100rpx 高 + 中间圆钮上浮约 56rpx)重叠 */
|
||||
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
flex-shrink: 0;
|
||||
margin: 16rpx 24rpx 0;
|
||||
padding: 20rpx 32rpx 16rpx;
|
||||
border-radius: 10rpx;
|
||||
background: linear-gradient(to right, #fff5f5, #ffe5e8);
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #e63946;
|
||||
}
|
||||
|
||||
.photo-count {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bars {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.progress-bar.completed {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.progress-bar.pending {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.instruction-card {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 12rpx;
|
||||
padding: 14rpx 18rpx;
|
||||
border: 1rpx solid rgba(230, 57, 70, 0.1);
|
||||
}
|
||||
|
||||
.instruction-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background-color: #e63946;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.instruction-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rpx;
|
||||
}
|
||||
|
||||
.angle-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #c41d2a;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.camera-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.camera-preview {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 32rpx;
|
||||
overflow: hidden;
|
||||
border: 6rpx solid #e5e5e5;
|
||||
background-color: #000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.camera {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.photos-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.photo-item {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
border: 4rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.photo-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.photo-label {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
left: 8rpx;
|
||||
background: rgba(230, 57, 70, 0.9);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 24rpx 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.capture-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.capture-actions .capture-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.album-button {
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
border-radius: 50rpx;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border: 2rpx solid #e63946;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.album-button:active {
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.album-button-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #e63946;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.phone-auth-section {
|
||||
padding: 24rpx 24rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.phone-auth-tip {
|
||||
font-size: 26rpx;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.phone-auth-btn {
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%);
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.phone-auth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.capture-button {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 22rpx;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border-radius: 50rpx;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3);
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
flex: 1;
|
||||
padding: 28rpx 24rpx;
|
||||
border-radius: 50rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-button.secondary {
|
||||
background: #fff;
|
||||
border: 2rpx solid #e63946;
|
||||
}
|
||||
|
||||
.action-button.secondary:active {
|
||||
background: #fff5f5;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.action-button.primary {
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.4);
|
||||
}
|
||||
|
||||
.action-button.primary:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.3);
|
||||
}
|
||||
|
||||
.action-button-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.action-button.secondary .action-button-text {
|
||||
color: #e63946;
|
||||
}
|
||||
|
||||
.action-button.primary .action-button-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ Page({
|
||||
statusBarHeight: 0,
|
||||
navbarHeight: 88,
|
||||
showEnterpriseEntry: false,
|
||||
siteTitle: '神仙团队AI性格测试',
|
||||
startButtonText: '开始面相测试',
|
||||
aiAnalysisText: '智能分析',
|
||||
maintenanceMode: false
|
||||
siteTitle: '神仙团队性格测试',
|
||||
startButtonText: '开始性格测试',
|
||||
aiAnalysisText: '分析',
|
||||
reviewMode: true
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -24,35 +24,32 @@ Page({
|
||||
|
||||
const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {}
|
||||
const gd = getApp().globalData
|
||||
const maintenanceMode = !!(gd.maintenanceMode)
|
||||
this.setData({
|
||||
statusBarHeight: statusBarHeightRpx,
|
||||
navbarHeight: navbarHeightRpx,
|
||||
showEnterpriseEntry: userInfo.hasEnterprise === true,
|
||||
siteTitle: gd.siteTitle || '神仙团队AI性格测试',
|
||||
startButtonText: maintenanceMode ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '开始面相测试'),
|
||||
aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析',
|
||||
maintenanceMode
|
||||
siteTitle: gd.reviewMode ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
||||
startButtonText: gd.reviewMode ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '开始面相测试'),
|
||||
aiAnalysisText: gd.reviewMode ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
||||
reviewMode: !!gd.reviewMode
|
||||
})
|
||||
// 预加载站点名称与文案配置
|
||||
app.getRuntimeConfig().then((cfg) => {
|
||||
if (cfg) {
|
||||
const rm = !!cfg.reviewMode
|
||||
getApp().globalData.reviewMode = rm
|
||||
if (cfg.siteTitle) {
|
||||
getApp().globalData.siteTitle = cfg.siteTitle
|
||||
this.setData({ siteTitle: cfg.siteTitle })
|
||||
this.setData({ siteTitle: rm ? cfg.siteTitle.replace(/AI/gi, '') : cfg.siteTitle })
|
||||
}
|
||||
if (cfg.textConfig) {
|
||||
getApp().globalData.textConfig = cfg.textConfig
|
||||
this.setData({
|
||||
startButtonText: rm ? '开始性格测试' : (cfg.textConfig.startButtonText || '开始面相测试'),
|
||||
aiAnalysisText: rm ? '分析' : (cfg.textConfig.aiAnalysisText || '分析')
|
||||
})
|
||||
}
|
||||
const maintenanceMode = !!(cfg && cfg.maintenanceMode)
|
||||
if (cfg.maintenanceMode !== undefined) getApp().globalData.maintenanceMode = maintenanceMode
|
||||
this.setData({
|
||||
startButtonText: maintenanceMode ? '开始性格测试' : (cfg.textConfig && cfg.textConfig.startButtonText || '开始面相测试'),
|
||||
aiAnalysisText: (cfg.textConfig && cfg.textConfig.aiAnalysisText) || '智能分析',
|
||||
maintenanceMode
|
||||
})
|
||||
const tabBar = typeof this.getTabBar === 'function' && this.getTabBar()
|
||||
if (tabBar) tabBar.setData({ maintenanceMode })
|
||||
this.setData({ reviewMode: rm })
|
||||
}
|
||||
}).catch(() => {})
|
||||
// ── 解析入参(兼容扫码 scene / 分享链接 options)──
|
||||
@@ -91,20 +88,21 @@ Page({
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const tabBar = typeof this.getTabBar === 'function' && this.getTabBar()
|
||||
if (tabBar) tabBar.setData({ selected: 0, maintenanceMode: !!getApp().globalData.maintenanceMode })
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
this.getTabBar().setData({ selected: 0 })
|
||||
}
|
||||
// 个人版首页:固定 scope=personal,并清除企业来源上下文
|
||||
try {
|
||||
getApp().globalData.appScope = 'personal'
|
||||
getApp().globalData.enterpriseIdFromScene = null
|
||||
} catch (e) {}
|
||||
const gd = getApp().globalData
|
||||
const maintenanceMode = !!(gd.maintenanceMode)
|
||||
const rm = !!gd.reviewMode
|
||||
this.setData({
|
||||
siteTitle: gd.siteTitle || '神仙团队AI性格测试',
|
||||
startButtonText: maintenanceMode ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '开始面相测试'),
|
||||
aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析',
|
||||
maintenanceMode
|
||||
siteTitle: rm ? (gd.siteTitle || '神仙团队性格测试').replace(/AI/gi, '') : (gd.siteTitle || '神仙团队性格测试'),
|
||||
startButtonText: rm ? '开始性格测试' : ((gd.textConfig && gd.textConfig.startButtonText) || '开始面相测试'),
|
||||
aiAnalysisText: rm ? '分析' : ((gd.textConfig && gd.textConfig.aiAnalysisText) || '分析'),
|
||||
reviewMode: rm
|
||||
})
|
||||
const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {}
|
||||
this.setData({ showEnterpriseEntry: userInfo.hasEnterprise === true })
|
||||
@@ -126,16 +124,14 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 开始拍照(个人版入口:强制本次链路为个人定价);审核模式下跳转 test-select
|
||||
// 开始测试(审核模式跳问卷,正常模式跳拍照)
|
||||
startCamera() {
|
||||
try { getApp().globalData.appScope = 'personal' } catch (e) {}
|
||||
if (this.data.maintenanceMode) {
|
||||
if (this.data.reviewMode) {
|
||||
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||
return
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/camera' })
|
||||
}
|
||||
wx.switchTab({
|
||||
url: '/pages/index/camera'
|
||||
})
|
||||
},
|
||||
|
||||
// 上传照片(个人版入口:强制本次链路为个人定价)
|
||||
@@ -174,16 +170,18 @@ Page({
|
||||
|
||||
onShareAppMessage() {
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
const rm = this.data.reviewMode
|
||||
return {
|
||||
title: 'AI人脸性格分析 - 看看你的面相透露了什么性格密码',
|
||||
title: rm ? '性格测试 - 快来测测你的MBTI性格类型' : '性格测试 - 看看你的面相透露了什么性格密码',
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
const rm = this.data.reviewMode
|
||||
return {
|
||||
title: 'AI人脸性格分析 - 看看你的面相透露了什么性格密码',
|
||||
title: rm ? '性格测试 - 快来测测你的MBTI性格类型' : '性格测试 - 看看你的面相透露了什么性格密码',
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"custom-tab-bar": "/custom-tab-bar/index"
|
||||
}
|
||||
}
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"custom-tab-bar": "/custom-tab-bar/index"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<!--pages/index/index.wxml - AI测试首页(按旧版模板重构)-->
|
||||
<!--pages/index/index.wxml - 首页-->
|
||||
<view class="container" style="padding-top: {{navbarHeight}}px;">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" style="padding-top: {{statusBarHeight}}rpx;">
|
||||
<view class="navbar-content">
|
||||
<view class="switch-enterprise-btn" wx:if="{{showEnterpriseEntry}}" bindtap="switchToEnterprise">
|
||||
<view class="switch-enterprise-btn" wx:if="{{showEnterpriseEntry && !reviewMode}}" bindtap="switchToEnterprise">
|
||||
<text class="enterprise-icon">🏢</text>
|
||||
<text class="enterprise-text">企业版</text>
|
||||
</view>
|
||||
<view class="navbar-title">{{siteTitle || '神仙团队AI性格测试'}}</view>
|
||||
<view class="navbar-title">{{siteTitle || '神仙团队性格测试'}}</view>
|
||||
<view class="navbar-placeholder"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 背景装饰(与企业版一致) -->
|
||||
<!-- 背景装饰 -->
|
||||
<view class="bg-decoration bg-top-right"></view>
|
||||
<view class="bg-decoration bg-bottom-left"></view>
|
||||
|
||||
@@ -21,25 +21,28 @@
|
||||
<view class="image-wrapper">
|
||||
<image class="main-image" src="/images/mbti-team-image.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="float-tag tag-1">面相分析</view>
|
||||
<view class="float-tag tag-2">骨相分析</view>
|
||||
<!-- 审核模式:只显示性格测评标签 -->
|
||||
<view class="float-tag tag-1" wx:if="{{!reviewMode}}">面相分析</view>
|
||||
<view class="float-tag tag-2" wx:if="{{!reviewMode}}">骨相分析</view>
|
||||
<view class="float-tag tag-3">性格测评</view>
|
||||
<view class="float-tag tag-1" wx:if="{{reviewMode}}">MBTI</view>
|
||||
<view class="float-tag tag-2" wx:if="{{reviewMode}}">DISC</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="process-section">
|
||||
<view class="section-title">测试流程</view>
|
||||
<view class="process-steps">
|
||||
<view class="process-steps" wx:if="{{!reviewMode}}">
|
||||
<view class="step-item">
|
||||
<view class="step-circle active">1</view>
|
||||
<text class="step-label">STEP1</text>
|
||||
<text class="step-text">{{maintenanceMode ? '选择测试' : '拍摄照片'}}</text>
|
||||
<text class="step-text">拍摄照片</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">2</view>
|
||||
<text class="step-label">STEP2</text>
|
||||
<text class="step-text">{{maintenanceMode ? '回答题目' : (aiAnalysisText || '智能分析')}}</text>
|
||||
<text class="step-text">{{aiAnalysisText || '分析'}}</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
@@ -48,10 +51,30 @@
|
||||
<text class="step-text">生成报告</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 审核模式:纯问卷流程 -->
|
||||
<view class="process-steps" wx:if="{{reviewMode}}">
|
||||
<view class="step-item">
|
||||
<view class="step-circle active">1</view>
|
||||
<text class="step-label">STEP1</text>
|
||||
<text class="step-text">选择测试</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">2</view>
|
||||
<text class="step-label">STEP2</text>
|
||||
<text class="step-text">回答问题</text>
|
||||
</view>
|
||||
<view class="step-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">3</view>
|
||||
<text class="step-label">STEP3</text>
|
||||
<text class="step-text">查看结果</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="start-button" bindtap="startCamera">
|
||||
<text class="button-text">{{startButtonText || '开始面相测试'}}</text>
|
||||
<text class="button-text">{{startButtonText || '开始性格测试'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 自定义底部导航 -->
|
||||
|
||||
@@ -1,271 +1,305 @@
|
||||
/* pages/index/index.wxss - 个人版首页,样式与企业版一致 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
/* 底部留足空间,避免开始按钮遮挡 tabBar 中间浮起圆钮(上浮 56rpx) */
|
||||
padding-bottom: calc(156rpx + env(safe-area-inset-bottom) + 40rpx);
|
||||
}
|
||||
|
||||
/* 背景装饰圆形(与企业版一致) */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-top-right {
|
||||
top: -200rpx;
|
||||
right: -200rpx;
|
||||
background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.bg-bottom-left {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
/* 自定义导航栏 */
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
z-index: 10000;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 40rpx;
|
||||
position: relative;
|
||||
background: #ffffff;
|
||||
min-height: 88rpx;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.navbar-placeholder {
|
||||
width: 140rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 与企业版切换按钮同款样式(红主色) */
|
||||
.switch-enterprise-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
background: rgba(230, 57, 70, 0.1);
|
||||
border-radius: 30rpx;
|
||||
border: 1rpx solid rgba(230, 57, 70, 0.2);
|
||||
flex-shrink: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.enterprise-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.enterprise-text {
|
||||
font-size: 24rpx;
|
||||
color: #e63946;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.top-image-section {
|
||||
width: 100%;
|
||||
padding: 15rpx 40rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.main-image {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 85%;
|
||||
height: 85%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.float-tag {
|
||||
position: absolute;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #e63946;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.2);
|
||||
white-space: nowrap;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tag-1 {
|
||||
top: 15%;
|
||||
right: 10rpx;
|
||||
}
|
||||
|
||||
.tag-2 {
|
||||
bottom: 25%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tag-3 {
|
||||
bottom: 25%;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.process-section {
|
||||
padding: 15rpx 40rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
text-align: center;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.process-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
background: #b8b9bc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.step-circle.active {
|
||||
background: #e63946;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
width: 50rpx;
|
||||
height: 4rpx;
|
||||
background: #e0e0e0;
|
||||
margin: 0 6rpx 50rpx;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
margin: 20rpx 40rpx 0;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border-radius: 50rpx;
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 8rpx 30rpx rgba(230, 57, 70, 0.3);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
margin: 0 40rpx 16rpx;
|
||||
border: 2rpx solid #8b5cf6;
|
||||
border-radius: 50rpx;
|
||||
padding: 18rpx;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 26rpx;
|
||||
color: #8b5cf6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 确保底部导航显示 */
|
||||
custom-tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
/* pages/index/index.wxss - 个人版首页,样式与企业版一致 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(100rpx + env(safe-area-inset-bottom) + 40rpx);
|
||||
}
|
||||
|
||||
/* 背景装饰圆形(与企业版一致) */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-top-right {
|
||||
top: -200rpx;
|
||||
right: -200rpx;
|
||||
background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.bg-bottom-left {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
/* 自定义导航栏 */
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
z-index: 10000;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 40rpx;
|
||||
position: relative;
|
||||
background: #ffffff;
|
||||
min-height: 88rpx;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.navbar-placeholder {
|
||||
width: 140rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 与企业版切换按钮同款样式(红主色) */
|
||||
.switch-enterprise-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
background: rgba(230, 57, 70, 0.1);
|
||||
border-radius: 30rpx;
|
||||
border: 1rpx solid rgba(230, 57, 70, 0.2);
|
||||
flex-shrink: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.enterprise-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.enterprise-text {
|
||||
font-size: 24rpx;
|
||||
color: #e63946;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.top-image-section {
|
||||
width: 100%;
|
||||
padding: 15rpx 40rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.main-image {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 85%;
|
||||
height: 85%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.float-tag {
|
||||
position: absolute;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #e63946;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.2);
|
||||
white-space: nowrap;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tag-1 {
|
||||
top: 15%;
|
||||
right: 10rpx;
|
||||
}
|
||||
|
||||
.tag-2 {
|
||||
bottom: 25%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tag-3 {
|
||||
bottom: 25%;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.process-section {
|
||||
padding: 15rpx 40rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
text-align: center;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.process-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
background: #b8b9bc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.step-circle.active {
|
||||
background: #e63946;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
width: 50rpx;
|
||||
height: 4rpx;
|
||||
background: #e0e0e0;
|
||||
margin: 0 6rpx 50rpx;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
margin: 20rpx 40rpx 0;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border-radius: 50rpx;
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 8rpx 30rpx rgba(230, 57, 70, 0.3);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
margin: 0 40rpx 16rpx;
|
||||
border: 2rpx solid #8b5cf6;
|
||||
border-radius: 50rpx;
|
||||
padding: 18rpx;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 26rpx;
|
||||
color: #8b5cf6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* AI 生成内容标识横幅(监管合规,始终显示) */
|
||||
.ai-disclosure-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
margin: 24rpx 40rpx 16rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #f0f4ff 0%, #e8ecf8 100%);
|
||||
border: 1rpx solid #c7d2fe;
|
||||
border-radius: 12rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ai-disclosure-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
border-radius: 8rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ai-disclosure-text {
|
||||
font-size: 22rpx;
|
||||
color: #4338ca;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 确保底部导航显示 */
|
||||
custom-tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "分析报告",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "分析报告",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// pages/index/camera.js - 面相分析拍照页,拍完后上传到服务器再跳转结果页
|
||||
// pages/index/upload.js - 上传照片引导页
|
||||
const app = getApp()
|
||||
const { hasPhone, bindPhoneByCode } = require('../../utils/phoneAuth.js')
|
||||
const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -16,7 +16,7 @@ Page({
|
||||
guideText: '请正对镜头',
|
||||
uploading: false,
|
||||
needPhoneAuth: false,
|
||||
aiAnalysisText: '智能分析'
|
||||
aiAnalysisText: '分析'
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -27,13 +27,19 @@ Page({
|
||||
app.getRuntimeConfig().then((cfg) => {
|
||||
if (cfg && cfg.textConfig) {
|
||||
app.globalData.textConfig = cfg.textConfig
|
||||
this.setData({ aiAnalysisText: cfg.textConfig.aiAnalysisText || '智能分析' })
|
||||
this.setData({ aiAnalysisText: cfg.textConfig.aiAnalysisText || '分析' })
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 审核模式下重定向到测试选择页
|
||||
if (app.globalData.reviewMode) {
|
||||
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||
return
|
||||
}
|
||||
if (!ensureProfileCompleteAndRedirect()) return
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
this.getTabBar().setData({ selected: 1 })
|
||||
}
|
||||
@@ -162,8 +168,9 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 完成拍照:先上传 3 张图到服务器,拿到 URL 后再跳转结果页(不强制完善资料)
|
||||
// 完成拍照:先上传 3 张图到服务器,拿到 URL 后再跳转结果页
|
||||
completeCapture() {
|
||||
if (!ensureProfileCompleteAndRedirect()) return
|
||||
const urls = (this.data.uploadedUrls || []).filter(Boolean)
|
||||
if (!urls.length) {
|
||||
wx.showToast({ title: '请先上传至少一张照片', icon: 'none' })
|
||||
@@ -179,7 +186,7 @@ Page({
|
||||
console.error('相机错误:', e)
|
||||
wx.showModal({
|
||||
title: '相机权限',
|
||||
content: '请允许使用相机权限以进行AI人脸分析',
|
||||
content: '请允许使用相机权限以进行性格分析',
|
||||
confirmText: '去设置',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "拍照",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {
|
||||
"custom-tab-bar": "/custom-tab-bar/index"
|
||||
}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "拍照",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {
|
||||
"custom-tab-bar": "/custom-tab-bar/index"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
<!--pages/index/camera.wxml - AI拍照页面(仿上传照片指南布局)-->
|
||||
<view class="page">
|
||||
<!-- 主体内容 -->
|
||||
<view class="content">
|
||||
<!-- 引导文案 -->
|
||||
<view class="intro">
|
||||
<text class="intro-title">多角度拍摄更精准</text>
|
||||
<view class="intro-desc-wrap">
|
||||
<text class="intro-desc">为了更准确地通过骨相与面相分析您的性格,</text>
|
||||
<text class="intro-desc">请上传以下三个维度的照片。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 三个角度上传卡片 -->
|
||||
<view class="steps">
|
||||
<!-- 正面 -->
|
||||
<view class="step-item">
|
||||
<view class="step-index step-index-primary">
|
||||
<text class="step-index-text step-index-text-primary">1</text>
|
||||
</view>
|
||||
<view class="step-body">
|
||||
<text class="step-title">正面</text>
|
||||
<text class="step-subtitle">目视前方,五官端正,不遮挡额头</text>
|
||||
<view class="step-upload-row">
|
||||
<image class="sample-image" mode="aspectFill" src="{{sampleImages[0]}}"></image>
|
||||
<view class="upload-card" bindtap="onUploadFront">
|
||||
<image wx:if="{{photos[0]}}" class="upload-preview" mode="aspectFill" src="{{photos[0]}}"></image>
|
||||
<block wx:else>
|
||||
<text class="upload-icon">📷</text>
|
||||
<text class="upload-text">点击上传</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 左侧 45 度 -->
|
||||
<view class="step-item">
|
||||
<view class="step-index step-index-secondary">
|
||||
<text class="step-index-text step-index-text-secondary">2</text>
|
||||
</view>
|
||||
<view class="step-body">
|
||||
<text class="step-title">左侧 45度</text>
|
||||
<text class="step-subtitle">展示左侧面颊,用于分析面部轮廓</text>
|
||||
<view class="step-upload-row">
|
||||
<image class="sample-image" mode="aspectFill" src="{{sampleImages[1]}}"></image>
|
||||
<view class="upload-card" bindtap="onUploadLeft">
|
||||
<image wx:if="{{photos[1]}}" class="upload-preview" mode="aspectFill" src="{{photos[1]}}"></image>
|
||||
<block wx:else>
|
||||
<text class="upload-icon">📷</text>
|
||||
<text class="upload-text">点击上传</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧 45 度 -->
|
||||
<view class="step-item">
|
||||
<view class="step-index step-index-secondary">
|
||||
<text class="step-index-text step-index-text-secondary">3</text>
|
||||
</view>
|
||||
<view class="step-body">
|
||||
<text class="step-title">右侧 45度</text>
|
||||
<text class="step-subtitle">展示右侧面颊,完整捕捉面部信息</text>
|
||||
<view class="step-upload-row">
|
||||
<image class="sample-image" mode="aspectFill" src="{{sampleImages[2]}}"></image>
|
||||
<view class="upload-card" bindtap="onUploadRight">
|
||||
<image wx:if="{{photos[2]}}" class="upload-preview" mode="aspectFill" src="{{photos[2]}}"></image>
|
||||
<block wx:else>
|
||||
<text class="upload-icon">📷</text>
|
||||
<text class="upload-text">点击上传</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<view class="tips-card">
|
||||
<view class="tips-title-row">
|
||||
<view class="tips-icon"></view>
|
||||
<text class="tips-title">温馨提示</text>
|
||||
</view>
|
||||
<view class="tips-list">
|
||||
<text class="tips-item">· 光线充足,背景整洁,避免过曝或过暗</text>
|
||||
<text class="tips-item">· 保持表情自然,无需刻意微笑</text>
|
||||
<text class="tips-item">· 如有佩戴眼镜,请确保镜片不反光</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手机号授权入口:仅在未授权时显示 -->
|
||||
<view class="phone-auth-section" wx:if="{{needPhoneAuth}}">
|
||||
<text class="phone-auth-tip">为保障服务与联系,请先授权手机号。</text>
|
||||
<button
|
||||
class="phone-auth-btn"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumber"
|
||||
>
|
||||
授权手机号后开始查看报告
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="footer">
|
||||
<button class="primary-btn" bindtap="completeCapture">智能分析</button>
|
||||
</view>
|
||||
|
||||
<!-- 自定义底部导航,与首页保持一致 -->
|
||||
<custom-tab-bar />
|
||||
</view>
|
||||
<!--pages/index/upload.wxml - 上传照片引导页-->
|
||||
<view class="page">
|
||||
<!-- 主体内容 -->
|
||||
<view class="content">
|
||||
<!-- 引导文案 -->
|
||||
<view class="intro">
|
||||
<text class="intro-title">多角度拍摄更精准</text>
|
||||
<view class="intro-desc-wrap">
|
||||
<text class="intro-desc">为了更准确地分析您的性格特征,</text>
|
||||
<text class="intro-desc">请上传以下三个维度的照片。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 三个角度上传卡片 -->
|
||||
<view class="steps">
|
||||
<!-- 正面 -->
|
||||
<view class="step-item">
|
||||
<view class="step-index step-index-primary">
|
||||
<text class="step-index-text step-index-text-primary">1</text>
|
||||
</view>
|
||||
<view class="step-body">
|
||||
<text class="step-title">正面</text>
|
||||
<text class="step-subtitle">目视前方,五官端正,不遮挡额头</text>
|
||||
<view class="step-upload-row">
|
||||
<image class="sample-image" mode="aspectFill" src="{{sampleImages[0]}}"></image>
|
||||
<view class="upload-card" bindtap="onUploadFront">
|
||||
<image wx:if="{{photos[0]}}" class="upload-preview" mode="aspectFill" src="{{photos[0]}}"></image>
|
||||
<block wx:else>
|
||||
<text class="upload-icon">📷</text>
|
||||
<text class="upload-text">点击上传</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 左侧 45 度 -->
|
||||
<view class="step-item">
|
||||
<view class="step-index step-index-secondary">
|
||||
<text class="step-index-text step-index-text-secondary">2</text>
|
||||
</view>
|
||||
<view class="step-body">
|
||||
<text class="step-title">左侧 45度</text>
|
||||
<text class="step-subtitle">展示左侧面颊,用于分析面部轮廓</text>
|
||||
<view class="step-upload-row">
|
||||
<image class="sample-image" mode="aspectFill" src="{{sampleImages[1]}}"></image>
|
||||
<view class="upload-card" bindtap="onUploadLeft">
|
||||
<image wx:if="{{photos[1]}}" class="upload-preview" mode="aspectFill" src="{{photos[1]}}"></image>
|
||||
<block wx:else>
|
||||
<text class="upload-icon">📷</text>
|
||||
<text class="upload-text">点击上传</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧 45 度 -->
|
||||
<view class="step-item">
|
||||
<view class="step-index step-index-secondary">
|
||||
<text class="step-index-text step-index-text-secondary">3</text>
|
||||
</view>
|
||||
<view class="step-body">
|
||||
<text class="step-title">右侧 45度</text>
|
||||
<text class="step-subtitle">展示右侧面颊,完整捕捉面部信息</text>
|
||||
<view class="step-upload-row">
|
||||
<image class="sample-image" mode="aspectFill" src="{{sampleImages[2]}}"></image>
|
||||
<view class="upload-card" bindtap="onUploadRight">
|
||||
<image wx:if="{{photos[2]}}" class="upload-preview" mode="aspectFill" src="{{photos[2]}}"></image>
|
||||
<block wx:else>
|
||||
<text class="upload-icon">📷</text>
|
||||
<text class="upload-text">点击上传</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<view class="tips-card">
|
||||
<view class="tips-title-row">
|
||||
<view class="tips-icon"></view>
|
||||
<text class="tips-title">温馨提示</text>
|
||||
</view>
|
||||
<view class="tips-list">
|
||||
<text class="tips-item">· 光线充足,背景整洁,避免过曝或过暗</text>
|
||||
<text class="tips-item">· 保持表情自然,无需刻意微笑</text>
|
||||
<text class="tips-item">· 如有佩戴眼镜,请确保镜片不反光</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手机号授权入口:仅在未授权时显示 -->
|
||||
<view class="phone-auth-section" wx:if="{{needPhoneAuth}}">
|
||||
<text class="phone-auth-tip">为保障服务与联系,请先授权手机号。</text>
|
||||
<button
|
||||
class="phone-auth-btn"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumber"
|
||||
>
|
||||
授权手机号后开始查看报告
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="footer">
|
||||
<button class="primary-btn" bindtap="completeCapture">开始分析</button>
|
||||
</view>
|
||||
|
||||
<!-- 自定义底部导航,与首页保持一致 -->
|
||||
<custom-tab-bar />
|
||||
</view>
|
||||
|
||||
@@ -1,243 +1,276 @@
|
||||
/* pages/index/camera.wxss - 上传照片引导页(参照设计稿) */
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 主体内容 */
|
||||
.content {
|
||||
flex: 1;
|
||||
/* 底部按钮作为正常内容,在 tabBar 之上留出空间 */
|
||||
padding: 40rpx 48rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 顶部说明文案 */
|
||||
.intro {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.intro-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.intro-desc-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
row-gap: 4rpx;
|
||||
}
|
||||
|
||||
.intro-desc {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* 三个步骤区域 */
|
||||
.steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 40rpx;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.step-index {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 9999rpx;
|
||||
border-width: 4rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.step-index-primary {
|
||||
background-color: #ffe5e9;
|
||||
border-color: #ff5e7d;
|
||||
}
|
||||
|
||||
.step-index-secondary {
|
||||
background-color: #f3f4f6;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.step-index-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.step-index-text-primary {
|
||||
color: #ff5e7d;
|
||||
}
|
||||
|
||||
.step-index-text-secondary {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.step-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.step-subtitle {
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.step-upload-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 24rpx;
|
||||
}
|
||||
|
||||
.sample-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 24rpx;
|
||||
border: 2rpx solid #f3f4f6;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 24rpx;
|
||||
border: 4rpx dashed #fecaca;
|
||||
background-color: #fff5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #f97373;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 40rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.upload-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 温馨提示卡片 */
|
||||
.tips-card {
|
||||
margin-top: 48rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #fff5f5;
|
||||
border: 2rpx solid #ffe0e0;
|
||||
}
|
||||
|
||||
.tips-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.tips-icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 9999rpx;
|
||||
border: 4rpx solid #e63946;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
}
|
||||
|
||||
.tips-list {
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.tips-item {
|
||||
font-size: 24rpx;
|
||||
color: #b91c1c;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* 底部按钮区域 */
|
||||
.footer {
|
||||
padding: 24rpx 0 0;
|
||||
margin: 24rpx 48rpx calc(160rpx + env(safe-area-inset-bottom));
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.footer .primary-btn{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.phone-auth-section {
|
||||
padding: 24rpx 24rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.phone-auth-tip {
|
||||
font-size: 26rpx;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.phone-auth-btn {
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%);
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.phone-auth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
width: 100%;
|
||||
padding: 24rpx 0;
|
||||
border-radius: 9999rpx;
|
||||
border: none;
|
||||
background-image: linear-gradient(90deg, #ff5e7d 0%, #ff85a1 100%);
|
||||
box-shadow: 0 10rpx 25rpx rgba(248, 113, 113, 0.45);
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.primary-btn::after {
|
||||
border: none;
|
||||
}
|
||||
/* pages/index/camera.wxss - 上传照片引导页(参照设计稿) */
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 主体内容 */
|
||||
.content {
|
||||
flex: 1;
|
||||
/* 底部按钮作为正常内容,在 tabBar 之上留出空间 */
|
||||
padding: 40rpx 48rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 顶部说明文案 */
|
||||
.intro {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.intro-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.intro-desc-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
row-gap: 4rpx;
|
||||
}
|
||||
|
||||
.intro-desc {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* 三个步骤区域 */
|
||||
.steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 40rpx;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.step-index {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 9999rpx;
|
||||
border-width: 4rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.step-index-primary {
|
||||
background-color: #ffe5e9;
|
||||
border-color: #ff5e7d;
|
||||
}
|
||||
|
||||
.step-index-secondary {
|
||||
background-color: #f3f4f6;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.step-index-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.step-index-text-primary {
|
||||
color: #ff5e7d;
|
||||
}
|
||||
|
||||
.step-index-text-secondary {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.step-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.step-subtitle {
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.step-upload-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 24rpx;
|
||||
}
|
||||
|
||||
.sample-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 24rpx;
|
||||
border: 2rpx solid #f3f4f6;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 24rpx;
|
||||
border: 4rpx dashed #fecaca;
|
||||
background-color: #fff5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #f97373;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 40rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.upload-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 温馨提示卡片 */
|
||||
.tips-card {
|
||||
margin-top: 48rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #fff5f5;
|
||||
border: 2rpx solid #ffe0e0;
|
||||
}
|
||||
|
||||
.tips-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.tips-icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 9999rpx;
|
||||
border: 4rpx solid #e63946;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
}
|
||||
|
||||
.tips-list {
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.tips-item {
|
||||
font-size: 24rpx;
|
||||
color: #b91c1c;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* 底部按钮区域 */
|
||||
.footer {
|
||||
padding: 24rpx 0 0;
|
||||
margin: 24rpx 48rpx calc(160rpx + env(safe-area-inset-bottom));
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.footer .primary-btn{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.phone-auth-section {
|
||||
padding: 24rpx 24rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.phone-auth-tip {
|
||||
font-size: 26rpx;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.phone-auth-btn {
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%);
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.phone-auth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* AI 生成内容标识横幅 */
|
||||
.ai-disclosure-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
margin: 32rpx 48rpx 0;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #f0f4ff 0%, #e8ecf8 100%);
|
||||
border: 1rpx solid #c7d2fe;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.ai-disclosure-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
border-radius: 8rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ai-disclosure-text {
|
||||
font-size: 22rpx;
|
||||
color: #4338ca;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
width: 100%;
|
||||
padding: 24rpx 0;
|
||||
border-radius: 9999rpx;
|
||||
border: none;
|
||||
background-image: linear-gradient(90deg, #ff5e7d 0%, #ff85a1 100%);
|
||||
box-shadow: 0 10rpx 25rpx rgba(248, 113, 113, 0.45);
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.primary-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
// 手机号授权页:用户点击按钮授权后,用 code 换手机号并写回 userInfo,再返回或跳转 next
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
next: '',
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.setData({
|
||||
next: options.next ? decodeURIComponent(options.next) : '',
|
||||
})
|
||||
},
|
||||
|
||||
onGetPhoneNumber(e) {
|
||||
const { code, errMsg } = e.detail || {}
|
||||
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
|
||||
wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!code) {
|
||||
wx.showToast({ title: '获取手机号失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const token = app.globalData.token || wx.getStorageSync('token')
|
||||
if (!token) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '处理中...', mask: true })
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase.replace(/\/$/, '')}/api/auth/wechat/phone`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: { code },
|
||||
success: (res) => {
|
||||
wx.hideLoading()
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const data = res.data.data || {}
|
||||
const user = data.user || app.globalData.userInfo || {}
|
||||
const phone = data.phone || user.phone || ''
|
||||
const newUser = { ...user, phone }
|
||||
app.globalData.userInfo = newUser
|
||||
wx.setStorageSync('userInfo', newUser)
|
||||
wx.showToast({ title: '授权成功', icon: 'success' })
|
||||
const nextPath = this.data.next && this.data.next.startsWith('/') ? this.data.next : ''
|
||||
const tabBarPaths = ['/pages/index/index', '/pages/index/camera', '/pages/profile/index']
|
||||
const isTabBar = tabBarPaths.some(p => nextPath === p || nextPath.startsWith(p + '?'))
|
||||
if (nextPath) {
|
||||
setTimeout(() => {
|
||||
if (isTabBar) {
|
||||
const pathOnly = nextPath.split('?')[0]
|
||||
wx.switchTab({ url: pathOnly, fail: () => wx.navigateBack() })
|
||||
} else {
|
||||
wx.redirectTo({ url: nextPath, fail: () => wx.navigateBack() })
|
||||
}
|
||||
}, 500)
|
||||
} else {
|
||||
setTimeout(() => wx.navigateBack(), 500)
|
||||
}
|
||||
} else {
|
||||
wx.showToast({ title: res.data && res.data.message ? res.data.message : '获取手机号失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '网络请求失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
// 手机号授权页:用户点击按钮授权后,用 code 换手机号并写回 userInfo,再返回或跳转 next
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
next: '',
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.setData({
|
||||
next: options.next ? decodeURIComponent(options.next) : '',
|
||||
})
|
||||
},
|
||||
|
||||
onGetPhoneNumber(e) {
|
||||
const { code, errMsg } = e.detail || {}
|
||||
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
|
||||
wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!code) {
|
||||
wx.showToast({ title: '获取手机号失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const token = app.globalData.token || wx.getStorageSync('token')
|
||||
if (!token) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '处理中...', mask: true })
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase.replace(/\/$/, '')}/api/auth/wechat/phone`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: { code },
|
||||
success: (res) => {
|
||||
wx.hideLoading()
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const data = res.data.data || {}
|
||||
const user = data.user || app.globalData.userInfo || {}
|
||||
const phone = data.phone || user.phone || ''
|
||||
const newUser = { ...user, phone }
|
||||
app.globalData.userInfo = newUser
|
||||
wx.setStorageSync('userInfo', newUser)
|
||||
wx.showToast({ title: '授权成功', icon: 'success' })
|
||||
const nextPath = this.data.next && this.data.next.startsWith('/') ? this.data.next : ''
|
||||
const tabBarPaths = ['/pages/index/index', '/pages/index/camera', '/pages/profile/index']
|
||||
const isTabBar = tabBarPaths.some(p => nextPath === p || nextPath.startsWith(p + '?'))
|
||||
if (nextPath) {
|
||||
setTimeout(() => {
|
||||
if (isTabBar) {
|
||||
const pathOnly = nextPath.split('?')[0]
|
||||
wx.switchTab({ url: pathOnly, fail: () => wx.navigateBack() })
|
||||
} else {
|
||||
wx.redirectTo({ url: nextPath, fail: () => wx.navigateBack() })
|
||||
}
|
||||
}, 500)
|
||||
} else {
|
||||
setTimeout(() => wx.navigateBack(), 500)
|
||||
}
|
||||
} else {
|
||||
wx.showToast({ title: res.data && res.data.message ? res.data.message : '获取手机号失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '网络请求失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "授权手机号",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "授权手机号",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<view class="container">
|
||||
<view class="tip">为保障服务与联系需要,请授权您的手机号。</view>
|
||||
<view class="tip sub">授权后可在「查看报告」「支付」「企业咨询」等场景使用,仅需授权一次。</view>
|
||||
<button class="auth-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">授权手机号</button>
|
||||
</view>
|
||||
<view class="container">
|
||||
<view class="tip">为保障服务与联系需要,请授权您的手机号。</view>
|
||||
<view class="tip sub">授权后可在「查看报告」「支付」「企业咨询」等场景使用,仅需授权一次。</view>
|
||||
<button class="auth-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">授权手机号</button>
|
||||
</view>
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
.container {
|
||||
padding: 48rpx 32rpx;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tip {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.tip.sub {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 64rpx;
|
||||
}
|
||||
.auth-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%);
|
||||
color: #fff;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
}
|
||||
.auth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
.container {
|
||||
padding: 48rpx 32rpx;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tip {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.tip.sub {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 64rpx;
|
||||
}
|
||||
.auth-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%);
|
||||
color: #fff;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
}
|
||||
.auth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ Page({
|
||||
discTime: '',
|
||||
pdpTime: '',
|
||||
aiTime: '',
|
||||
reviewMode: false,
|
||||
/** 最近记录的数据库 ID,用于跳转时传参 */
|
||||
mbtiResultId: null,
|
||||
discResultId: null,
|
||||
@@ -45,13 +46,16 @@ Page({
|
||||
this.runLoginThenLoad()
|
||||
},
|
||||
onShow() {
|
||||
// 同步审核模式
|
||||
this.setData({ reviewMode: !!app.globalData.reviewMode })
|
||||
// 如果是从其他页面返回,且已经登录,则只刷新数据而不重新执行登录流程
|
||||
if (this.data.hasLogin) {
|
||||
this.loadData()
|
||||
}
|
||||
|
||||
const tabBar = typeof this.getTabBar === 'function' && this.getTabBar()
|
||||
if (tabBar) tabBar.setData({ selected: 2, maintenanceMode: !!app.globalData.maintenanceMode })
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
this.getTabBar().setData({ selected: 2 })
|
||||
}
|
||||
},
|
||||
|
||||
/** 先确保登录完成(静默登录),再刷新页面数据 */
|
||||
@@ -301,16 +305,18 @@ Page({
|
||||
|
||||
onShareAppMessage() {
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
const rm = !!app.globalData.reviewMode
|
||||
return {
|
||||
title: '神仙团队AI性格测试 - 发现你的MBTI类型',
|
||||
title: '神仙团队性格测试 - 发现你的MBTI类型',
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
const rm = !!app.globalData.reviewMode
|
||||
return {
|
||||
title: '神仙团队AI性格测试 - 发现你的MBTI类型',
|
||||
title: '神仙团队性格测试 - 发现你的MBTI类型',
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#F9FAFB",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#F9FAFB",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -108,8 +108,8 @@
|
||||
<text class="card-value">{{pdpType}}</text>
|
||||
<text class="card-time">{{pdpTime}}</text>
|
||||
</view>
|
||||
<!-- AI 面相 -->
|
||||
<view class="result-card card-rose" wx:if="{{aiType}}" bindtap="viewAI">
|
||||
<!-- AI 面相(审核模式下隐藏) -->
|
||||
<view class="result-card card-rose" wx:if="{{aiType && !reviewMode}}" bindtap="viewAI">
|
||||
<view class="card-deco"></view>
|
||||
<view class="card-icon-wrap card-icon-rose">
|
||||
<text class="card-icon">👁️</text>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,270 +1,270 @@
|
||||
// pages/promo/index.js
|
||||
const app = getApp()
|
||||
const { request } = require('../../utils/request')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
balance: '0.00',
|
||||
totalEarned: '0.00',
|
||||
pendingAmount: '0.00',
|
||||
bindingCount: 0,
|
||||
paidCount: 0,
|
||||
expiringCount: 0,
|
||||
activeTab: 0,
|
||||
userList: [],
|
||||
listTotal: 0,
|
||||
listPage: 1,
|
||||
listLoading: false,
|
||||
listFinished: false,
|
||||
commissionRate: '',
|
||||
bindingDays: '',
|
||||
testCommissionType: '',
|
||||
testCommissionRate: '',
|
||||
testCommissionAmount: '',
|
||||
testNoPayment: false,
|
||||
withdrawMinYuan: '1.00',
|
||||
withdrawMaxYuan: '',
|
||||
withdrawFeePct: 0,
|
||||
requireWithdrawAudit: true,
|
||||
showWithdrawDialog: false,
|
||||
withdrawAmountInput: '',
|
||||
withdrawError: '',
|
||||
withdrawFeeYuan: '0.00',
|
||||
withdrawActualYuan: '0.00'
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadStats()
|
||||
this.loadBindings(true)
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadStats()
|
||||
},
|
||||
|
||||
/** 加载推广统计数据 */
|
||||
loadStats() {
|
||||
request({
|
||||
url: '/api/distribution/stats',
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
const payload = res && res.data
|
||||
if (payload && payload.code === 200 && payload.data) {
|
||||
const d = payload.data
|
||||
const title = d.promoCenterTitle || '推广中心'
|
||||
wx.setNavigationBarTitle({ title })
|
||||
this.setData({
|
||||
balance: d.walletBalance,
|
||||
totalEarned: d.totalEarned,
|
||||
pendingAmount: d.pendingAmount,
|
||||
bindingCount: d.bindingCount,
|
||||
paidCount: d.paidCount,
|
||||
expiringCount: d.expiringCount,
|
||||
totalInvite: d.totalInvite,
|
||||
commissionRate: d.commissionRate,
|
||||
bindingDays: d.bindingDays,
|
||||
testCommissionType: d.testCommissionType,
|
||||
testCommissionRate: d.testCommissionRate,
|
||||
testCommissionAmount: d.testCommissionAmount,
|
||||
testNoPayment: d.testNoPayment,
|
||||
withdrawMinYuan: d.withdrawMinYuan != null ? d.withdrawMinYuan : '1.00',
|
||||
withdrawMaxYuan: d.withdrawMaxYuan != null && d.withdrawMaxYuan !== '' ? d.withdrawMaxYuan : '',
|
||||
withdrawFeePct: d.withdrawFeePct != null ? d.withdrawFeePct : 0,
|
||||
requireWithdrawAudit: d.requireWithdrawAudit !== false,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 加载绑定用户列表(tab: 0=绑定中 1=已付款 2=已过期) */
|
||||
loadBindings(reset = false) {
|
||||
if (this.data.listLoading) return
|
||||
if (!reset && this.data.listFinished) return
|
||||
|
||||
const page = reset ? 1 : this.data.listPage
|
||||
this.setData({ listLoading: true })
|
||||
|
||||
request({
|
||||
url: `/api/distribution/bindings?tab=${this.data.activeTab}&page=${page}&pageSize=10`,
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
const payload = res && res.data
|
||||
if (payload && payload.code === 200 && payload.data) {
|
||||
const { list, total } = payload.data
|
||||
const formatted = (list || []).map(item => ({
|
||||
...item,
|
||||
createdAtStr: item.createdAt ? this._fmtTimestamp(item.createdAt) : ''
|
||||
}))
|
||||
const newList = reset ? formatted : [...this.data.userList, ...formatted]
|
||||
this.setData({
|
||||
userList: newList,
|
||||
listTotal: total,
|
||||
listPage: page + 1,
|
||||
listFinished: newList.length >= total,
|
||||
})
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.setData({ listLoading: false })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 时间戳格式化为 YYYY-MM-DD */
|
||||
_fmtTimestamp(ts) {
|
||||
if (!ts) return ''
|
||||
const d = new Date(ts * 1000)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
},
|
||||
|
||||
/** 切换用户列表 Tab */
|
||||
switchTab(e) {
|
||||
const index = parseInt(e.currentTarget.dataset.index)
|
||||
if (index === this.data.activeTab) return
|
||||
this.setData({ activeTab: index, userList: [], listPage: 1, listFinished: false })
|
||||
this.loadBindings(true)
|
||||
},
|
||||
|
||||
/** 上拉加载更多 */
|
||||
onReachBottom() {
|
||||
this.loadBindings(false)
|
||||
},
|
||||
|
||||
/** 申请提现:打开自定义金额弹框 */
|
||||
handleWithdraw() {
|
||||
const balance = parseFloat(this.data.balance)
|
||||
if (balance < 1) {
|
||||
wx.showToast({ title: '余额不足1元,暂无法提现', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const pct = this.data.withdrawFeePct || 0
|
||||
const feeYuan = (balance * pct / 100).toFixed(2)
|
||||
const actualYuan = (balance - balance * pct / 100).toFixed(2)
|
||||
this.setData({
|
||||
showWithdrawDialog: true,
|
||||
withdrawAmountInput: this.data.balance,
|
||||
withdrawFeeYuan: feeYuan,
|
||||
withdrawActualYuan: actualYuan,
|
||||
withdrawError: ''
|
||||
})
|
||||
},
|
||||
|
||||
/** 关闭提现弹框 */
|
||||
closeWithdrawDialog() {
|
||||
this.setData({
|
||||
showWithdrawDialog: false,
|
||||
withdrawError: ''
|
||||
})
|
||||
},
|
||||
|
||||
/** 输入金额:实时计算手续费与实际到账 */
|
||||
onWithdrawInput(e) {
|
||||
const raw = e.detail.value
|
||||
const val = parseFloat(raw)
|
||||
const pct = this.data.withdrawFeePct || 0
|
||||
let feeYuan = '0.00'
|
||||
let actualYuan = '0.00'
|
||||
if (raw !== '' && !isNaN(val) && val >= 0) {
|
||||
const fee = val * pct / 100
|
||||
feeYuan = fee.toFixed(2)
|
||||
actualYuan = (val - fee).toFixed(2)
|
||||
}
|
||||
this.setData({
|
||||
withdrawAmountInput: raw,
|
||||
withdrawFeeYuan: feeYuan,
|
||||
withdrawActualYuan: actualYuan,
|
||||
withdrawError: ''
|
||||
})
|
||||
},
|
||||
|
||||
/** 确认提现(使用用户填写的金额) */
|
||||
confirmWithdraw() {
|
||||
const balance = parseFloat(this.data.balance)
|
||||
const val = parseFloat(this.data.withdrawAmountInput)
|
||||
const minYuan = parseFloat(this.data.withdrawMinYuan) || 1
|
||||
const pct = this.data.withdrawFeePct || 0
|
||||
|
||||
if (isNaN(val)) {
|
||||
this.setData({ withdrawError: '请输入正确的金额' })
|
||||
return
|
||||
}
|
||||
if (val < 1) {
|
||||
this.setData({ withdrawError: '单次提现金额至少 1 元' })
|
||||
return
|
||||
}
|
||||
if (val > balance) {
|
||||
this.setData({ withdrawError: '不可超过当前可提现金额' })
|
||||
return
|
||||
}
|
||||
const actualYuan = val - val * pct / 100
|
||||
if (actualYuan < minYuan) {
|
||||
this.setData({ withdrawError: `实际到账金额不得低于最低提现金额 ¥${minYuan.toFixed(2)}` })
|
||||
return
|
||||
}
|
||||
|
||||
const amountFen = Math.floor(val * 100)
|
||||
this.setData({ withdrawError: '' })
|
||||
|
||||
request({
|
||||
url: '/api/distribution/withdraw',
|
||||
method: 'POST',
|
||||
data: { amountFen },
|
||||
success: (r) => {
|
||||
const payload = r && r.data
|
||||
if (payload && payload.code === 200) {
|
||||
const msg = payload.msg || payload.message || ''
|
||||
wx.showToast({ title: '申请已提交', icon: 'success' })
|
||||
this.setData({ showWithdrawDialog: false })
|
||||
this.loadStats()
|
||||
// 免审核且已自动发起微信转账时,自动进入提现记录页
|
||||
if (msg.indexOf('已自动发起') !== -1) {
|
||||
setTimeout(() => {
|
||||
wx.navigateTo({ url: '/pages/promo/withdrawals' })
|
||||
}, 500)
|
||||
}
|
||||
} else {
|
||||
const errMsg = (payload && (payload.msg || payload.message)) || '申请失败,请稍后重试'
|
||||
this.setData({ withdrawError: errMsg })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 查看提现记录 */
|
||||
goToWithdrawHistory() {
|
||||
wx.navigateTo({ url: '/pages/promo/withdrawals' })
|
||||
},
|
||||
|
||||
/** 生成海报 */
|
||||
generatePoster() {
|
||||
wx.navigateTo({ url: '/pages/promo/poster' })
|
||||
},
|
||||
|
||||
/** 分享到朋友圈:引导用户使用右上角菜单 */
|
||||
shareToTimeline() {
|
||||
wx.showToast({ title: '请点击右上角 ··· 选择「分享到朋友圈」', icon: 'none', duration: 2500 })
|
||||
},
|
||||
|
||||
/** 分享给好友 */
|
||||
onShareAppMessage() {
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: '神仙团队AI性格测试 - 发现你的内在潜能',
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
/** 分享到朋友圈 */
|
||||
onShareTimeline() {
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: '神仙团队AI性格测试 - 发现你的内在潜能',
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
// pages/promo/index.js
|
||||
const app = getApp()
|
||||
const { request } = require('../../utils/request')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
balance: '0.00',
|
||||
totalEarned: '0.00',
|
||||
pendingAmount: '0.00',
|
||||
bindingCount: 0,
|
||||
paidCount: 0,
|
||||
expiringCount: 0,
|
||||
activeTab: 0,
|
||||
userList: [],
|
||||
listTotal: 0,
|
||||
listPage: 1,
|
||||
listLoading: false,
|
||||
listFinished: false,
|
||||
commissionRate: '',
|
||||
bindingDays: '',
|
||||
testCommissionType: '',
|
||||
testCommissionRate: '',
|
||||
testCommissionAmount: '',
|
||||
testNoPayment: false,
|
||||
withdrawMinYuan: '1.00',
|
||||
withdrawMaxYuan: '',
|
||||
withdrawFeePct: 0,
|
||||
requireWithdrawAudit: true,
|
||||
showWithdrawDialog: false,
|
||||
withdrawAmountInput: '',
|
||||
withdrawError: '',
|
||||
withdrawFeeYuan: '0.00',
|
||||
withdrawActualYuan: '0.00'
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadStats()
|
||||
this.loadBindings(true)
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadStats()
|
||||
},
|
||||
|
||||
/** 加载推广统计数据 */
|
||||
loadStats() {
|
||||
request({
|
||||
url: '/api/distribution/stats',
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
const payload = res && res.data
|
||||
if (payload && payload.code === 200 && payload.data) {
|
||||
const d = payload.data
|
||||
const title = d.promoCenterTitle || '推广中心'
|
||||
wx.setNavigationBarTitle({ title })
|
||||
this.setData({
|
||||
balance: d.walletBalance,
|
||||
totalEarned: d.totalEarned,
|
||||
pendingAmount: d.pendingAmount,
|
||||
bindingCount: d.bindingCount,
|
||||
paidCount: d.paidCount,
|
||||
expiringCount: d.expiringCount,
|
||||
totalInvite: d.totalInvite,
|
||||
commissionRate: d.commissionRate,
|
||||
bindingDays: d.bindingDays,
|
||||
testCommissionType: d.testCommissionType,
|
||||
testCommissionRate: d.testCommissionRate,
|
||||
testCommissionAmount: d.testCommissionAmount,
|
||||
testNoPayment: d.testNoPayment,
|
||||
withdrawMinYuan: d.withdrawMinYuan != null ? d.withdrawMinYuan : '1.00',
|
||||
withdrawMaxYuan: d.withdrawMaxYuan != null && d.withdrawMaxYuan !== '' ? d.withdrawMaxYuan : '',
|
||||
withdrawFeePct: d.withdrawFeePct != null ? d.withdrawFeePct : 0,
|
||||
requireWithdrawAudit: d.requireWithdrawAudit !== false,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 加载绑定用户列表(tab: 0=绑定中 1=已付款 2=已过期) */
|
||||
loadBindings(reset = false) {
|
||||
if (this.data.listLoading) return
|
||||
if (!reset && this.data.listFinished) return
|
||||
|
||||
const page = reset ? 1 : this.data.listPage
|
||||
this.setData({ listLoading: true })
|
||||
|
||||
request({
|
||||
url: `/api/distribution/bindings?tab=${this.data.activeTab}&page=${page}&pageSize=10`,
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
const payload = res && res.data
|
||||
if (payload && payload.code === 200 && payload.data) {
|
||||
const { list, total } = payload.data
|
||||
const formatted = (list || []).map(item => ({
|
||||
...item,
|
||||
createdAtStr: item.createdAt ? this._fmtTimestamp(item.createdAt) : ''
|
||||
}))
|
||||
const newList = reset ? formatted : [...this.data.userList, ...formatted]
|
||||
this.setData({
|
||||
userList: newList,
|
||||
listTotal: total,
|
||||
listPage: page + 1,
|
||||
listFinished: newList.length >= total,
|
||||
})
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.setData({ listLoading: false })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 时间戳格式化为 YYYY-MM-DD */
|
||||
_fmtTimestamp(ts) {
|
||||
if (!ts) return ''
|
||||
const d = new Date(ts * 1000)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
},
|
||||
|
||||
/** 切换用户列表 Tab */
|
||||
switchTab(e) {
|
||||
const index = parseInt(e.currentTarget.dataset.index)
|
||||
if (index === this.data.activeTab) return
|
||||
this.setData({ activeTab: index, userList: [], listPage: 1, listFinished: false })
|
||||
this.loadBindings(true)
|
||||
},
|
||||
|
||||
/** 上拉加载更多 */
|
||||
onReachBottom() {
|
||||
this.loadBindings(false)
|
||||
},
|
||||
|
||||
/** 申请提现:打开自定义金额弹框 */
|
||||
handleWithdraw() {
|
||||
const balance = parseFloat(this.data.balance)
|
||||
if (balance < 1) {
|
||||
wx.showToast({ title: '余额不足1元,暂无法提现', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const pct = this.data.withdrawFeePct || 0
|
||||
const feeYuan = (balance * pct / 100).toFixed(2)
|
||||
const actualYuan = (balance - balance * pct / 100).toFixed(2)
|
||||
this.setData({
|
||||
showWithdrawDialog: true,
|
||||
withdrawAmountInput: this.data.balance,
|
||||
withdrawFeeYuan: feeYuan,
|
||||
withdrawActualYuan: actualYuan,
|
||||
withdrawError: ''
|
||||
})
|
||||
},
|
||||
|
||||
/** 关闭提现弹框 */
|
||||
closeWithdrawDialog() {
|
||||
this.setData({
|
||||
showWithdrawDialog: false,
|
||||
withdrawError: ''
|
||||
})
|
||||
},
|
||||
|
||||
/** 输入金额:实时计算手续费与实际到账 */
|
||||
onWithdrawInput(e) {
|
||||
const raw = e.detail.value
|
||||
const val = parseFloat(raw)
|
||||
const pct = this.data.withdrawFeePct || 0
|
||||
let feeYuan = '0.00'
|
||||
let actualYuan = '0.00'
|
||||
if (raw !== '' && !isNaN(val) && val >= 0) {
|
||||
const fee = val * pct / 100
|
||||
feeYuan = fee.toFixed(2)
|
||||
actualYuan = (val - fee).toFixed(2)
|
||||
}
|
||||
this.setData({
|
||||
withdrawAmountInput: raw,
|
||||
withdrawFeeYuan: feeYuan,
|
||||
withdrawActualYuan: actualYuan,
|
||||
withdrawError: ''
|
||||
})
|
||||
},
|
||||
|
||||
/** 确认提现(使用用户填写的金额) */
|
||||
confirmWithdraw() {
|
||||
const balance = parseFloat(this.data.balance)
|
||||
const val = parseFloat(this.data.withdrawAmountInput)
|
||||
const minYuan = parseFloat(this.data.withdrawMinYuan) || 1
|
||||
const pct = this.data.withdrawFeePct || 0
|
||||
|
||||
if (isNaN(val)) {
|
||||
this.setData({ withdrawError: '请输入正确的金额' })
|
||||
return
|
||||
}
|
||||
if (val < 1) {
|
||||
this.setData({ withdrawError: '单次提现金额至少 1 元' })
|
||||
return
|
||||
}
|
||||
if (val > balance) {
|
||||
this.setData({ withdrawError: '不可超过当前可提现金额' })
|
||||
return
|
||||
}
|
||||
const actualYuan = val - val * pct / 100
|
||||
if (actualYuan < minYuan) {
|
||||
this.setData({ withdrawError: `实际到账金额不得低于最低提现金额 ¥${minYuan.toFixed(2)}` })
|
||||
return
|
||||
}
|
||||
|
||||
const amountFen = Math.floor(val * 100)
|
||||
this.setData({ withdrawError: '' })
|
||||
|
||||
request({
|
||||
url: '/api/distribution/withdraw',
|
||||
method: 'POST',
|
||||
data: { amountFen },
|
||||
success: (r) => {
|
||||
const payload = r && r.data
|
||||
if (payload && payload.code === 200) {
|
||||
const msg = payload.msg || payload.message || ''
|
||||
wx.showToast({ title: '申请已提交', icon: 'success' })
|
||||
this.setData({ showWithdrawDialog: false })
|
||||
this.loadStats()
|
||||
// 免审核且已自动发起微信转账时,自动进入提现记录页
|
||||
if (msg.indexOf('已自动发起') !== -1) {
|
||||
setTimeout(() => {
|
||||
wx.navigateTo({ url: '/pages/promo/withdrawals' })
|
||||
}, 500)
|
||||
}
|
||||
} else {
|
||||
const errMsg = (payload && (payload.msg || payload.message)) || '申请失败,请稍后重试'
|
||||
this.setData({ withdrawError: errMsg })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 查看提现记录 */
|
||||
goToWithdrawHistory() {
|
||||
wx.navigateTo({ url: '/pages/promo/withdrawals' })
|
||||
},
|
||||
|
||||
/** 生成海报 */
|
||||
generatePoster() {
|
||||
wx.navigateTo({ url: '/pages/promo/poster' })
|
||||
},
|
||||
|
||||
/** 分享到朋友圈:引导用户使用右上角菜单 */
|
||||
shareToTimeline() {
|
||||
wx.showToast({ title: '请点击右上角 ··· 选择「分享到朋友圈」', icon: 'none', duration: 2500 })
|
||||
},
|
||||
|
||||
/** 分享给好友 */
|
||||
onShareAppMessage() {
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: '神仙团队性格测试 - 发现你的内在潜能',
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
/** 分享到朋友圈 */
|
||||
onShareTimeline() {
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: '神仙团队性格测试 - 发现你的内在潜能',
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "推广中心",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black"
|
||||
{
|
||||
"navigationBarTitleText": "推广中心",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
@@ -1,219 +1,219 @@
|
||||
<!--pages/promo/index.wxml-->
|
||||
<view class="container">
|
||||
<!-- 顶部统计卡片 -->
|
||||
<view class="hero-card">
|
||||
<view class="hero-header-row">
|
||||
<view class="hero-left">
|
||||
<view class="wallet-icon">
|
||||
<text class="emoji">🧧</text>
|
||||
</view>
|
||||
<view class="title-text">
|
||||
<text class="label">可提现金额</text>
|
||||
<view class="badge">
|
||||
<view class="dot"></view>
|
||||
<text class="badge-text">{{commissionRate}}% 高额返利</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="hero-right">
|
||||
<text class="amount">¥{{balance}}</text>
|
||||
<text class="amount-sub">累计: ¥{{totalEarned}} | 待审核: ¥{{pendingAmount}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="withdraw-btn {{balance > 0 ? '' : 'disabled'}}" bindtap="handleWithdraw">
|
||||
<text>{{balance > 0 ? '立即提现' : '暂无提现'}}</text>
|
||||
</button>
|
||||
|
||||
<view class="record-link" bindtap="goToWithdrawHistory">
|
||||
<text>查看提现记录</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义提现金额弹框 -->
|
||||
<view class="withdraw-mask" wx:if="{{showWithdrawDialog}}">
|
||||
<view class="withdraw-dialog">
|
||||
<view class="dialog-title">申请提现</view>
|
||||
<view class="dialog-sub">可提现 ¥{{balance}},请输入本次提现金额</view>
|
||||
|
||||
<view class="amount-input-row">
|
||||
<text class="currency">¥</text>
|
||||
<input
|
||||
class="amount-input"
|
||||
type="digit"
|
||||
focus="true"
|
||||
value="{{withdrawAmountInput}}"
|
||||
placeholder="至少 1.00 元"
|
||||
bindinput="onWithdrawInput"
|
||||
/>
|
||||
</view>
|
||||
<view class="amount-hint">
|
||||
<text>本次最高可提 ¥{{balance}}</text>
|
||||
</view>
|
||||
<view class="fee-actual-row" wx:if="{{withdrawFeePct > 0}}">
|
||||
<text>手续费 ¥{{withdrawFeeYuan}}</text>
|
||||
<text class="actual">实际到账 ¥{{withdrawActualYuan}}</text>
|
||||
</view>
|
||||
<view class="error-text" wx:if="{{withdrawError}}">
|
||||
<text>{{withdrawError}}</text>
|
||||
</view>
|
||||
|
||||
<view class="dialog-actions">
|
||||
<view class="btn cancel" bindtap="closeWithdrawDialog">取消</view>
|
||||
<view class="btn confirm" bindtap="confirmWithdraw">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 四格统计 -->
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">{{bindingCount}}</text>
|
||||
<text class="stat-label">绑定中</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">{{paidCount}}</text>
|
||||
<text class="stat-label">已付款</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val highlight">{{expiringCount}}</text>
|
||||
<text class="stat-label">即将过期</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">{{totalInvite}}</text>
|
||||
<text class="stat-label">总邀请</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收益规则 -->
|
||||
<view class="section rule-section">
|
||||
<view class="section-header">
|
||||
<view class="header-icon red">
|
||||
<text class="emoji-sm">🛡️</text>
|
||||
</view>
|
||||
<text class="section-title">推广收益规则</text>
|
||||
</view>
|
||||
<view class="rule-list">
|
||||
<view class="rule-item">
|
||||
<text class="rule-bullet">✨</text>
|
||||
<text class="rule-text">用户绑定有效期为 <text class="highlight">{{bindingDays}}天</text>,期满自动解除</text>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<text class="rule-bullet">💰</text>
|
||||
<text class="rule-text">单笔提现:最低 <text class="highlight">¥{{withdrawMinYuan}}</text>,</text>
|
||||
<text class="rule-text" wx:if="{{withdrawMaxYuan}}">最高 <text class="highlight">¥{{withdrawMaxYuan}}</text></text>
|
||||
<text class="rule-text" wx:else>不设上限</text>
|
||||
</view>
|
||||
<view class="rule-item" wx:if="{{withdrawFeePct > 0}}">
|
||||
<text class="rule-bullet">📋</text>
|
||||
<text class="rule-text">提现手续费:<text class="highlight">{{withdrawFeePct}}%</text></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 绑定用户列表 -->
|
||||
<view class="section user-section">
|
||||
<view class="section-header border-b">
|
||||
<view class="header-left">
|
||||
<text class="emoji-sm">👥</text>
|
||||
<text class="section-title">绑定用户</text>
|
||||
<text class="count-label">({{totalInvite}})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab 切换 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item {{activeTab === 0 ? 'active' : ''}}" bindtap="switchTab" data-index="0">
|
||||
绑定中 ({{bindingCount}})
|
||||
</view>
|
||||
<view class="tab-item {{activeTab === 1 ? 'active' : ''}}" bindtap="switchTab" data-index="1">
|
||||
已付款 ({{paidCount}})
|
||||
</view>
|
||||
<view class="tab-item {{activeTab === 2 ? 'active' : ''}}" bindtap="switchTab" data-index="2">
|
||||
已过期 ({{expiringCount}})
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{userList.length === 0}}">
|
||||
<view class="empty-icon">
|
||||
<text class="emoji-lg">🔍</text>
|
||||
</view>
|
||||
<text class="empty-text">目前还没有绑定的用户哦</text>
|
||||
<text class="empty-sub">快去分享链接邀请好友吧</text>
|
||||
</view>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<view class="user-list" wx:else>
|
||||
<view class="user-item" wx:for="{{userList}}" wx:key="id">
|
||||
<!-- 头像 -->
|
||||
<image wx:if="{{item.avatar}}" class="user-avatar" src="{{item.avatar}}" mode="aspectFill"/>
|
||||
<view wx:else class="user-avatar user-avatar-placeholder">
|
||||
<text class="user-avatar-letter">{{item.nickname ? item.nickname[0] : '?'}}</text>
|
||||
</view>
|
||||
<!-- 昵称 + 绑定时间 -->
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{item.nickname || '微信用户'}}</text>
|
||||
<text class="user-time">绑定于 {{item.createdAtStr}}</text>
|
||||
</view>
|
||||
<!-- 状态 / 剩余天数 -->
|
||||
<view class="user-badge {{item.status === 'active' ? 'badge-active' : (item.status === 'expired' ? 'badge-expired' : 'badge-paid')}}">
|
||||
<text wx:if="{{item.status === 'active'}}">{{item.remainDays}}天到期</text>
|
||||
<text wx:elif="{{item.status === 'expired'}}">已过期</text>
|
||||
<text wx:else>已付款</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 / 没有更多 -->
|
||||
<view class="list-footer" wx:if="{{listLoading}}">
|
||||
<text class="list-footer-text">加载中...</text>
|
||||
</view>
|
||||
<view class="list-footer" wx:elif="{{listFinished && userList.length > 0}}">
|
||||
<text class="list-footer-text">— 已加载全部 —</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作菜单 -->
|
||||
<view class="menu-card">
|
||||
<view class="menu-item" bindtap="generatePoster">
|
||||
<view class="menu-icon-wrap rose">
|
||||
<text class="emoji-sm">🎨</text>
|
||||
</view>
|
||||
<view class="menu-info">
|
||||
<text class="menu-name">生成推广海报</text>
|
||||
<text class="menu-desc">一键生成您的专属精美海报</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" bindtap="shareToTimeline">
|
||||
<view class="menu-icon-wrap emerald">
|
||||
<text class="emoji-sm">💬</text>
|
||||
</view>
|
||||
<view class="menu-info">
|
||||
<text class="menu-name">分享到朋友圈</text>
|
||||
<text class="menu-desc">通过右上角菜单分享到朋友圈</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<button class="menu-item share-btn" open-type="share">
|
||||
<view class="menu-icon-wrap violet">
|
||||
<text class="emoji-sm">📤</text>
|
||||
</view>
|
||||
<view class="menu-info">
|
||||
<text class="menu-name">分享给好友</text>
|
||||
<text class="menu-desc">通过系统面板发送给微信好友</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<view class="footer-tip">
|
||||
<text>分享专属链接,好友通过链接点击后自动绑定</text>
|
||||
<text>购买任意测评即可获得 <text class="highlight">90%</text> 的现金返利</text>
|
||||
</view>
|
||||
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
<!--pages/promo/index.wxml-->
|
||||
<view class="container">
|
||||
<!-- 顶部统计卡片 -->
|
||||
<view class="hero-card">
|
||||
<view class="hero-header-row">
|
||||
<view class="hero-left">
|
||||
<view class="wallet-icon">
|
||||
<text class="emoji">🧧</text>
|
||||
</view>
|
||||
<view class="title-text">
|
||||
<text class="label">可提现金额</text>
|
||||
<view class="badge">
|
||||
<view class="dot"></view>
|
||||
<text class="badge-text">{{commissionRate}}% 高额返利</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="hero-right">
|
||||
<text class="amount">¥{{balance}}</text>
|
||||
<text class="amount-sub">累计: ¥{{totalEarned}} | 待审核: ¥{{pendingAmount}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="withdraw-btn {{balance > 0 ? '' : 'disabled'}}" bindtap="handleWithdraw">
|
||||
<text>{{balance > 0 ? '立即提现' : '暂无提现'}}</text>
|
||||
</button>
|
||||
|
||||
<view class="record-link" bindtap="goToWithdrawHistory">
|
||||
<text>查看提现记录</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义提现金额弹框 -->
|
||||
<view class="withdraw-mask" wx:if="{{showWithdrawDialog}}">
|
||||
<view class="withdraw-dialog">
|
||||
<view class="dialog-title">申请提现</view>
|
||||
<view class="dialog-sub">可提现 ¥{{balance}},请输入本次提现金额</view>
|
||||
|
||||
<view class="amount-input-row">
|
||||
<text class="currency">¥</text>
|
||||
<input
|
||||
class="amount-input"
|
||||
type="digit"
|
||||
focus="true"
|
||||
value="{{withdrawAmountInput}}"
|
||||
placeholder="至少 1.00 元"
|
||||
bindinput="onWithdrawInput"
|
||||
/>
|
||||
</view>
|
||||
<view class="amount-hint">
|
||||
<text>本次最高可提 ¥{{balance}}</text>
|
||||
</view>
|
||||
<view class="fee-actual-row" wx:if="{{withdrawFeePct > 0}}">
|
||||
<text>手续费 ¥{{withdrawFeeYuan}}</text>
|
||||
<text class="actual">实际到账 ¥{{withdrawActualYuan}}</text>
|
||||
</view>
|
||||
<view class="error-text" wx:if="{{withdrawError}}">
|
||||
<text>{{withdrawError}}</text>
|
||||
</view>
|
||||
|
||||
<view class="dialog-actions">
|
||||
<view class="btn cancel" bindtap="closeWithdrawDialog">取消</view>
|
||||
<view class="btn confirm" bindtap="confirmWithdraw">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 四格统计 -->
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">{{bindingCount}}</text>
|
||||
<text class="stat-label">绑定中</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">{{paidCount}}</text>
|
||||
<text class="stat-label">已付款</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val highlight">{{expiringCount}}</text>
|
||||
<text class="stat-label">即将过期</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">{{totalInvite}}</text>
|
||||
<text class="stat-label">总邀请</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收益规则 -->
|
||||
<view class="section rule-section">
|
||||
<view class="section-header">
|
||||
<view class="header-icon red">
|
||||
<text class="emoji-sm">🛡️</text>
|
||||
</view>
|
||||
<text class="section-title">推广收益规则</text>
|
||||
</view>
|
||||
<view class="rule-list">
|
||||
<view class="rule-item">
|
||||
<text class="rule-bullet">✨</text>
|
||||
<text class="rule-text">用户绑定有效期为 <text class="highlight">{{bindingDays}}天</text>,期满自动解除</text>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<text class="rule-bullet">💰</text>
|
||||
<text class="rule-text">单笔提现:最低 <text class="highlight">¥{{withdrawMinYuan}}</text>,</text>
|
||||
<text class="rule-text" wx:if="{{withdrawMaxYuan}}">最高 <text class="highlight">¥{{withdrawMaxYuan}}</text></text>
|
||||
<text class="rule-text" wx:else>不设上限</text>
|
||||
</view>
|
||||
<view class="rule-item" wx:if="{{withdrawFeePct > 0}}">
|
||||
<text class="rule-bullet">📋</text>
|
||||
<text class="rule-text">提现手续费:<text class="highlight">{{withdrawFeePct}}%</text></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 绑定用户列表 -->
|
||||
<view class="section user-section">
|
||||
<view class="section-header border-b">
|
||||
<view class="header-left">
|
||||
<text class="emoji-sm">👥</text>
|
||||
<text class="section-title">绑定用户</text>
|
||||
<text class="count-label">({{totalInvite}})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab 切换 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item {{activeTab === 0 ? 'active' : ''}}" bindtap="switchTab" data-index="0">
|
||||
绑定中 ({{bindingCount}})
|
||||
</view>
|
||||
<view class="tab-item {{activeTab === 1 ? 'active' : ''}}" bindtap="switchTab" data-index="1">
|
||||
已付款 ({{paidCount}})
|
||||
</view>
|
||||
<view class="tab-item {{activeTab === 2 ? 'active' : ''}}" bindtap="switchTab" data-index="2">
|
||||
已过期 ({{expiringCount}})
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{userList.length === 0}}">
|
||||
<view class="empty-icon">
|
||||
<text class="emoji-lg">🔍</text>
|
||||
</view>
|
||||
<text class="empty-text">目前还没有绑定的用户哦</text>
|
||||
<text class="empty-sub">快去分享链接邀请好友吧</text>
|
||||
</view>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<view class="user-list" wx:else>
|
||||
<view class="user-item" wx:for="{{userList}}" wx:key="id">
|
||||
<!-- 头像 -->
|
||||
<image wx:if="{{item.avatar}}" class="user-avatar" src="{{item.avatar}}" mode="aspectFill"/>
|
||||
<view wx:else class="user-avatar user-avatar-placeholder">
|
||||
<text class="user-avatar-letter">{{item.nickname ? item.nickname[0] : '?'}}</text>
|
||||
</view>
|
||||
<!-- 昵称 + 绑定时间 -->
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{item.nickname || '微信用户'}}</text>
|
||||
<text class="user-time">绑定于 {{item.createdAtStr}}</text>
|
||||
</view>
|
||||
<!-- 状态 / 剩余天数 -->
|
||||
<view class="user-badge {{item.status === 'active' ? 'badge-active' : (item.status === 'expired' ? 'badge-expired' : 'badge-paid')}}">
|
||||
<text wx:if="{{item.status === 'active'}}">{{item.remainDays}}天到期</text>
|
||||
<text wx:elif="{{item.status === 'expired'}}">已过期</text>
|
||||
<text wx:else>已付款</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 / 没有更多 -->
|
||||
<view class="list-footer" wx:if="{{listLoading}}">
|
||||
<text class="list-footer-text">加载中...</text>
|
||||
</view>
|
||||
<view class="list-footer" wx:elif="{{listFinished && userList.length > 0}}">
|
||||
<text class="list-footer-text">— 已加载全部 —</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作菜单 -->
|
||||
<view class="menu-card">
|
||||
<view class="menu-item" bindtap="generatePoster">
|
||||
<view class="menu-icon-wrap rose">
|
||||
<text class="emoji-sm">🎨</text>
|
||||
</view>
|
||||
<view class="menu-info">
|
||||
<text class="menu-name">生成推广海报</text>
|
||||
<text class="menu-desc">一键生成您的专属精美海报</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" bindtap="shareToTimeline">
|
||||
<view class="menu-icon-wrap emerald">
|
||||
<text class="emoji-sm">💬</text>
|
||||
</view>
|
||||
<view class="menu-info">
|
||||
<text class="menu-name">分享到朋友圈</text>
|
||||
<text class="menu-desc">通过右上角菜单分享到朋友圈</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<button class="menu-item share-btn" open-type="share">
|
||||
<view class="menu-icon-wrap violet">
|
||||
<text class="emoji-sm">📤</text>
|
||||
</view>
|
||||
<view class="menu-info">
|
||||
<text class="menu-name">分享给好友</text>
|
||||
<text class="menu-desc">通过系统面板发送给微信好友</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<view class="footer-tip">
|
||||
<text>分享专属链接,好友通过链接点击后自动绑定</text>
|
||||
<text>购买任意测评即可获得 <text class="highlight">90%</text> 的现金返利</text>
|
||||
</view>
|
||||
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "生成推广海报",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
{
|
||||
"navigationBarTitleText": "生成推广海报",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
<view class="container">
|
||||
<view class="poster-preview">
|
||||
<image wx:if="{{posterUrl}}" class="poster-img" src="{{posterUrl}}" mode="widthFix" />
|
||||
<view wx:if="{{loading}}" class="loading-mask">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">{{loadingText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-bar">
|
||||
<button class="save-btn" bindtap="savePoster" disabled="{{loading || !posterUrl}}">
|
||||
<text>保存海报分享</text>
|
||||
</button>
|
||||
<view class="tip-text">保存到相册后可分享至朋友圈</view>
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="container">
|
||||
<view class="poster-preview">
|
||||
<image wx:if="{{posterUrl}}" class="poster-img" src="{{posterUrl}}" mode="widthFix" />
|
||||
<view wx:if="{{loading}}" class="loading-mask">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">{{loadingText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-bar">
|
||||
<button class="save-btn" bindtap="savePoster" disabled="{{loading || !posterUrl}}">
|
||||
<text>保存海报分享</text>
|
||||
</button>
|
||||
<view class="tip-text">保存到相册后可分享至朋友圈</view>
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,83 +1,83 @@
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8fafc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
.poster-preview {
|
||||
position: relative;
|
||||
width: 600rpx;
|
||||
min-height: 800rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 20rpx 50rpx rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.poster-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-mask {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx; height: 60rpx;
|
||||
border: 4rpx solid #f3f4f6;
|
||||
border-top: 4rpx solid #f43f5e;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text { font-size: 24rpx; color: #64748b; }
|
||||
|
||||
.action-bar {
|
||||
position: fixed;
|
||||
bottom: 0; left: 0; right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: 40rpx 40rpx 0 0;
|
||||
box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 100% !important;
|
||||
height: 100rpx;
|
||||
background: #f43f5e;
|
||||
color: #fff;
|
||||
border-radius: 24rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 10rpx 20rpx rgba(244, 63, 94, 0.2);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.save-btn[disabled] {
|
||||
background: #cbd5e1;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tip-text { font-size: 24rpx; color: #94a3b8; margin-bottom: 20rpx; }
|
||||
.safe-bottom { height: env(safe-area-inset-bottom); }
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8fafc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
.poster-preview {
|
||||
position: relative;
|
||||
width: 600rpx;
|
||||
min-height: 800rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 20rpx 50rpx rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.poster-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-mask {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx; height: 60rpx;
|
||||
border: 4rpx solid #f3f4f6;
|
||||
border-top: 4rpx solid #f43f5e;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text { font-size: 24rpx; color: #64748b; }
|
||||
|
||||
.action-bar {
|
||||
position: fixed;
|
||||
bottom: 0; left: 0; right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: 40rpx 40rpx 0 0;
|
||||
box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 100% !important;
|
||||
height: 100rpx;
|
||||
background: #f43f5e;
|
||||
color: #fff;
|
||||
border-radius: 24rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 10rpx 20rpx rgba(244, 63, 94, 0.2);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.save-btn[disabled] {
|
||||
background: #cbd5e1;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tip-text { font-size: 24rpx; color: #94a3b8; margin-bottom: 20rpx; }
|
||||
.safe-bottom { height: env(safe-area-inset-bottom); }
|
||||
|
||||
@@ -1,236 +1,236 @@
|
||||
// pages/promo/withdrawals.js - 提现记录列表页
|
||||
const { request } = require('../../utils/request')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
finished: false,
|
||||
// 确认收款按钮 loading 状态(按单条)
|
||||
confirmingId: null
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
// 加载提现记录列表(reset 为 true 时重置分页)
|
||||
loadData(reset = false) {
|
||||
if (this.data.loading) return
|
||||
if (!reset && this.data.finished) return
|
||||
|
||||
const page = reset ? 1 : this.data.page
|
||||
this.setData({ loading: true })
|
||||
|
||||
request({
|
||||
url: `/api/distribution/withdrawals?page=${page}&pageSize=${this.data.pageSize}`,
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
const payload = res && res.data
|
||||
if (payload && payload.code === 200 && payload.data) {
|
||||
const { list = [], total = 0 } = payload.data
|
||||
const mapped = list.map(item => {
|
||||
const statusCode = this._normalizeStatusCode(item.status)
|
||||
const amountYuan = item.amountYuan != null
|
||||
? item.amountYuan
|
||||
: (item.amountFen != null ? (item.amountFen / 100).toFixed(2) : '')
|
||||
|
||||
const balanceAfterYuan = item.balanceAfterYuan != null
|
||||
? item.balanceAfterYuan
|
||||
: (item.balanceAfterFen != null ? (item.balanceAfterFen / 100).toFixed(2) : '')
|
||||
|
||||
const feeYuan = item.feeYuan != null
|
||||
? item.feeYuan
|
||||
: (item.feeFen != null ? (item.feeFen / 100).toFixed(2) : '0.00')
|
||||
|
||||
return {
|
||||
...item,
|
||||
statusCode,
|
||||
amountYuan,
|
||||
balanceAfterYuan,
|
||||
feeYuan,
|
||||
// 申请时间:用 createdAt
|
||||
applyAtStr: this._fmtTime(item.createdAt),
|
||||
// 处理时间:用 transferAt(如有)
|
||||
handleAtStr: this._fmtTime(item.transferAt),
|
||||
statusTagText: this._statusTagText(statusCode),
|
||||
statusTagClass: this._statusTagClass(statusCode)
|
||||
}
|
||||
})
|
||||
const newList = reset ? mapped : [...this.data.list, ...mapped]
|
||||
this.setData({
|
||||
list: newList,
|
||||
total,
|
||||
page: page + 1,
|
||||
finished: newList.length >= total
|
||||
})
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 时间戳格式化
|
||||
_fmtTime(ts) {
|
||||
if (!ts) return ''
|
||||
const d = new Date(ts * 1000)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
return `${y}-${m}-${day} ${hh}:${mm}`
|
||||
},
|
||||
|
||||
// 将后端 status(数字 / 中文 / 英文)统一转成数字枚举:
|
||||
// 0=审核中,1=已驳回,2=待收款,3=已收款,4=已过期
|
||||
_normalizeStatusCode(status) {
|
||||
if (typeof status === 'number') return status
|
||||
if (typeof status === 'string') {
|
||||
const s = status.trim()
|
||||
if (/^\d+$/.test(s)) return parseInt(s, 10)
|
||||
switch (s) {
|
||||
case 'pending':
|
||||
case '审核中':
|
||||
return 0
|
||||
case 'rejected':
|
||||
case '已驳回':
|
||||
return 1
|
||||
case '待收款':
|
||||
return 2
|
||||
case 'transferred':
|
||||
case '已收款':
|
||||
return 3
|
||||
case '已过期':
|
||||
return 4
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
},
|
||||
|
||||
// 状态中文文案
|
||||
_statusTagText(code) {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return '待审核'
|
||||
case 1:
|
||||
return '已驳回'
|
||||
case 2:
|
||||
return '待收款'
|
||||
case 3:
|
||||
return '已收款'
|
||||
case 4:
|
||||
return '已过期'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
// 顶部右侧小标签样式
|
||||
_statusTagClass(code) {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return 'tag-pending'
|
||||
case 1:
|
||||
return 'tag-rejected'
|
||||
case 2:
|
||||
return 'tag-wait'
|
||||
case 3:
|
||||
return 'tag-transferred'
|
||||
case 4:
|
||||
return 'tag-expired'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
// 用户点击单条「确认收款」
|
||||
handleConfirmReceipt(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
if (!id || this.data.confirmingId === id) return
|
||||
|
||||
const item = (this.data.list || []).find(x => String(x.id) === String(id))
|
||||
if (!item) {
|
||||
wx.showToast({ title: '记录不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 兼容驼峰 / 下划线字段
|
||||
const packageInfo = item.packageInfo || item.package_info
|
||||
if (!packageInfo) {
|
||||
wx.showToast({ title: '该提现单无法确认收款,请联系客服', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (!wx.canIUse || !wx.canIUse('requestMerchantTransfer')) {
|
||||
wx.showModal({
|
||||
content: '当前微信版本过低,无法使用确认收款功能,请升级微信后重试',
|
||||
showCancel: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ confirmingId: id })
|
||||
|
||||
wx.showLoading({ title: '正在调起确认收款...' })
|
||||
|
||||
// 获取 AppID:优先记录里的,其次运行时获取
|
||||
let appId = item.appId || item.app_id
|
||||
try {
|
||||
if (!appId && wx.getAccountInfoSync) {
|
||||
const info = wx.getAccountInfoSync()
|
||||
appId = info && info.miniProgram && info.miniProgram.appId
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
// 商户号:从记录中取(后端已写入 mch_id)
|
||||
const mchId = item.mchId || item.mch_id
|
||||
|
||||
wx.requestMerchantTransfer({
|
||||
mchId,
|
||||
appId,
|
||||
// 文档里字段名是 package,对应后台返回的 package_info
|
||||
package: packageInfo,
|
||||
success: (res) => {
|
||||
console.log('requestMerchantTransfer success', res)
|
||||
wx.showToast({ title: '已调起收款确认', icon: 'none', duration: 2000 })
|
||||
// 主动查询转账状态并更新订单,实现及时刷新(参考商户单号查询:https://pay.weixin.qq.com/doc/v3/merchant/4012716437)
|
||||
request({
|
||||
url: '/api/distribution/withdrawals/query-transfer',
|
||||
method: 'POST',
|
||||
data: { id: item.id },
|
||||
success: (r) => {
|
||||
const p = r && r.data
|
||||
if (p && p.code === 200 && p.data && p.data.status === 3) {
|
||||
wx.showToast({ title: '已收款', icon: 'success' })
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.loadData(true)
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('requestMerchantTransfer fail', err)
|
||||
wx.showToast({ title: '调起失败,请稍后重试', icon: 'none' })
|
||||
},
|
||||
complete: () => {
|
||||
wx.hideLoading()
|
||||
this.setData({ confirmingId: null })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
this.loadData(false)
|
||||
}
|
||||
})
|
||||
|
||||
// pages/promo/withdrawals.js - 提现记录列表页
|
||||
const { request } = require('../../utils/request')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
finished: false,
|
||||
// 确认收款按钮 loading 状态(按单条)
|
||||
confirmingId: null
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
// 加载提现记录列表(reset 为 true 时重置分页)
|
||||
loadData(reset = false) {
|
||||
if (this.data.loading) return
|
||||
if (!reset && this.data.finished) return
|
||||
|
||||
const page = reset ? 1 : this.data.page
|
||||
this.setData({ loading: true })
|
||||
|
||||
request({
|
||||
url: `/api/distribution/withdrawals?page=${page}&pageSize=${this.data.pageSize}`,
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
const payload = res && res.data
|
||||
if (payload && payload.code === 200 && payload.data) {
|
||||
const { list = [], total = 0 } = payload.data
|
||||
const mapped = list.map(item => {
|
||||
const statusCode = this._normalizeStatusCode(item.status)
|
||||
const amountYuan = item.amountYuan != null
|
||||
? item.amountYuan
|
||||
: (item.amountFen != null ? (item.amountFen / 100).toFixed(2) : '')
|
||||
|
||||
const balanceAfterYuan = item.balanceAfterYuan != null
|
||||
? item.balanceAfterYuan
|
||||
: (item.balanceAfterFen != null ? (item.balanceAfterFen / 100).toFixed(2) : '')
|
||||
|
||||
const feeYuan = item.feeYuan != null
|
||||
? item.feeYuan
|
||||
: (item.feeFen != null ? (item.feeFen / 100).toFixed(2) : '0.00')
|
||||
|
||||
return {
|
||||
...item,
|
||||
statusCode,
|
||||
amountYuan,
|
||||
balanceAfterYuan,
|
||||
feeYuan,
|
||||
// 申请时间:用 createdAt
|
||||
applyAtStr: this._fmtTime(item.createdAt),
|
||||
// 处理时间:用 transferAt(如有)
|
||||
handleAtStr: this._fmtTime(item.transferAt),
|
||||
statusTagText: this._statusTagText(statusCode),
|
||||
statusTagClass: this._statusTagClass(statusCode)
|
||||
}
|
||||
})
|
||||
const newList = reset ? mapped : [...this.data.list, ...mapped]
|
||||
this.setData({
|
||||
list: newList,
|
||||
total,
|
||||
page: page + 1,
|
||||
finished: newList.length >= total
|
||||
})
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 时间戳格式化
|
||||
_fmtTime(ts) {
|
||||
if (!ts) return ''
|
||||
const d = new Date(ts * 1000)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
return `${y}-${m}-${day} ${hh}:${mm}`
|
||||
},
|
||||
|
||||
// 将后端 status(数字 / 中文 / 英文)统一转成数字枚举:
|
||||
// 0=审核中,1=已驳回,2=待收款,3=已收款,4=已过期
|
||||
_normalizeStatusCode(status) {
|
||||
if (typeof status === 'number') return status
|
||||
if (typeof status === 'string') {
|
||||
const s = status.trim()
|
||||
if (/^\d+$/.test(s)) return parseInt(s, 10)
|
||||
switch (s) {
|
||||
case 'pending':
|
||||
case '审核中':
|
||||
return 0
|
||||
case 'rejected':
|
||||
case '已驳回':
|
||||
return 1
|
||||
case '待收款':
|
||||
return 2
|
||||
case 'transferred':
|
||||
case '已收款':
|
||||
return 3
|
||||
case '已过期':
|
||||
return 4
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
},
|
||||
|
||||
// 状态中文文案
|
||||
_statusTagText(code) {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return '待审核'
|
||||
case 1:
|
||||
return '已驳回'
|
||||
case 2:
|
||||
return '待收款'
|
||||
case 3:
|
||||
return '已收款'
|
||||
case 4:
|
||||
return '已过期'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
// 顶部右侧小标签样式
|
||||
_statusTagClass(code) {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return 'tag-pending'
|
||||
case 1:
|
||||
return 'tag-rejected'
|
||||
case 2:
|
||||
return 'tag-wait'
|
||||
case 3:
|
||||
return 'tag-transferred'
|
||||
case 4:
|
||||
return 'tag-expired'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
// 用户点击单条「确认收款」
|
||||
handleConfirmReceipt(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
if (!id || this.data.confirmingId === id) return
|
||||
|
||||
const item = (this.data.list || []).find(x => String(x.id) === String(id))
|
||||
if (!item) {
|
||||
wx.showToast({ title: '记录不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 兼容驼峰 / 下划线字段
|
||||
const packageInfo = item.packageInfo || item.package_info
|
||||
if (!packageInfo) {
|
||||
wx.showToast({ title: '该提现单无法确认收款,请联系客服', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (!wx.canIUse || !wx.canIUse('requestMerchantTransfer')) {
|
||||
wx.showModal({
|
||||
content: '当前微信版本过低,无法使用确认收款功能,请升级微信后重试',
|
||||
showCancel: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ confirmingId: id })
|
||||
|
||||
wx.showLoading({ title: '正在调起确认收款...' })
|
||||
|
||||
// 获取 AppID:优先记录里的,其次运行时获取
|
||||
let appId = item.appId || item.app_id
|
||||
try {
|
||||
if (!appId && wx.getAccountInfoSync) {
|
||||
const info = wx.getAccountInfoSync()
|
||||
appId = info && info.miniProgram && info.miniProgram.appId
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
// 商户号:从记录中取(后端已写入 mch_id)
|
||||
const mchId = item.mchId || item.mch_id
|
||||
|
||||
wx.requestMerchantTransfer({
|
||||
mchId,
|
||||
appId,
|
||||
// 文档里字段名是 package,对应后台返回的 package_info
|
||||
package: packageInfo,
|
||||
success: (res) => {
|
||||
console.log('requestMerchantTransfer success', res)
|
||||
wx.showToast({ title: '已调起收款确认', icon: 'none', duration: 2000 })
|
||||
// 主动查询转账状态并更新订单,实现及时刷新(参考商户单号查询:https://pay.weixin.qq.com/doc/v3/merchant/4012716437)
|
||||
request({
|
||||
url: '/api/distribution/withdrawals/query-transfer',
|
||||
method: 'POST',
|
||||
data: { id: item.id },
|
||||
success: (r) => {
|
||||
const p = r && r.data
|
||||
if (p && p.code === 200 && p.data && p.data.status === 3) {
|
||||
wx.showToast({ title: '已收款', icon: 'success' })
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.loadData(true)
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('requestMerchantTransfer fail', err)
|
||||
wx.showToast({ title: '调起失败,请稍后重试', icon: 'none' })
|
||||
},
|
||||
complete: () => {
|
||||
wx.hideLoading()
|
||||
this.setData({ confirmingId: null })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
this.loadData(false)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "提现记录",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
{
|
||||
"navigationBarTitleText": "提现记录",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
<!-- pages/promo/withdrawals.wxml - 提现记录列表 -->
|
||||
<view class="page">
|
||||
<!-- 顶部说明 -->
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty" wx:if="{{!loading && list.length === 0}}">
|
||||
<view class="empty-icon">
|
||||
<text>📭</text>
|
||||
</view>
|
||||
<text class="empty-text">暂无提现记录</text>
|
||||
<text class="empty-sub">成功发起提现后,将在这里展示进度</text>
|
||||
</view>
|
||||
|
||||
<!-- 列表(参考碎片时间提现列表样式结构) -->
|
||||
<view class="list" wx:else>
|
||||
<view class="item" wx:for="{{list}}" wx:key="id">
|
||||
<!-- 顶部:单号 + 状态标签 -->
|
||||
<view class="item-header">
|
||||
<text class="order-no">单号:{{item.out_bill_no}}</text>
|
||||
<view class="status-tag {{item.statusTagClass}}">
|
||||
<text>{{item.statusTagText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 中部:金额 + 手续费 + 提现后余额 -->
|
||||
<view class="item-top">
|
||||
<view class="amount-block">
|
||||
<text class="amount">¥{{item.amountYuan}}</text>
|
||||
</view>
|
||||
<view class="fee-block" wx:if="{{item.feeYuan && item.feeYuan !== '0.00'}}">
|
||||
<text class="fee-label">手续费</text>
|
||||
<text class="fee-value">¥{{item.feeYuan}}</text>
|
||||
</view>
|
||||
<view class="balance-block" wx:if="{{item.balanceAfterYuan}}">
|
||||
<text class="balance-label">提现后余额</text>
|
||||
<text class="balance-value">¥{{item.balanceAfterYuan}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<view class="time-block">
|
||||
<text class="time-row">申请时间:{{item.applyAtStr}}</text>
|
||||
<text class="time-row" wx:if="{{item.handleAtStr}}">处理时间:{{item.handleAtStr}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 拒绝原因 -->
|
||||
<view class="reject-box" wx:if="{{item.status === 'rejected' && item.auditNote}}">
|
||||
<text class="reject-title">拒绝原因:</text>
|
||||
<text class="reject-text">{{item.auditNote}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 每条记录自己的确认收款按钮:只有「待收款」(statusCode=2) 需要用户确认 -->
|
||||
<view class="confirm-wrapper" wx:if="{{item.statusCode === 2}}">
|
||||
<button
|
||||
class="confirm-btn"
|
||||
bindtap="handleConfirmReceipt"
|
||||
data-id="{{item.id}}"
|
||||
loading="{{confirmingId === item.id}}"
|
||||
disabled="{{confirmingId === item.id}}"
|
||||
>
|
||||
确认收款
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer" wx:if="{{loading}}">
|
||||
<text class="footer-text">加载中...</text>
|
||||
</view>
|
||||
<view class="footer" wx:elif="{{finished}}">
|
||||
<text class="footer-text">— 已加载全部 —</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
|
||||
<!-- pages/promo/withdrawals.wxml - 提现记录列表 -->
|
||||
<view class="page">
|
||||
<!-- 顶部说明 -->
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty" wx:if="{{!loading && list.length === 0}}">
|
||||
<view class="empty-icon">
|
||||
<text>📭</text>
|
||||
</view>
|
||||
<text class="empty-text">暂无提现记录</text>
|
||||
<text class="empty-sub">成功发起提现后,将在这里展示进度</text>
|
||||
</view>
|
||||
|
||||
<!-- 列表(参考碎片时间提现列表样式结构) -->
|
||||
<view class="list" wx:else>
|
||||
<view class="item" wx:for="{{list}}" wx:key="id">
|
||||
<!-- 顶部:单号 + 状态标签 -->
|
||||
<view class="item-header">
|
||||
<text class="order-no">单号:{{item.out_bill_no}}</text>
|
||||
<view class="status-tag {{item.statusTagClass}}">
|
||||
<text>{{item.statusTagText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 中部:金额 + 手续费 + 提现后余额 -->
|
||||
<view class="item-top">
|
||||
<view class="amount-block">
|
||||
<text class="amount">¥{{item.amountYuan}}</text>
|
||||
</view>
|
||||
<view class="fee-block" wx:if="{{item.feeYuan && item.feeYuan !== '0.00'}}">
|
||||
<text class="fee-label">手续费</text>
|
||||
<text class="fee-value">¥{{item.feeYuan}}</text>
|
||||
</view>
|
||||
<view class="balance-block" wx:if="{{item.balanceAfterYuan}}">
|
||||
<text class="balance-label">提现后余额</text>
|
||||
<text class="balance-value">¥{{item.balanceAfterYuan}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<view class="time-block">
|
||||
<text class="time-row">申请时间:{{item.applyAtStr}}</text>
|
||||
<text class="time-row" wx:if="{{item.handleAtStr}}">处理时间:{{item.handleAtStr}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 拒绝原因 -->
|
||||
<view class="reject-box" wx:if="{{item.status === 'rejected' && item.auditNote}}">
|
||||
<text class="reject-title">拒绝原因:</text>
|
||||
<text class="reject-text">{{item.auditNote}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 每条记录自己的确认收款按钮:只有「待收款」(statusCode=2) 需要用户确认 -->
|
||||
<view class="confirm-wrapper" wx:if="{{item.statusCode === 2}}">
|
||||
<button
|
||||
class="confirm-btn"
|
||||
bindtap="handleConfirmReceipt"
|
||||
data-id="{{item.id}}"
|
||||
loading="{{confirmingId === item.id}}"
|
||||
disabled="{{confirmingId === item.id}}"
|
||||
>
|
||||
确认收款
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer" wx:if="{{loading}}">
|
||||
<text class="footer-text">加载中...</text>
|
||||
</view>
|
||||
<view class="footer" wx:elif="{{finished}}">
|
||||
<text class="footer-text">— 已加载全部 —</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -1,244 +1,244 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f9fafb;
|
||||
padding: 24rpx 32rpx 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
margin-top: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin-top: 80rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
background: #f3f4f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 56rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 4rpx;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.empty-sub {
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.tag-pending {
|
||||
background: #e5e7eb;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.tag-approved {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tag-wait {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tag-rejected {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.tag-transferred {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.tag-expired {
|
||||
background: #f3f4f6;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.item-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.amount-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.fee-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.fee-label {
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.fee-value {
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.balance-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
margin-top: 4rpx;
|
||||
font-size: 26rpx;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.time-block {
|
||||
margin-top: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.time-row {
|
||||
font-size: 22rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.reject-box {
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.reject-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.reject-text {
|
||||
font-size: 24rpx;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 16rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.confirm-wrapper {
|
||||
margin-top: 20rpx;
|
||||
padding: 0 8rpx 4rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #FF6B8A 0%, #FF8E53 100%);
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 10rpx 18rpx rgba(255, 107, 138, 0.35);
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.confirm-btn[disabled] {
|
||||
opacity: 0.7;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.safe-bottom {
|
||||
height: calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f9fafb;
|
||||
padding: 24rpx 32rpx 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
margin-top: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin-top: 80rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
background: #f3f4f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 56rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 4rpx;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.empty-sub {
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.tag-pending {
|
||||
background: #e5e7eb;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.tag-approved {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tag-wait {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tag-rejected {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.tag-transferred {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.tag-expired {
|
||||
background: #f3f4f6;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.item-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.amount-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.fee-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.fee-label {
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.fee-value {
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.balance-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
margin-top: 4rpx;
|
||||
font-size: 26rpx;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.time-block {
|
||||
margin-top: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.time-row {
|
||||
font-size: 22rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.reject-box {
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.reject-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.reject-text {
|
||||
font-size: 24rpx;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 16rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.confirm-wrapper {
|
||||
margin-top: 20rpx;
|
||||
padding: 0 8rpx 4rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #FF6B8A 0%, #FF8E53 100%);
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 10rpx 18rpx rgba(255, 107, 138, 0.35);
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.confirm-btn[disabled] {
|
||||
opacity: 0.7;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.safe-bottom {
|
||||
height: calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,245 +1,247 @@
|
||||
// pages/purchase/index.js - 开通会员(深度服务价格:个人/企业区分,类目由后端配置可新增)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
const { hasPhone, bindPhoneByCode } = require('../../utils/phoneAuth.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
activeTab: 'personal',
|
||||
personalCategories: [],
|
||||
enterpriseCategories: [],
|
||||
loading: true,
|
||||
purchasing: false,
|
||||
hasPhone: false,
|
||||
successModal: {
|
||||
visible: false,
|
||||
title: '',
|
||||
content: '',
|
||||
wechat: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const tab = (options && options.tab === 'enterprise') ? 'enterprise' : 'personal'
|
||||
this.setData({ activeTab: tab })
|
||||
wx.setNavigationBarTitle({ title: tab === 'enterprise' ? '开通企业版' : '开通个人版' })
|
||||
this.loadDeepPricing()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.setData({ hasPhone: hasPhone() })
|
||||
},
|
||||
|
||||
loadDeepPricing() {
|
||||
const apiBase = app.globalData.apiBase || ''
|
||||
if (!apiBase) {
|
||||
this.setData({ loading: false })
|
||||
return
|
||||
}
|
||||
this.setData({ loading: true })
|
||||
Promise.all([
|
||||
this.requestDeepPricing('personal'),
|
||||
this.requestDeepPricing('enterprise')
|
||||
]).then(([personal, enterprise]) => {
|
||||
this.setData({
|
||||
personalCategories: personal || [],
|
||||
enterpriseCategories: enterprise || [],
|
||||
loading: false
|
||||
})
|
||||
}).catch(() => {
|
||||
this.setData({ loading: false })
|
||||
})
|
||||
},
|
||||
|
||||
requestDeepPricing(scope) {
|
||||
return new Promise((resolve) => {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase.replace(/\/$/, '')}/api/config/deep-pricing`,
|
||||
method: 'GET',
|
||||
data: { scope },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200 && Array.isArray(res.data.data && res.data.data.categories)) {
|
||||
resolve(res.data.data.categories)
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
},
|
||||
fail: () => resolve([])
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
this.setData({ activeTab: tab })
|
||||
},
|
||||
|
||||
// 无需再次授权时,直接点击按钮执行购买/咨询
|
||||
handlePurchaseTap(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
const index = e.currentTarget.dataset.index
|
||||
this.handlePurchase(tab, index)
|
||||
},
|
||||
|
||||
// 实际执行购买/咨询逻辑(不强制完善资料)
|
||||
handlePurchase(tab, index) {
|
||||
if (index === undefined || index === null) return
|
||||
const list = tab === 'enterprise' ? this.data.enterpriseCategories : this.data.personalCategories
|
||||
const category = list[index]
|
||||
if (!category) return
|
||||
|
||||
if (category.actionType === 'buy' && category.productKey) {
|
||||
this.purchasePersonal(category)
|
||||
} else {
|
||||
this.applyConsult(category)
|
||||
}
|
||||
},
|
||||
|
||||
// 购买/企业咨询按钮:就地触发微信系统手机号授权,然后执行 handlePurchase
|
||||
onGetPhoneNumberForPurchase(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
const index = e.currentTarget.dataset.index
|
||||
const { code, errMsg } = e.detail || {}
|
||||
|
||||
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
|
||||
if (!hasPhone()) {
|
||||
wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// 用户拒绝但之前已授权过,本地已有手机号,则直接继续
|
||||
this.handlePurchase(tab, index)
|
||||
return
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
if (hasPhone()) {
|
||||
this.handlePurchase(tab, index)
|
||||
} else {
|
||||
wx.showToast({ title: '获取手机号失败', icon: 'none' })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
bindPhoneByCode(code)
|
||||
.then(() => {
|
||||
this.setData({ hasPhone: true })
|
||||
this.handlePurchase(tab, index)
|
||||
})
|
||||
.catch(() => {
|
||||
// 失败时只提示,不阻塞后续再次点击
|
||||
})
|
||||
},
|
||||
|
||||
purchasePersonal(category) {
|
||||
if (this.data.purchasing) return
|
||||
this.setData({ purchasing: true })
|
||||
wx.showLoading({ title: '处理中...', mask: true })
|
||||
const deepProductId = category.id || category.productKey || ''
|
||||
const title = category.title || '个人深度服务(1v1深度解读)'
|
||||
payment.purchasePersonalDeepService({
|
||||
deepProductId,
|
||||
description: title,
|
||||
success: () => {
|
||||
wx.hideLoading()
|
||||
this.setData({ purchasing: false })
|
||||
this._reportCrmLead(category, 'buy')
|
||||
const successMsg = (category.successMessage || '购买成功!我们的顾问会尽快与您联系,为您提供专属深度解读服务。').trim()
|
||||
const wechat = (category.serviceWechat || '').trim()
|
||||
this._showSuccessModal('购买成功', successMsg, wechat)
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
this.setData({ purchasing: false })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
applyConsult(category) {
|
||||
// serviceWechat 展示给用户,consultWechat 是存客宝 API key
|
||||
const wechat = (category.serviceWechat || '').trim()
|
||||
const apiKey = (category.consultWechat || '').trim()
|
||||
const successMsg = (category.successMessage || '感谢您的申请,我们的顾问会尽快与您联系!').trim()
|
||||
wx.showLoading({ title: '提交中...', mask: true })
|
||||
if (apiKey) {
|
||||
this._reportCrmLead(category, 'consult')
|
||||
}
|
||||
setTimeout(() => {
|
||||
wx.hideLoading()
|
||||
this._showSuccessModal('申请成功', successMsg, wechat)
|
||||
}, 600)
|
||||
},
|
||||
|
||||
_showSuccessModal(title, content, wechat) {
|
||||
this.setData({
|
||||
successModal: {
|
||||
visible: true,
|
||||
title: title || '成功',
|
||||
content: content || '',
|
||||
wechat: wechat || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
closeSuccessModal() {
|
||||
this.setData({ 'successModal.visible': false })
|
||||
},
|
||||
|
||||
copyWechat() {
|
||||
const wechat = this.data.successModal.wechat
|
||||
if (!wechat) return
|
||||
wx.setClipboardData({
|
||||
data: wechat,
|
||||
success: () => wx.showToast({ title: '已复制微信号', icon: 'success' })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 向后端上报存客宝线索,后端负责签名和调用存客宝 API
|
||||
* @param {Object} category 深度服务类目对象(需含 consultWechat / title)
|
||||
* @param {string} actionType 'buy'(付款完成)| 'consult'(申请咨询)
|
||||
*/
|
||||
_reportCrmLead(category, actionType) {
|
||||
const apiKey = category.consultWechat || ''
|
||||
if (!apiKey) return
|
||||
const apiBase = app.globalData.apiBase || ''
|
||||
if (!apiBase) return
|
||||
|
||||
const isEnterprise = this.data.activeTab === 'enterprise'
|
||||
const source = (isEnterprise ? '企业深度服务' : '个人深度服务') + (category.title ? `-${category.title}` : '')
|
||||
const remark = actionType === 'buy' ? '完成付款' : '申请咨询'
|
||||
|
||||
wx.request({
|
||||
url: `${apiBase.replace(/\/$/, '')}/api/crm/report`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
Authorization: `Bearer ${wx.getStorageSync('token') || ''}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
apiKey,
|
||||
source,
|
||||
remark,
|
||||
siteTags: category.title || '',
|
||||
},
|
||||
success(res) {
|
||||
console.log('[CRM] 线索上报结果', res.data)
|
||||
},
|
||||
fail(err) {
|
||||
console.warn('[CRM] 线索上报请求失败', err)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const { getSharePath } = require('../../utils/share')
|
||||
return { title: '神仙团队AI性格测试 - 发现你的内在潜能', path: getSharePath('/pages/purchase/index') }
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: '神仙团队AI性格测试 - 发现你的内在潜能',
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
// pages/purchase/index.js - 开通会员(深度服务价格:个人/企业区分,类目由后端配置可新增)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
activeTab: 'personal',
|
||||
personalCategories: [],
|
||||
enterpriseCategories: [],
|
||||
loading: true,
|
||||
purchasing: false,
|
||||
hasPhone: false,
|
||||
successModal: {
|
||||
visible: false,
|
||||
title: '',
|
||||
content: '',
|
||||
wechat: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const tab = (options && options.tab === 'enterprise') ? 'enterprise' : 'personal'
|
||||
this.setData({ activeTab: tab })
|
||||
wx.setNavigationBarTitle({ title: tab === 'enterprise' ? '开通企业版' : '开通个人版' })
|
||||
this.loadDeepPricing()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (!ensureProfileCompleteAndRedirect()) return
|
||||
this.setData({ hasPhone: hasPhone() })
|
||||
},
|
||||
|
||||
loadDeepPricing() {
|
||||
const apiBase = app.globalData.apiBase || ''
|
||||
if (!apiBase) {
|
||||
this.setData({ loading: false })
|
||||
return
|
||||
}
|
||||
this.setData({ loading: true })
|
||||
Promise.all([
|
||||
this.requestDeepPricing('personal'),
|
||||
this.requestDeepPricing('enterprise')
|
||||
]).then(([personal, enterprise]) => {
|
||||
this.setData({
|
||||
personalCategories: personal || [],
|
||||
enterpriseCategories: enterprise || [],
|
||||
loading: false
|
||||
})
|
||||
}).catch(() => {
|
||||
this.setData({ loading: false })
|
||||
})
|
||||
},
|
||||
|
||||
requestDeepPricing(scope) {
|
||||
return new Promise((resolve) => {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase.replace(/\/$/, '')}/api/config/deep-pricing`,
|
||||
method: 'GET',
|
||||
data: { scope },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200 && Array.isArray(res.data.data && res.data.data.categories)) {
|
||||
resolve(res.data.data.categories)
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
},
|
||||
fail: () => resolve([])
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
this.setData({ activeTab: tab })
|
||||
},
|
||||
|
||||
// 无需再次授权时,直接点击按钮执行购买/咨询
|
||||
handlePurchaseTap(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
const index = e.currentTarget.dataset.index
|
||||
this.handlePurchase(tab, index)
|
||||
},
|
||||
|
||||
// 实际执行购买/咨询逻辑(已确保有手机号)
|
||||
handlePurchase(tab, index) {
|
||||
if (!ensureProfileCompleteAndRedirect()) return
|
||||
if (index === undefined || index === null) return
|
||||
const list = tab === 'enterprise' ? this.data.enterpriseCategories : this.data.personalCategories
|
||||
const category = list[index]
|
||||
if (!category) return
|
||||
|
||||
if (category.actionType === 'buy' && category.productKey) {
|
||||
this.purchasePersonal(category)
|
||||
} else {
|
||||
this.applyConsult(category)
|
||||
}
|
||||
},
|
||||
|
||||
// 购买/企业咨询按钮:就地触发微信系统手机号授权,然后执行 handlePurchase
|
||||
onGetPhoneNumberForPurchase(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
const index = e.currentTarget.dataset.index
|
||||
const { code, errMsg } = e.detail || {}
|
||||
|
||||
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
|
||||
if (!hasPhone()) {
|
||||
wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// 用户拒绝但之前已授权过,本地已有手机号,则直接继续
|
||||
this.handlePurchase(tab, index)
|
||||
return
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
if (hasPhone()) {
|
||||
this.handlePurchase(tab, index)
|
||||
} else {
|
||||
wx.showToast({ title: '获取手机号失败', icon: 'none' })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
bindPhoneByCode(code)
|
||||
.then(() => {
|
||||
this.setData({ hasPhone: true })
|
||||
this.handlePurchase(tab, index)
|
||||
})
|
||||
.catch(() => {
|
||||
// 失败时只提示,不阻塞后续再次点击
|
||||
})
|
||||
},
|
||||
|
||||
purchasePersonal(category) {
|
||||
if (this.data.purchasing) return
|
||||
this.setData({ purchasing: true })
|
||||
wx.showLoading({ title: '处理中...', mask: true })
|
||||
const deepProductId = category.id || category.productKey || ''
|
||||
const title = category.title || '个人深度服务(1v1深度解读)'
|
||||
payment.purchasePersonalDeepService({
|
||||
deepProductId,
|
||||
description: title,
|
||||
success: () => {
|
||||
wx.hideLoading()
|
||||
this.setData({ purchasing: false })
|
||||
this._reportCrmLead(category, 'buy')
|
||||
const successMsg = (category.successMessage || '购买成功!我们的顾问会尽快与您联系,为您提供专属深度解读服务。').trim()
|
||||
const wechat = (category.serviceWechat || '').trim()
|
||||
this._showSuccessModal('购买成功', successMsg, wechat)
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
this.setData({ purchasing: false })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
applyConsult(category) {
|
||||
// serviceWechat 展示给用户,consultWechat 是存客宝 API key
|
||||
const wechat = (category.serviceWechat || '').trim()
|
||||
const apiKey = (category.consultWechat || '').trim()
|
||||
const successMsg = (category.successMessage || '感谢您的申请,我们的顾问会尽快与您联系!').trim()
|
||||
wx.showLoading({ title: '提交中...', mask: true })
|
||||
if (apiKey) {
|
||||
this._reportCrmLead(category, 'consult')
|
||||
}
|
||||
setTimeout(() => {
|
||||
wx.hideLoading()
|
||||
this._showSuccessModal('申请成功', successMsg, wechat)
|
||||
}, 600)
|
||||
},
|
||||
|
||||
_showSuccessModal(title, content, wechat) {
|
||||
this.setData({
|
||||
successModal: {
|
||||
visible: true,
|
||||
title: title || '成功',
|
||||
content: content || '',
|
||||
wechat: wechat || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
closeSuccessModal() {
|
||||
this.setData({ 'successModal.visible': false })
|
||||
},
|
||||
|
||||
copyWechat() {
|
||||
const wechat = this.data.successModal.wechat
|
||||
if (!wechat) return
|
||||
wx.setClipboardData({
|
||||
data: wechat,
|
||||
success: () => wx.showToast({ title: '已复制微信号', icon: 'success' })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 向后端上报存客宝线索,后端负责签名和调用存客宝 API
|
||||
* @param {Object} category 深度服务类目对象(需含 consultWechat / title)
|
||||
* @param {string} actionType 'buy'(付款完成)| 'consult'(申请咨询)
|
||||
*/
|
||||
_reportCrmLead(category, actionType) {
|
||||
const apiKey = category.consultWechat || ''
|
||||
if (!apiKey) return
|
||||
const apiBase = app.globalData.apiBase || ''
|
||||
if (!apiBase) return
|
||||
|
||||
const isEnterprise = this.data.activeTab === 'enterprise'
|
||||
const source = (isEnterprise ? '企业深度服务' : '个人深度服务') + (category.title ? `-${category.title}` : '')
|
||||
const remark = actionType === 'buy' ? '完成付款' : '申请咨询'
|
||||
|
||||
wx.request({
|
||||
url: `${apiBase.replace(/\/$/, '')}/api/crm/report`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
Authorization: `Bearer ${wx.getStorageSync('token') || ''}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
apiKey,
|
||||
source,
|
||||
remark,
|
||||
siteTags: category.title || '',
|
||||
},
|
||||
success(res) {
|
||||
console.log('[CRM] 线索上报结果', res.data)
|
||||
},
|
||||
fail(err) {
|
||||
console.warn('[CRM] 线索上报请求失败', err)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const { getSharePath } = require('../../utils/share')
|
||||
return { title: '神仙团队性格测试 - 发现你的内在潜能', path: getSharePath('/pages/purchase/index') }
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: '神仙团队性格测试 - 发现你的内在潜能',
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "开通会员",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "开通会员",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,135 +1,135 @@
|
||||
<!--pages/purchase/index.wxml - 开通会员(深度服务类目由接口拉取,个人/企业区分)-->
|
||||
|
||||
<!-- 成功弹窗 -->
|
||||
<view class="success-mask" wx:if="{{successModal.visible}}" bindtap="closeSuccessModal">
|
||||
<view class="success-dialog" catchtap="catchTap">
|
||||
<view class="success-icon-wrap">
|
||||
<view class="success-icon-circle">
|
||||
<text class="success-icon-check">✓</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="success-dialog-title">{{successModal.title}}</text>
|
||||
<text class="success-dialog-content">{{successModal.content}}</text>
|
||||
<view wx:if="{{successModal.wechat}}" class="success-wechat-wrap">
|
||||
<text class="success-wechat-label">客服微信</text>
|
||||
<text class="success-wechat-val">{{successModal.wechat}}</text>
|
||||
</view>
|
||||
<view class="success-dialog-btns">
|
||||
<button wx:if="{{successModal.wechat}}" class="success-btn-copy" bindtap="copyWechat">复制微信号</button>
|
||||
<button class="success-btn-close" bindtap="closeSuccessModal">我知道了</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="container">
|
||||
|
||||
<view wx:if="{{loading}}" class="loading-wrap">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 个人版:按类目列表渲染 -->
|
||||
<view wx:elif="{{activeTab === 'personal'}}">
|
||||
<view wx:for="{{personalCategories}}" wx:key="id" class="pricing-card {{index === 0 ? 'featured' : ''}}" style="margin: 24rpx;">
|
||||
<view class="card-header">
|
||||
<text class="plan-title">{{item.title}}</text>
|
||||
<view class="price-section" wx:if="{{item.price !== undefined}}">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-amount">{{item.price}}</text>
|
||||
<text class="price-unit">{{item.priceUnit || '/次'}}</text>
|
||||
</view>
|
||||
<text class="plan-subtitle" wx:if="{{item.subtitle}}">{{item.subtitle}}</text>
|
||||
</view>
|
||||
<view class="features-list" wx:if="{{item.features && item.features.length}}">
|
||||
<view class="feature-item" wx:for="{{item.features}}" wx:for-item="f" wx:key="*this">
|
||||
<view class="check-icon">✓</view>
|
||||
<text class="feature-text">{{f}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 未有手机号时:使用微信系统手机号授权组件 -->
|
||||
<button
|
||||
class="purchase-button primary"
|
||||
wx:if="{{item.actionType === 'buy' && !hasPhone}}"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumberForPurchase"
|
||||
data-tab="personal"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
<text class="button-text">立即购买</text>
|
||||
</button>
|
||||
<!-- 已有手机号时:普通按钮,直接走支付 -->
|
||||
<button
|
||||
class="purchase-button primary"
|
||||
wx:elif="{{item.actionType === 'buy' && hasPhone}}"
|
||||
bindtap="handlePurchaseTap"
|
||||
data-tab="personal"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
<text class="button-text">立即购买</text>
|
||||
</button>
|
||||
</view>
|
||||
<view wx:if="{{!loading && activeTab === 'personal' && personalCategories.length === 0}}" class="empty-tip">
|
||||
<text>暂无可用的个人版套餐</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 企业版:按类目列表渲染 -->
|
||||
<view wx:elif="{{activeTab === 'enterprise'}}">
|
||||
<view wx:for="{{enterpriseCategories}}" wx:key="id" class="pricing-card {{index === 1 ? 'featured-blue' : ''}}" style="margin: 24rpx;">
|
||||
<view class="card-header-flex">
|
||||
<view class="header-left">
|
||||
<text class="plan-title-sm">{{item.title}}</text>
|
||||
<text class="plan-desc" wx:if="{{item.subtitle}}">{{item.subtitle}}</text>
|
||||
</view>
|
||||
<view class="header-right" wx:if="{{item.priceDisplay || item.price}}">
|
||||
<text class="price-amount-small">{{item.priceDisplay || '¥' + item.price}}</text>
|
||||
<text class="price-limit" wx:if="{{item.userLimit}}">{{item.userLimit}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="features-list" wx:if="{{item.features && item.features.length}}">
|
||||
<view class="feature-item" wx:for="{{item.features}}" wx:for-item="f" wx:key="*this">
|
||||
<view class="check-icon blue">✓</view>
|
||||
<text class="feature-text">{{f}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 未有手机号时:使用微信系统手机号授权组件 -->
|
||||
<button
|
||||
class="purchase-button secondary"
|
||||
wx:if="{{!hasPhone}}"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumberForPurchase"
|
||||
data-tab="enterprise"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
<text class="button-text">{{item.buttonText || '申请咨询'}}</text>
|
||||
</button>
|
||||
<!-- 已有手机号时:普通按钮,直接触发咨询逻辑 -->
|
||||
<button
|
||||
class="purchase-button secondary"
|
||||
wx:elif="{{hasPhone}}"
|
||||
bindtap="handlePurchaseTap"
|
||||
data-tab="enterprise"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
<text class="button-text">{{item.buttonText || '申请咨询'}}</text>
|
||||
</button>
|
||||
</view>
|
||||
<view wx:if="{{!loading && activeTab === 'enterprise' && enterpriseCategories.length === 0}}" class="empty-tip">
|
||||
<text>暂无可用的企业版套餐</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="safety-tips">
|
||||
<view class="safety-item">
|
||||
<text class="safety-icon">🔒</text>
|
||||
<text class="safety-text">微信安全支付</text>
|
||||
</view>
|
||||
<view class="safety-item">
|
||||
<text class="safety-icon">✅</text>
|
||||
<text class="safety-text">即时到账开通</text>
|
||||
</view>
|
||||
<view class="safety-item">
|
||||
<text class="safety-icon">📞</text>
|
||||
<text class="safety-text">7×24客服</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/purchase/index.wxml - 开通会员(深度服务类目由接口拉取,个人/企业区分)-->
|
||||
|
||||
<!-- 成功弹窗 -->
|
||||
<view class="success-mask" wx:if="{{successModal.visible}}" bindtap="closeSuccessModal">
|
||||
<view class="success-dialog" catchtap="catchTap">
|
||||
<view class="success-icon-wrap">
|
||||
<view class="success-icon-circle">
|
||||
<text class="success-icon-check">✓</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="success-dialog-title">{{successModal.title}}</text>
|
||||
<text class="success-dialog-content">{{successModal.content}}</text>
|
||||
<view wx:if="{{successModal.wechat}}" class="success-wechat-wrap">
|
||||
<text class="success-wechat-label">客服微信</text>
|
||||
<text class="success-wechat-val">{{successModal.wechat}}</text>
|
||||
</view>
|
||||
<view class="success-dialog-btns">
|
||||
<button wx:if="{{successModal.wechat}}" class="success-btn-copy" bindtap="copyWechat">复制微信号</button>
|
||||
<button class="success-btn-close" bindtap="closeSuccessModal">我知道了</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="container">
|
||||
|
||||
<view wx:if="{{loading}}" class="loading-wrap">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 个人版:按类目列表渲染 -->
|
||||
<view wx:elif="{{activeTab === 'personal'}}">
|
||||
<view wx:for="{{personalCategories}}" wx:key="id" class="pricing-card {{index === 0 ? 'featured' : ''}}" style="margin: 24rpx;">
|
||||
<view class="card-header">
|
||||
<text class="plan-title">{{item.title}}</text>
|
||||
<view class="price-section" wx:if="{{item.price !== undefined}}">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-amount">{{item.price}}</text>
|
||||
<text class="price-unit">{{item.priceUnit || '/次'}}</text>
|
||||
</view>
|
||||
<text class="plan-subtitle" wx:if="{{item.subtitle}}">{{item.subtitle}}</text>
|
||||
</view>
|
||||
<view class="features-list" wx:if="{{item.features && item.features.length}}">
|
||||
<view class="feature-item" wx:for="{{item.features}}" wx:for-item="f" wx:key="*this">
|
||||
<view class="check-icon">✓</view>
|
||||
<text class="feature-text">{{f}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 未有手机号时:使用微信系统手机号授权组件 -->
|
||||
<button
|
||||
class="purchase-button primary"
|
||||
wx:if="{{item.actionType === 'buy' && !hasPhone}}"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumberForPurchase"
|
||||
data-tab="personal"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
<text class="button-text">立即购买</text>
|
||||
</button>
|
||||
<!-- 已有手机号时:普通按钮,直接走支付 -->
|
||||
<button
|
||||
class="purchase-button primary"
|
||||
wx:elif="{{item.actionType === 'buy' && hasPhone}}"
|
||||
bindtap="handlePurchaseTap"
|
||||
data-tab="personal"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
<text class="button-text">立即购买</text>
|
||||
</button>
|
||||
</view>
|
||||
<view wx:if="{{!loading && activeTab === 'personal' && personalCategories.length === 0}}" class="empty-tip">
|
||||
<text>暂无可用的个人版套餐</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 企业版:按类目列表渲染 -->
|
||||
<view wx:elif="{{activeTab === 'enterprise'}}">
|
||||
<view wx:for="{{enterpriseCategories}}" wx:key="id" class="pricing-card {{index === 1 ? 'featured-blue' : ''}}" style="margin: 24rpx;">
|
||||
<view class="card-header-flex">
|
||||
<view class="header-left">
|
||||
<text class="plan-title-sm">{{item.title}}</text>
|
||||
<text class="plan-desc" wx:if="{{item.subtitle}}">{{item.subtitle}}</text>
|
||||
</view>
|
||||
<view class="header-right" wx:if="{{item.priceDisplay || item.price}}">
|
||||
<text class="price-amount-small">{{item.priceDisplay || '¥' + item.price}}</text>
|
||||
<text class="price-limit" wx:if="{{item.userLimit}}">{{item.userLimit}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="features-list" wx:if="{{item.features && item.features.length}}">
|
||||
<view class="feature-item" wx:for="{{item.features}}" wx:for-item="f" wx:key="*this">
|
||||
<view class="check-icon blue">✓</view>
|
||||
<text class="feature-text">{{f}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 未有手机号时:使用微信系统手机号授权组件 -->
|
||||
<button
|
||||
class="purchase-button secondary"
|
||||
wx:if="{{!hasPhone}}"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumberForPurchase"
|
||||
data-tab="enterprise"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
<text class="button-text">{{item.buttonText || '申请咨询'}}</text>
|
||||
</button>
|
||||
<!-- 已有手机号时:普通按钮,直接触发咨询逻辑 -->
|
||||
<button
|
||||
class="purchase-button secondary"
|
||||
wx:elif="{{hasPhone}}"
|
||||
bindtap="handlePurchaseTap"
|
||||
data-tab="enterprise"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
<text class="button-text">{{item.buttonText || '申请咨询'}}</text>
|
||||
</button>
|
||||
</view>
|
||||
<view wx:if="{{!loading && activeTab === 'enterprise' && enterpriseCategories.length === 0}}" class="empty-tip">
|
||||
<text>暂无可用的企业版套餐</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="safety-tips">
|
||||
<view class="safety-item">
|
||||
<text class="safety-icon">🔒</text>
|
||||
<text class="safety-text">微信安全支付</text>
|
||||
</view>
|
||||
<view class="safety-item">
|
||||
<text class="safety-icon">✅</text>
|
||||
<text class="safety-text">即时到账开通</text>
|
||||
</view>
|
||||
<view class="safety-item">
|
||||
<text class="safety-icon">📞</text>
|
||||
<text class="safety-text">7×24客服</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "企业余额充值"
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "企业余额充值"
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<view class="recharge-page">
|
||||
<view class="card">
|
||||
<view class="title">企业余额充值</view>
|
||||
<view class="subtitle">扫码后在小程序内完成支付,支付成功后自动进入企业余额</view>
|
||||
|
||||
<view class="amount-box">
|
||||
<text class="amount-label">充值金额</text>
|
||||
<text class="amount-value">¥{{amountYuan}}</text>
|
||||
</view>
|
||||
|
||||
<view class="meta-row">
|
||||
<text class="meta-label">企业名称</text>
|
||||
<text class="meta-value">{{enterpriseName || ('企业 #' + enterpriseId)}}</text>
|
||||
</view>
|
||||
|
||||
<button class="pay-btn" loading="{{paying}}" disabled="{{paying}}" bindtap="submitRecharge">
|
||||
<text class="pay-btn-text">{{paying ? '支付中...' : '立即充值'}}</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="recharge-page">
|
||||
<view class="card">
|
||||
<view class="title">企业余额充值</view>
|
||||
<view class="subtitle">扫码后在小程序内完成支付,支付成功后自动进入企业余额</view>
|
||||
|
||||
<view class="amount-box">
|
||||
<text class="amount-label">充值金额</text>
|
||||
<text class="amount-value">¥{{amountYuan}}</text>
|
||||
</view>
|
||||
|
||||
<view class="meta-row">
|
||||
<text class="meta-label">企业名称</text>
|
||||
<text class="meta-value">{{enterpriseName || ('企业 #' + enterpriseId)}}</text>
|
||||
</view>
|
||||
|
||||
<button class="pay-btn" loading="{{paying}}" disabled="{{paying}}" bindtap="submitRecharge">
|
||||
<text class="pay-btn-text">{{paying ? '支付中...' : '立即充值'}}</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,106 +1,106 @@
|
||||
.recharge-page {
|
||||
min-height: 100vh;
|
||||
padding: 40rpx 28rpx;
|
||||
background: linear-gradient(180deg, #f5f3ff 0%, #ffffff 100%);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border-radius: 32rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
box-shadow: 0 20rpx 60rpx rgba(124, 58, 237, 0.08);
|
||||
border: 2rpx solid rgba(124, 58, 237, 0.06);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.7;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.amount-box {
|
||||
margin-top: 36rpx;
|
||||
padding: 32rpx;
|
||||
background: #f8fafc;
|
||||
border-radius: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 28rpx;
|
||||
padding: 24rpx 8rpx 0;
|
||||
font-size: 26rpx;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
max-width: 420rpx;
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
margin-top: 44rpx;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 55%, #a855f7 100%);
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 18rpx 36rpx rgba(124, 58, 237, 0.28);
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pay-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.pay-btn-text {
|
||||
position: relative;
|
||||
padding-right: 28rpx;
|
||||
}
|
||||
|
||||
.pay-btn-text::after {
|
||||
content: '>';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 24rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.recharge-page {
|
||||
min-height: 100vh;
|
||||
padding: 40rpx 28rpx;
|
||||
background: linear-gradient(180deg, #f5f3ff 0%, #ffffff 100%);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border-radius: 32rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
box-shadow: 0 20rpx 60rpx rgba(124, 58, 237, 0.08);
|
||||
border: 2rpx solid rgba(124, 58, 237, 0.06);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.7;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.amount-box {
|
||||
margin-top: 36rpx;
|
||||
padding: 32rpx;
|
||||
background: #f8fafc;
|
||||
border-radius: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 28rpx;
|
||||
padding: 24rpx 8rpx 0;
|
||||
font-size: 26rpx;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
max-width: 420rpx;
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
margin-top: 44rpx;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 55%, #a855f7 100%);
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 18rpx 36rpx rgba(124, 58, 237, 0.28);
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pay-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.pay-btn-text {
|
||||
position: relative;
|
||||
padding-right: 28rpx;
|
||||
}
|
||||
|
||||
.pay-btn-text::after {
|
||||
content: '>';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 24rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@@ -1,171 +1,157 @@
|
||||
// pages/result/disc.js - DISC结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
const { isProfileComplete } = require('../../utils/phoneAuth.js')
|
||||
|
||||
function toIntPercent(v) {
|
||||
if (v == null) return 0
|
||||
const n = typeof v === 'number' ? v : Number(v)
|
||||
return Number.isFinite(n) ? Math.round(n) : 0
|
||||
}
|
||||
|
||||
function withPercentagesInt(data) {
|
||||
if (!data) return data
|
||||
const p = data.percentages || {}
|
||||
const out = { ...data }
|
||||
out.percentagesInt = {
|
||||
D: toIntPercent(p.D ?? p.d),
|
||||
I: toIntPercent(p.I ?? p.i),
|
||||
S: toIntPercent(p.S ?? p.s),
|
||||
C: toIntPercent(p.C ?? p.c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
typeList: [
|
||||
{ type: 'D', label: 'D型 - 支配型', colorClass: 'fill-d' },
|
||||
{ type: 'I', label: 'I型 - 影响型', colorClass: 'fill-i' },
|
||||
{ type: 'S', label: 'S型 - 稳健型', colorClass: 'fill-s' },
|
||||
{ type: 'C', label: 'C型 - 谨慎型', colorClass: 'fill-c' }
|
||||
],
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 },
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false,
|
||||
isProfileComplete: false,
|
||||
maintenanceMode: false
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const maintenanceMode = !!(getApp().globalData && getApp().globalData.maintenanceMode)
|
||||
this.setData({ isProfileComplete: isProfileComplete(), maintenanceMode })
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
if (id && type === 'disc') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('discResult')
|
||||
if (result) {
|
||||
this.setData({ result: withPercentagesInt(result) })
|
||||
this.initPayInfoFromRuntime('disc')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.setData({ result: withPercentagesInt(data) })
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
const maintenanceMode = !!(getApp().globalData && getApp().globalData.maintenanceMode)
|
||||
this.setData({ payInfo, isProfileComplete: isProfileComplete(), maintenanceMode })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const pricing = cfg.pricing || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: { requiresPayment, isPaid: false, amountYuan }
|
||||
})
|
||||
})
|
||||
.catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } }))
|
||||
},
|
||||
|
||||
goToCompleteProfile() {
|
||||
wx.navigateTo({ url: '/pages/user-profile/index' })
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay, isProfileComplete } = this.data
|
||||
if (!isProfileComplete) return
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||
payment.purchaseDiscTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('discResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/disc' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
// pages/result/disc.js - DISC结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
|
||||
function toIntPercent(v) {
|
||||
if (v == null) return 0
|
||||
const n = typeof v === 'number' ? v : Number(v)
|
||||
return Number.isFinite(n) ? Math.round(n) : 0
|
||||
}
|
||||
|
||||
function withPercentagesInt(data) {
|
||||
if (!data) return data
|
||||
const p = data.percentages || {}
|
||||
const out = { ...data }
|
||||
out.percentagesInt = {
|
||||
D: toIntPercent(p.D ?? p.d),
|
||||
I: toIntPercent(p.I ?? p.i),
|
||||
S: toIntPercent(p.S ?? p.s),
|
||||
C: toIntPercent(p.C ?? p.c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
typeList: [
|
||||
{ type: 'D', label: 'D型 - 支配型', colorClass: 'fill-d' },
|
||||
{ type: 'I', label: 'I型 - 影响型', colorClass: 'fill-i' },
|
||||
{ type: 'S', label: 'S型 - 稳健型', colorClass: 'fill-s' },
|
||||
{ type: 'C', label: 'C型 - 谨慎型', colorClass: 'fill-c' }
|
||||
],
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 },
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
if (id && type === 'disc') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('discResult')
|
||||
if (result) {
|
||||
this.setData({ result: withPercentagesInt(result) })
|
||||
this.initPayInfoFromRuntime('disc')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.setData({ result: withPercentagesInt(data) })
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const pricing = cfg.pricing || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: { requiresPayment, isPaid: false, amountYuan }
|
||||
})
|
||||
})
|
||||
.catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } }))
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||
payment.purchaseDiscTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('discResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/disc' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "DISC结果",
|
||||
"navigationBarBackgroundColor": "#3B82F6",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "DISC结果",
|
||||
"navigationBarBackgroundColor": "#3B82F6",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,81 @@
|
||||
<!--pages/result/disc.wxml - DISC结果(按旧版模板重构)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">您的DISC性格类型</text>
|
||||
<text class="type-value">{{result.dominantType}}{{result.secondaryType ? ' + ' + result.secondaryType : ''}}</text>
|
||||
<text class="type-description" wx:if="{{result.description && result.description.description}}">{{result.description.description}}</text>
|
||||
</view>
|
||||
|
||||
<view class="paywall-card" wx:if="{{(!maintenanceMode && !isProfileComplete) || ((maintenanceMode || isProfileComplete) && payInfo.requiresPayment && !payInfo.isPaid)}}">
|
||||
<view class="paywall-content">
|
||||
<view class="paywall-blur">
|
||||
<text class="paywall-fake-title">完整DISC报告</text>
|
||||
<text class="paywall-fake-line">• 四维得分详情</text>
|
||||
<text class="paywall-fake-line">• 性格特征与优劣势</text>
|
||||
<text class="paywall-fake-line">• 职业匹配建议</text>
|
||||
</view>
|
||||
<view class="paywall-mask"></view>
|
||||
<view
|
||||
class="paywall-btn"
|
||||
wx:if="{{!maintenanceMode && !isProfileComplete}}"
|
||||
bindtap="goToCompleteProfile"
|
||||
>
|
||||
<text class="paywall-btn-main">完善资料</text>
|
||||
</view>
|
||||
<view
|
||||
wx:elif="{{payInfo.requiresPayment && !payInfo.isPaid}}"
|
||||
class="paywall-btn"
|
||||
bindtap="unlockFullReport"
|
||||
>
|
||||
<text class="paywall-btn-main">解锁完整报告</text>
|
||||
<text class="paywall-btn-price">¥{{payInfo.amountYuan}} / 次</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="scores-section" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="section-title">DISC得分详情</text>
|
||||
<view class="score-item" wx:for="{{typeList}}" wx:key="type">
|
||||
<view class="score-header">
|
||||
<text class="score-label">{{item.label}}</text>
|
||||
<text class="score-value">{{result.percentagesInt[item.type] != null ? result.percentagesInt[item.type] + '%' : '0%'}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar {{item.colorClass}}" style="width: {{result.percentages[item.type]}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="card-title">主要性格特征分析</text>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">优势</text>
|
||||
<view class="trait-item" wx:for="{{result.description.strengths}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">需要注意的方面</text>
|
||||
<view class="trait-item" wx:for="{{result.description.weaknesses}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="card-title">职业匹配度分析</text>
|
||||
<view class="career-item" wx:for="{{result.description.careers}}" wx:key="*this">
|
||||
<view class="career-dot"></view>
|
||||
<text class="career-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
<view class="btn btn-outline" bindtap="retakeTest">
|
||||
<text class="btn-text-outline">重新测试</text>
|
||||
</view>
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!--pages/result/disc.wxml - DISC结果(按旧版模板重构)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">您的DISC性格类型</text>
|
||||
<text class="type-value">{{result.dominantType}}{{result.secondaryType ? ' + ' + result.secondaryType : ''}}</text>
|
||||
<text class="type-description" wx:if="{{result.description && result.description.description}}">{{result.description.description}}</text>
|
||||
</view>
|
||||
|
||||
<view class="paywall-card" wx:if="{{payInfo.requiresPayment && !payInfo.isPaid}}">
|
||||
<view class="paywall-content">
|
||||
<view class="paywall-blur">
|
||||
<text class="paywall-fake-title">完整DISC报告</text>
|
||||
<text class="paywall-fake-line">• 四维得分详情</text>
|
||||
<text class="paywall-fake-line">• 性格特征与优劣势</text>
|
||||
<text class="paywall-fake-line">• 职业匹配建议</text>
|
||||
</view>
|
||||
<view class="paywall-mask"></view>
|
||||
<view class="paywall-btn" bindtap="unlockFullReport">
|
||||
<text class="paywall-btn-main">解锁完整报告</text>
|
||||
<text class="paywall-btn-price">¥{{payInfo.amountYuan}} / 次</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="scores-section" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="section-title">DISC得分详情</text>
|
||||
<view class="score-item" wx:for="{{typeList}}" wx:key="type">
|
||||
<view class="score-header">
|
||||
<text class="score-label">{{item.label}}</text>
|
||||
<text class="score-value">{{result.percentagesInt[item.type] != null ? result.percentagesInt[item.type] + '%' : '0%'}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar {{item.colorClass}}" style="width: {{result.percentages[item.type]}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="card-title">主要性格特征分析</text>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">优势</text>
|
||||
<view class="trait-item" wx:for="{{result.description.strengths}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">需要注意的方面</text>
|
||||
<view class="trait-item" wx:for="{{result.description.weaknesses}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="card-title">职业匹配度分析</text>
|
||||
<view class="career-item" wx:for="{{result.description.careers}}" wx:key="*this">
|
||||
<view class="career-dot"></view>
|
||||
<text class="career-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
<view class="btn btn-outline" bindtap="retakeTest">
|
||||
<text class="btn-text-outline">重新测试</text>
|
||||
</view>
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -1,231 +1,224 @@
|
||||
// pages/result/mbti.js - MBTI结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
const { hasPhone, bindPhoneByCode, isProfileComplete } = require('../../utils/phoneAuth.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
dimensions: [],
|
||||
mbtiDesc: {
|
||||
title: '',
|
||||
description: '',
|
||||
strengths: [],
|
||||
weaknesses: [],
|
||||
careers: [],
|
||||
relationships: ''
|
||||
},
|
||||
payInfo: {
|
||||
requiresPayment: false,
|
||||
isPaid: false,
|
||||
amountYuan: 0
|
||||
},
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false,
|
||||
hasPhone: false,
|
||||
isProfileComplete: false,
|
||||
maintenanceMode: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
|
||||
if (id && type === 'mbti') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
|
||||
const result = wx.getStorageSync('mbtiResult')
|
||||
if (result) {
|
||||
this.applyResult(result)
|
||||
this.initPayInfoFromRuntime('mbti')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const maintenanceMode = !!(getApp().globalData && getApp().globalData.maintenanceMode)
|
||||
this.setData({ hasPhone: hasPhone(), isProfileComplete: isProfileComplete(), maintenanceMode })
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) {
|
||||
wx.showToast({ title: '配置异常', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.applyResult(data)
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
const maintenanceMode = !!(getApp().globalData && getApp().globalData.maintenanceMode)
|
||||
this.setData({ payInfo, isProfileComplete: isProfileComplete(), maintenanceMode })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
applyResult(result) {
|
||||
if (!result) return
|
||||
const desc = result.description || {}
|
||||
const dimensions = (result.dimensionScores && !result.locked)
|
||||
? [
|
||||
{ key: 'EI', left: '外向(E)', right: '内向(I)', ...result.dimensionScores.EI },
|
||||
{ key: 'SN', left: '感觉(S)', right: '直觉(N)', ...result.dimensionScores.SN },
|
||||
{ key: 'TF', left: '思考(T)', right: '情感(F)', ...result.dimensionScores.TF },
|
||||
{ key: 'JP', left: '判断(J)', right: '知觉(P)', ...result.dimensionScores.JP }
|
||||
]
|
||||
: []
|
||||
this.setData({
|
||||
result,
|
||||
dimensions,
|
||||
mbtiDesc: {
|
||||
title: desc.name || '',
|
||||
description: desc.description || '',
|
||||
strengths: desc.strengths || [],
|
||||
weaknesses: desc.weaknesses || [],
|
||||
careers: desc.careers || [],
|
||||
relationships: desc.relationships || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const pricing = cfg.pricing || {}
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: {
|
||||
requiresPayment,
|
||||
isPaid: false,
|
||||
amountYuan
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.setData({
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 }
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payment.purchaseMbtiTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 完善资料:用户点击后手动跳转,不强制
|
||||
goToCompleteProfile() {
|
||||
wx.navigateTo({ url: '/pages/user-profile/index' })
|
||||
},
|
||||
|
||||
// 付费解锁按钮:就地触发微信手机号授权,然后调用 unlockFullReport
|
||||
onGetPhoneNumberForMbtiPay(e) {
|
||||
const { code, errMsg } = e.detail || {}
|
||||
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
|
||||
if (hasPhone()) {
|
||||
this.unlockFullReport()
|
||||
} else {
|
||||
wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' })
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!code) {
|
||||
if (hasPhone()) {
|
||||
this.unlockFullReport()
|
||||
} else {
|
||||
wx.showToast({ title: '获取手机号失败', icon: 'none' })
|
||||
}
|
||||
return
|
||||
}
|
||||
bindPhoneByCode(code)
|
||||
.then(() => {
|
||||
this.setData({ hasPhone: true })
|
||||
this.unlockFullReport()
|
||||
})
|
||||
.catch(() => {
|
||||
// 保持在当前页,等待用户重新点击
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('mbtiResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/mbti' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index'),
|
||||
imageUrl: '/images/share-mbti.png'
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
// pages/result/mbti.js - MBTI结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
dimensions: [],
|
||||
mbtiDesc: {
|
||||
title: '',
|
||||
description: '',
|
||||
strengths: [],
|
||||
weaknesses: [],
|
||||
careers: [],
|
||||
relationships: ''
|
||||
},
|
||||
payInfo: {
|
||||
requiresPayment: false,
|
||||
isPaid: false,
|
||||
amountYuan: 0
|
||||
},
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false,
|
||||
hasPhone: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
|
||||
if (id && type === 'mbti') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
|
||||
const result = wx.getStorageSync('mbtiResult')
|
||||
if (result) {
|
||||
this.applyResult(result)
|
||||
this.initPayInfoFromRuntime('mbti')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (!ensureProfileCompleteAndRedirect()) return
|
||||
this.setData({ hasPhone: hasPhone() })
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) {
|
||||
wx.showToast({ title: '配置异常', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.applyResult(data)
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
applyResult(result) {
|
||||
if (!result) return
|
||||
const desc = result.description || {}
|
||||
const dimensions = (result.dimensionScores && !result.locked)
|
||||
? [
|
||||
{ key: 'EI', left: '外向(E)', right: '内向(I)', ...result.dimensionScores.EI },
|
||||
{ key: 'SN', left: '感觉(S)', right: '直觉(N)', ...result.dimensionScores.SN },
|
||||
{ key: 'TF', left: '思考(T)', right: '情感(F)', ...result.dimensionScores.TF },
|
||||
{ key: 'JP', left: '判断(J)', right: '知觉(P)', ...result.dimensionScores.JP }
|
||||
]
|
||||
: []
|
||||
this.setData({
|
||||
result,
|
||||
dimensions,
|
||||
mbtiDesc: {
|
||||
title: desc.name || '',
|
||||
description: desc.description || '',
|
||||
strengths: desc.strengths || [],
|
||||
weaknesses: desc.weaknesses || [],
|
||||
careers: desc.careers || [],
|
||||
relationships: desc.relationships || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const pricing = cfg.pricing || {}
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: {
|
||||
requiresPayment,
|
||||
isPaid: false,
|
||||
amountYuan
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.setData({
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 }
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payment.purchaseMbtiTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 付费解锁按钮:就地触发微信手机号授权,然后调用 unlockFullReport
|
||||
onGetPhoneNumberForMbtiPay(e) {
|
||||
if (!ensureProfileCompleteAndRedirect()) return
|
||||
const { code, errMsg } = e.detail || {}
|
||||
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
|
||||
if (!hasPhone()) {
|
||||
wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.unlockFullReport()
|
||||
return
|
||||
}
|
||||
if (!code) {
|
||||
if (hasPhone()) {
|
||||
this.unlockFullReport()
|
||||
} else {
|
||||
wx.showToast({ title: '获取手机号失败', icon: 'none' })
|
||||
}
|
||||
return
|
||||
}
|
||||
bindPhoneByCode(code)
|
||||
.then(() => {
|
||||
this.setData({ hasPhone: true })
|
||||
this.unlockFullReport()
|
||||
})
|
||||
.catch(() => {
|
||||
// 保持在当前页,等待用户重新点击
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('mbtiResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/mbti' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index'),
|
||||
imageUrl: '/images/share-mbti.png'
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "MBTI结果",
|
||||
"navigationBarBackgroundColor": "#FF6B8A",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "MBTI结果",
|
||||
"navigationBarBackgroundColor": "#FF6B8A",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,116 +1,107 @@
|
||||
<!--pages/result/mbti.wxml - MBTI结果页面(支持付费墙)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">您的MBTI性格类型</text>
|
||||
<text class="type-value">{{result.mbtiType}}</text>
|
||||
<text class="type-title">{{mbtiDesc.title}}</text>
|
||||
<text class="type-description">{{mbtiDesc.description}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 付费墙:审核模式下跳过资料检查;非审核模式下未完善资料显示「完善资料」 -->
|
||||
<view class="paywall-card" wx:if="{{(!maintenanceMode && !isProfileComplete) || ((maintenanceMode || isProfileComplete) && payInfo.requiresPayment && !payInfo.isPaid)}}">
|
||||
<view class="paywall-content">
|
||||
<view class="paywall-blur">
|
||||
<text class="paywall-fake-title">完整性格分析</text>
|
||||
<text class="paywall-fake-line">• 四维得分与主导倾向</text>
|
||||
<text class="paywall-fake-line">• 优势与需要注意的方面</text>
|
||||
<text class="paywall-fake-line">• 职业匹配与人际关系建议</text>
|
||||
</view>
|
||||
<view class="paywall-mask"></view>
|
||||
<!-- 未完善资料(非审核模式):显示完善资料按钮,用户点击后手动跳转 -->
|
||||
<view
|
||||
class="paywall-btn"
|
||||
wx:if="{{!maintenanceMode && !isProfileComplete}}"
|
||||
bindtap="goToCompleteProfile"
|
||||
>
|
||||
<text class="paywall-btn-main">完善资料</text>
|
||||
</view>
|
||||
<!-- 有价格:解锁按钮 -->
|
||||
<block wx:elif="{{payInfo.requiresPayment && !payInfo.isPaid}}">
|
||||
<button
|
||||
class="paywall-btn"
|
||||
wx:if="{{!hasPhone}}"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumberForMbtiPay"
|
||||
>
|
||||
<text class="paywall-btn-main">解锁完整报告</text>
|
||||
<text class="paywall-btn-price">¥{{payInfo.amountYuan}} / 次</text>
|
||||
</button>
|
||||
<view
|
||||
class="paywall-btn"
|
||||
wx:elif="{{hasPhone}}"
|
||||
bindtap="unlockFullReport"
|
||||
>
|
||||
<text class="paywall-btn-main">解锁完整报告</text>
|
||||
<text class="paywall-btn-price">¥{{payInfo.amountYuan}} / 次</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="dimensions-section" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<view class="dimension-item" wx:for="{{dimensions}}" wx:key="key">
|
||||
<view class="dimension-labels">
|
||||
<text class="label-left">{{item.left}}</text>
|
||||
<text class="label-right">{{item.right}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar" style="width: {{item.percentage}}%"></view>
|
||||
</view>
|
||||
<view class="dimension-values">
|
||||
<text class="value-left">{{item.dominant}}</text>
|
||||
<text class="value-right">{{item.percentage}}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="card-title">性格特征分析</text>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">优势</text>
|
||||
<view class="trait-item" wx:for="{{mbtiDesc.strengths}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">需要注意的方面</text>
|
||||
<view class="trait-item" wx:for="{{mbtiDesc.weaknesses}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="card-title">职业匹配度分析</text>
|
||||
<view class="career-item" wx:for="{{mbtiDesc.careers}}" wx:key="*this">
|
||||
<view class="career-dot"></view>
|
||||
<text class="career-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid) && mbtiDesc.relationships}}">
|
||||
<text class="card-title">人际关系分析</text>
|
||||
<text class="relationship-text">{{mbtiDesc.relationships}}</text>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
<view class="btn btn-outline" bindtap="retakeTest">
|
||||
<text class="btn-text-outline">重新测试</text>
|
||||
</view>
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!--pages/result/mbti.wxml - MBTI结果页面(支持付费墙)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">您的MBTI性格类型</text>
|
||||
<text class="type-value">{{result.mbtiType}}</text>
|
||||
<text class="type-title">{{mbtiDesc.title}}</text>
|
||||
<text class="type-description">{{mbtiDesc.description}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 付费墙:未解锁时显示 -->
|
||||
<view class="paywall-card" wx:if="{{payInfo.requiresPayment && !payInfo.isPaid}}">
|
||||
<view class="paywall-content">
|
||||
<view class="paywall-blur">
|
||||
<text class="paywall-fake-title">完整性格分析</text>
|
||||
<text class="paywall-fake-line">• 四维得分与主导倾向</text>
|
||||
<text class="paywall-fake-line">• 优势与需要注意的方面</text>
|
||||
<text class="paywall-fake-line">• 职业匹配与人际关系建议</text>
|
||||
</view>
|
||||
<view class="paywall-mask"></view>
|
||||
<!-- 未有手机号:使用微信系统手机号授权 -->
|
||||
<button
|
||||
class="paywall-btn"
|
||||
wx:if="{{!hasPhone}}"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumberForMbtiPay"
|
||||
>
|
||||
<text class="paywall-btn-main">解锁完整报告</text>
|
||||
<text class="paywall-btn-price">¥{{payInfo.amountYuan}} / 次</text>
|
||||
</button>
|
||||
<!-- 已有手机号:普通按钮,直接解锁 -->
|
||||
<button
|
||||
class="paywall-btn"
|
||||
wx:elif="{{hasPhone}}"
|
||||
bindtap="unlockFullReport"
|
||||
>
|
||||
<text class="paywall-btn-main">解锁完整报告</text>
|
||||
<text class="paywall-btn-price">¥{{payInfo.amountYuan}} / 次</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="dimensions-section" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<view class="dimension-item" wx:for="{{dimensions}}" wx:key="key">
|
||||
<view class="dimension-labels">
|
||||
<text class="label-left">{{item.left}}</text>
|
||||
<text class="label-right">{{item.right}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar" style="width: {{item.percentage}}%"></view>
|
||||
</view>
|
||||
<view class="dimension-values">
|
||||
<text class="value-left">{{item.dominant}}</text>
|
||||
<text class="value-right">{{item.percentage}}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="card-title">性格特征分析</text>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">优势</text>
|
||||
<view class="trait-item" wx:for="{{mbtiDesc.strengths}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">需要注意的方面</text>
|
||||
<view class="trait-item" wx:for="{{mbtiDesc.weaknesses}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="card-title">职业匹配度分析</text>
|
||||
<view class="career-item" wx:for="{{mbtiDesc.careers}}" wx:key="*this">
|
||||
<view class="career-dot"></view>
|
||||
<text class="career-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(!payInfo.requiresPayment || payInfo.isPaid) && mbtiDesc.relationships}}">
|
||||
<text class="card-title">人际关系分析</text>
|
||||
<text class="relationship-text">{{mbtiDesc.relationships}}</text>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
<view class="btn btn-outline" bindtap="retakeTest">
|
||||
<text class="btn-text-outline">重新测试</text>
|
||||
</view>
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -1,171 +1,157 @@
|
||||
// pages/result/pdp.js - PDP结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
const { isProfileComplete } = require('../../utils/phoneAuth.js')
|
||||
|
||||
const PDP_KEYS = ['Tiger', 'Peacock', 'Koala', 'Owl', 'Chameleon']
|
||||
|
||||
function toIntPercent(v) {
|
||||
if (v == null) return 0
|
||||
const n = typeof v === 'number' ? v : Number(v)
|
||||
return Number.isFinite(n) ? Math.round(n) : 0
|
||||
}
|
||||
|
||||
function withPercentagesInt(data) {
|
||||
if (!data) return data
|
||||
const p = data.percentages || {}
|
||||
const out = { ...data }
|
||||
const ints = {}
|
||||
PDP_KEYS.forEach((k) => { ints[k] = toIntPercent(p[k] ?? p[k.toLowerCase()]) })
|
||||
out.percentagesInt = ints
|
||||
return out
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
typeList: [
|
||||
{ type: 'Tiger', emoji: '🐅', label: '老虎型', colorClass: 'fill-tiger' },
|
||||
{ type: 'Peacock', emoji: '🦚', label: '孔雀型', colorClass: 'fill-peacock' },
|
||||
{ type: 'Koala', emoji: '🐨', label: '考拉型', colorClass: 'fill-koala' },
|
||||
{ type: 'Owl', emoji: '🦉', label: '猫头鹰型', colorClass: 'fill-owl' },
|
||||
{ type: 'Chameleon', emoji: '🦎', label: '变色龙型', colorClass: 'fill-chameleon' }
|
||||
],
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 },
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false,
|
||||
isProfileComplete: false,
|
||||
maintenanceMode: false
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const maintenanceMode = !!(getApp().globalData && getApp().globalData.maintenanceMode)
|
||||
this.setData({ isProfileComplete: isProfileComplete(), maintenanceMode })
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
if (id && type === 'pdp') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('pdpResult')
|
||||
if (result) {
|
||||
this.setData({ result: withPercentagesInt(result) })
|
||||
this.initPayInfoFromRuntime('pdp')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.setData({ result: withPercentagesInt(data) })
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
const maintenanceMode = !!(getApp().globalData && getApp().globalData.maintenanceMode)
|
||||
this.setData({ payInfo, isProfileComplete: isProfileComplete(), maintenanceMode })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const pricing = cfg.pricing || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: { requiresPayment, isPaid: false, amountYuan }
|
||||
})
|
||||
})
|
||||
.catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } }))
|
||||
},
|
||||
|
||||
goToCompleteProfile() {
|
||||
wx.navigateTo({ url: '/pages/user-profile/index' })
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay, isProfileComplete } = this.data
|
||||
if (!isProfileComplete) return
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||
payment.purchasePdpTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('pdpResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
// pages/result/pdp.js - PDP结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
|
||||
const PDP_KEYS = ['Tiger', 'Peacock', 'Koala', 'Owl', 'Chameleon']
|
||||
|
||||
function toIntPercent(v) {
|
||||
if (v == null) return 0
|
||||
const n = typeof v === 'number' ? v : Number(v)
|
||||
return Number.isFinite(n) ? Math.round(n) : 0
|
||||
}
|
||||
|
||||
function withPercentagesInt(data) {
|
||||
if (!data) return data
|
||||
const p = data.percentages || {}
|
||||
const out = { ...data }
|
||||
const ints = {}
|
||||
PDP_KEYS.forEach((k) => { ints[k] = toIntPercent(p[k] ?? p[k.toLowerCase()]) })
|
||||
out.percentagesInt = ints
|
||||
return out
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
typeList: [
|
||||
{ type: 'Tiger', emoji: '🐅', label: '老虎型', colorClass: 'fill-tiger' },
|
||||
{ type: 'Peacock', emoji: '🦚', label: '孔雀型', colorClass: 'fill-peacock' },
|
||||
{ type: 'Koala', emoji: '🐨', label: '考拉型', colorClass: 'fill-koala' },
|
||||
{ type: 'Owl', emoji: '🦉', label: '猫头鹰型', colorClass: 'fill-owl' },
|
||||
{ type: 'Chameleon', emoji: '🦎', label: '变色龙型', colorClass: 'fill-chameleon' }
|
||||
],
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 },
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
if (id && type === 'pdp') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('pdpResult')
|
||||
if (result) {
|
||||
this.setData({ result: withPercentagesInt(result) })
|
||||
this.initPayInfoFromRuntime('pdp')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.setData({ result: withPercentagesInt(data) })
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const pricing = cfg.pricing || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: { requiresPayment, isPaid: false, amountYuan }
|
||||
})
|
||||
})
|
||||
.catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } }))
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||
payment.purchasePdpTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('pdpResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "PDP结果",
|
||||
"navigationBarBackgroundColor": "#F59E0B",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "PDP结果",
|
||||
"navigationBarBackgroundColor": "#F59E0B",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,98 +1,87 @@
|
||||
<!--pages/result/pdp.wxml - PDP结果(按旧版模板重构)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">PDP性格类型</text>
|
||||
<text class="type-value">{{result.description.type || result.dominantType}}{{result.description.emoji || ''}}</text>
|
||||
<text class="type-title" wx:if="{{result.description.title}}">{{result.description.title}}</text>
|
||||
<text class="type-description" wx:if="{{result.description.description}}">{{result.description.description}}</text>
|
||||
</view>
|
||||
|
||||
<view class="paywall-card" wx:if="{{(!maintenanceMode && !isProfileComplete) || ((maintenanceMode || isProfileComplete) && payInfo.requiresPayment && !payInfo.isPaid)}}">
|
||||
<view class="paywall-content">
|
||||
<view class="paywall-blur">
|
||||
<text class="paywall-fake-title">完整PDP报告</text>
|
||||
<text class="paywall-fake-line">• 五维得分详情</text>
|
||||
<text class="paywall-fake-line">• 性格特征与团队角色</text>
|
||||
<text class="paywall-fake-line">• 推荐职业</text>
|
||||
</view>
|
||||
<view class="paywall-mask"></view>
|
||||
<view
|
||||
class="paywall-btn"
|
||||
wx:if="{{!maintenanceMode && !isProfileComplete}}"
|
||||
bindtap="goToCompleteProfile"
|
||||
>
|
||||
<text class="paywall-btn-main">完善资料</text>
|
||||
</view>
|
||||
<view
|
||||
wx:elif="{{payInfo.requiresPayment && !payInfo.isPaid}}"
|
||||
class="paywall-btn"
|
||||
bindtap="unlockFullReport"
|
||||
>
|
||||
<text class="paywall-btn-main">解锁完整报告</text>
|
||||
<text class="paywall-btn-price">¥{{payInfo.amountYuan}} / 次</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="scores-section" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="section-title">PDP得分详情</text>
|
||||
<view class="score-item" wx:for="{{typeList}}" wx:key="type">
|
||||
<view class="score-header">
|
||||
<text class="score-label">{{item.emoji}} {{item.label}}</text>
|
||||
<text class="score-value">{{result.percentagesInt && result.percentagesInt[item.type] != null ? result.percentagesInt[item.type] + '%' : '0%'}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar {{item.colorClass}}" style="width: {{result.percentages[item.type]}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="card-title">性格特征</text>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">优势</text>
|
||||
<view class="trait-item" wx:for="{{result.description.strengths}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">需要注意的方面</text>
|
||||
<view class="trait-item" wx:for="{{result.description.weaknesses}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="card-title">团队角色</text>
|
||||
<text class="relationship-text">{{result.description.teamRole}}</text>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{(maintenanceMode || isProfileComplete) && (!payInfo.requiresPayment || payInfo.isPaid)}}">
|
||||
<text class="card-title">推荐职业</text>
|
||||
<view class="career-item" wx:for="{{result.description.careers}}" wx:key="*this">
|
||||
<view class="career-dot"></view>
|
||||
<text class="career-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
<view class="btn btn-outline" bindtap="retakeTest">
|
||||
<text class="btn-text-outline">重新测试</text>
|
||||
</view>
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!--pages/result/pdp.wxml - PDP结果(按旧版模板重构)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">PDP性格类型</text>
|
||||
<text class="type-value">{{result.description.type || result.dominantType}}{{result.description.emoji || ''}}</text>
|
||||
<text class="type-title" wx:if="{{result.description.title}}">{{result.description.title}}</text>
|
||||
<text class="type-description" wx:if="{{result.description.description}}">{{result.description.description}}</text>
|
||||
</view>
|
||||
|
||||
<view class="paywall-card" wx:if="{{payInfo.requiresPayment && !payInfo.isPaid}}">
|
||||
<view class="paywall-content">
|
||||
<view class="paywall-blur">
|
||||
<text class="paywall-fake-title">完整PDP报告</text>
|
||||
<text class="paywall-fake-line">• 五维得分详情</text>
|
||||
<text class="paywall-fake-line">• 性格特征与团队角色</text>
|
||||
<text class="paywall-fake-line">• 推荐职业</text>
|
||||
</view>
|
||||
<view class="paywall-mask"></view>
|
||||
<view class="paywall-btn" bindtap="unlockFullReport">
|
||||
<text class="paywall-btn-main">解锁完整报告</text>
|
||||
<text class="paywall-btn-price">¥{{payInfo.amountYuan}} / 次</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="scores-section" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="section-title">PDP得分详情</text>
|
||||
<view class="score-item" wx:for="{{typeList}}" wx:key="type">
|
||||
<view class="score-header">
|
||||
<text class="score-label">{{item.emoji}} {{item.label}}</text>
|
||||
<text class="score-value">{{result.percentagesInt && result.percentagesInt[item.type] != null ? result.percentagesInt[item.type] + '%' : '0%'}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar {{item.colorClass}}" style="width: {{result.percentages[item.type]}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="card-title">性格特征</text>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">优势</text>
|
||||
<view class="trait-item" wx:for="{{result.description.strengths}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="trait-section">
|
||||
<text class="trait-title">需要注意的方面</text>
|
||||
<view class="trait-item" wx:for="{{result.description.weaknesses}}" wx:key="*this">
|
||||
<text class="trait-bullet">•</text>
|
||||
<text class="trait-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="card-title">团队角色</text>
|
||||
<text class="relationship-text">{{result.description.teamRole}}</text>
|
||||
</view>
|
||||
|
||||
<view class="analysis-card" wx:if="{{!payInfo.requiresPayment || payInfo.isPaid}}">
|
||||
<text class="card-title">推荐职业</text>
|
||||
<view class="career-item" wx:for="{{result.description.careers}}" wx:key="*this">
|
||||
<view class="career-dot"></view>
|
||||
<text class="career-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
<view class="btn btn-outline" bindtap="retakeTest">
|
||||
<text class="btn-text-outline">重新测试</text>
|
||||
</view>
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "简历综合分析"
|
||||
}
|
||||
|
||||
{
|
||||
"navigationBarTitleText": "简历综合分析"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
<!--pages/result/resume.wxml - 简历综合分析结果页 -->
|
||||
<view class="container">
|
||||
<!-- 分析中状态 -->
|
||||
<view class="analyzing-modal" wx:if="{{loading && !error}}">
|
||||
<view class="analyzing-content">
|
||||
<view class="analyzing-icon">
|
||||
<view class="spinner"></view>
|
||||
</view>
|
||||
<text class="analyzing-title">{{analyzingTitle || '正在分析中'}}</text>
|
||||
<text class="analyzing-desc">{{analyzingTip}}</text>
|
||||
<view class="analyzing-bar">
|
||||
<view class="analyzing-bar-fill" style="width: {{progress}}%"></view>
|
||||
</view>
|
||||
<text class="analyzing-hint">这个过程可能需要30秒到1分钟,请耐心等待...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 结果显示 -->
|
||||
<block wx:if="{{!loading || error || content}}">
|
||||
|
||||
<!-- 页面顶部 Hero -->
|
||||
<view class="hero-banner {{error ? 'hero-error' : 'hero-success'}}">
|
||||
<text class="hero-icon">{{error ? '⚠️' : '📋'}}</text>
|
||||
<view class="hero-text-wrap">
|
||||
<text class="hero-title">{{error ? '生成失败' : '人才综合评估报告'}}</text>
|
||||
<text class="hero-sub">{{error ? '请检查后重新生成' : '基于测评结果 · AI 综合分析'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 异常状态 -->
|
||||
<view class="card error-card" wx:if="{{error}}">
|
||||
<text class="card-title error-title">失败原因</text>
|
||||
<text class="card-text error-message">{{error}}</text>
|
||||
<view class="action-btn-retry" bindtap="fetchResumeAnalysis">
|
||||
<text class="retry-text">点击重新生成</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 需要付费解锁 -->
|
||||
<view class="card pay-gate-card" wx:if="{{payInfo.requiresPayment && !payInfo.isPaid && !error}}">
|
||||
<text class="pay-gate-icon">🔒</text>
|
||||
<text class="pay-gate-title">报告已生成,需付费解锁</text>
|
||||
<text class="pay-gate-desc">完整的简历综合分析报告已生成\n支付后即可查看全部内容</text>
|
||||
<view class="pay-gate-amount">
|
||||
<text class="pay-amount-label">解锁价格</text>
|
||||
<text class="pay-amount-value">¥{{payInfo.amountYuan}}</text>
|
||||
</view>
|
||||
<view class="pay-gate-btn {{paying ? 'paying' : ''}}" bindtap="doPay">
|
||||
<text class="pay-gate-btn-text">{{paying ? '支付中...' : '立即支付解锁'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 成功内容:分块卡片展示 -->
|
||||
<block wx:if="{{content && sections.length > 0 && !payInfo.requiresPayment}}">
|
||||
<view class="card section-card" wx:for="{{sections}}" wx:key="index">
|
||||
<view class="section-header">
|
||||
<text class="section-icon">{{item.icon}}</text>
|
||||
<text class="section-title-text">{{item.title}}</text>
|
||||
</view>
|
||||
<view class="section-body">
|
||||
<text class="content-text">{{item.body}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 兜底显示:解析失败时显示原始内容 -->
|
||||
<view class="card" wx:if="{{content && sections.length === 0 && !payInfo.requiresPayment}}">
|
||||
<text class="card-title">综合分析综评</text>
|
||||
<text class="card-text content-text">{{content}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-section" wx:if="{{!loading || error}}">
|
||||
<button class="btn btn-primary" open-type="share" wx:if="{{!payInfo.requiresPayment && content}}">
|
||||
<text class="btn-text">分享分析报告</text>
|
||||
</button>
|
||||
<view class="btn btn-outline" bindtap="fetchResumeAnalysis" wx:if="{{!loading && content && !payInfo.requiresPayment}}">
|
||||
<text class="btn-text-outline">重新分析生成</text>
|
||||
</view>
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!--pages/result/resume.wxml - 简历综合分析结果页 -->
|
||||
<view class="container">
|
||||
<!-- 分析中状态 -->
|
||||
<view class="analyzing-modal" wx:if="{{loading && !error}}">
|
||||
<view class="analyzing-content">
|
||||
<view class="analyzing-icon">
|
||||
<view class="spinner"></view>
|
||||
</view>
|
||||
<text class="analyzing-title">{{analyzingTitle || '正在分析中'}}</text>
|
||||
<text class="analyzing-desc">{{analyzingTip}}</text>
|
||||
<view class="analyzing-bar">
|
||||
<view class="analyzing-bar-fill" style="width: {{progress}}%"></view>
|
||||
</view>
|
||||
<text class="analyzing-hint">这个过程可能需要30秒到1分钟,请耐心等待...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 结果显示 -->
|
||||
<block wx:if="{{!loading || error || content}}">
|
||||
|
||||
<!-- 页面顶部 Hero -->
|
||||
<view class="hero-banner {{error ? 'hero-error' : 'hero-success'}}">
|
||||
<text class="hero-icon">{{error ? '⚠️' : '📋'}}</text>
|
||||
<view class="hero-text-wrap">
|
||||
<text class="hero-title">{{error ? '生成失败' : '人才综合评估报告'}}</text>
|
||||
<text class="hero-sub">{{error ? '请检查后重新生成' : '基于测评结果 · 综合分析'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 异常状态 -->
|
||||
<view class="card error-card" wx:if="{{error}}">
|
||||
<text class="card-title error-title">失败原因</text>
|
||||
<text class="card-text error-message">{{error}}</text>
|
||||
<view class="action-btn-retry" bindtap="fetchResumeAnalysis">
|
||||
<text class="retry-text">点击重新生成</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 需要付费解锁 -->
|
||||
<view class="card pay-gate-card" wx:if="{{payInfo.requiresPayment && !payInfo.isPaid && !error}}">
|
||||
<text class="pay-gate-icon">🔒</text>
|
||||
<text class="pay-gate-title">报告已生成,需付费解锁</text>
|
||||
<text class="pay-gate-desc">完整的简历综合分析报告已生成\n支付后即可查看全部内容</text>
|
||||
<view class="pay-gate-amount">
|
||||
<text class="pay-amount-label">解锁价格</text>
|
||||
<text class="pay-amount-value">¥{{payInfo.amountYuan}}</text>
|
||||
</view>
|
||||
<view class="pay-gate-btn {{paying ? 'paying' : ''}}" bindtap="doPay">
|
||||
<text class="pay-gate-btn-text">{{paying ? '支付中...' : '立即支付解锁'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 成功内容:分块卡片展示 -->
|
||||
<block wx:if="{{content && sections.length > 0 && !payInfo.requiresPayment}}">
|
||||
<view class="card section-card" wx:for="{{sections}}" wx:key="index">
|
||||
<view class="section-header">
|
||||
<text class="section-icon">{{item.icon}}</text>
|
||||
<text class="section-title-text">{{item.title}}</text>
|
||||
</view>
|
||||
<view class="section-body">
|
||||
<text class="content-text">{{item.body}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 兜底显示:解析失败时显示原始内容 -->
|
||||
<view class="card" wx:if="{{content && sections.length === 0 && !payInfo.requiresPayment}}">
|
||||
<text class="card-title">综合分析综评</text>
|
||||
<text class="card-text content-text">{{content}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-section" wx:if="{{!loading || error}}">
|
||||
<button class="btn btn-primary" open-type="share" wx:if="{{!payInfo.requiresPayment && content}}">
|
||||
<text class="btn-text">分享分析报告</text>
|
||||
</button>
|
||||
<view class="btn btn-outline" bindtap="fetchResumeAnalysis" wx:if="{{!loading && content && !payInfo.requiresPayment}}">
|
||||
<text class="btn-text-outline">重新分析生成</text>
|
||||
</view>
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -1,348 +1,386 @@
|
||||
/* pages/result/resume.wxss - 简历综合分析结果页 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 0 24rpx 60rpx;
|
||||
}
|
||||
|
||||
/* ========== 分析中模态框 ========== */
|
||||
.analyzing-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.analyzing-content {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 80rpx 60rpx;
|
||||
width: 560rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.analyzing-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
margin-bottom: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border: 8rpx solid #e0e0e0;
|
||||
border-top-color: #e63946;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.analyzing-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.analyzing-desc {
|
||||
font-size: 28rpx;
|
||||
color: #e63946;
|
||||
margin-bottom: 24rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.analyzing-bar {
|
||||
width: 400rpx;
|
||||
height: 12rpx;
|
||||
background: #e5e5e5;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.analyzing-bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #e63946, #ff6b9d);
|
||||
border-radius: 6rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.analyzing-hint {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ========== 顶部 Hero 区域 ========== */
|
||||
.hero-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 20rpx;
|
||||
padding: 32rpx 28rpx;
|
||||
margin: 24rpx 0;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.hero-success {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #3a3a5c 100%);
|
||||
}
|
||||
|
||||
.hero-error {
|
||||
background: linear-gradient(135deg, #c62828 0%, #e53935 100%);
|
||||
}
|
||||
|
||||
.hero-icon {
|
||||
font-size: 64rpx;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hero-text-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ========== 通用卡片 ========== */
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
border-left: 8rpx solid #e63946;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.8;
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
color: #f44336;
|
||||
border-left-color: #f44336;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #666;
|
||||
background-color: #f9f9f9;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.action-btn-retry {
|
||||
margin-top: 32rpx;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 6rpx 16rpx rgba(230, 57, 70, 0.25);
|
||||
}
|
||||
|
||||
.retry-text {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ========== 分块卡片 ========== */
|
||||
.section-card {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx 20rpx;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #fff 100%);
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 16rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.section-title-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
flex: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.section-body {
|
||||
padding: 24rpx 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.section-body .content-text {
|
||||
font-size: 27rpx;
|
||||
color: #444;
|
||||
line-height: 1.9;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
|
||||
/* ========== 操作按钮 ========== */
|
||||
.action-section {
|
||||
margin-top: 16rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%!important;
|
||||
height: 100rpx;
|
||||
min-height: 100rpx;
|
||||
border-radius: 28rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0;
|
||||
border: none;
|
||||
line-height: 100rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: #fff;
|
||||
border: 2rpx solid #e63946;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-text-outline {
|
||||
font-size: 30rpx;
|
||||
color: #e63946;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== 支付解锁卡片 ========== */
|
||||
.pay-gate-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx 40rpx 48rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pay-gate-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.pay-gate-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.pay-gate-desc {
|
||||
font-size: 28rpx;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.pay-gate-amount {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
background: #fff5f5;
|
||||
border-radius: 20rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.pay-amount-label {
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.pay-amount-value {
|
||||
font-size: 52rpx;
|
||||
font-weight: 800;
|
||||
color: #e63946;
|
||||
}
|
||||
|
||||
.pay-gate-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border-radius: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.35);
|
||||
}
|
||||
|
||||
.pay-gate-btn.paying {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.pay-gate-btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
/* pages/result/resume.wxss - 简历综合分析结果页 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 0 24rpx 60rpx;
|
||||
}
|
||||
|
||||
/* ========== 分析中模态框 ========== */
|
||||
.analyzing-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.analyzing-content {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 80rpx 60rpx;
|
||||
width: 560rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.analyzing-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
margin-bottom: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border: 8rpx solid #e0e0e0;
|
||||
border-top-color: #e63946;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.analyzing-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.analyzing-desc {
|
||||
font-size: 28rpx;
|
||||
color: #e63946;
|
||||
margin-bottom: 24rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.analyzing-bar {
|
||||
width: 400rpx;
|
||||
height: 12rpx;
|
||||
background: #e5e5e5;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.analyzing-bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #e63946, #ff6b9d);
|
||||
border-radius: 6rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.analyzing-hint {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ========== 顶部 Hero 区域 ========== */
|
||||
.hero-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 20rpx;
|
||||
padding: 32rpx 28rpx;
|
||||
margin: 24rpx 0;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.hero-success {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #3a3a5c 100%);
|
||||
}
|
||||
|
||||
.hero-error {
|
||||
background: linear-gradient(135deg, #c62828 0%, #e53935 100%);
|
||||
}
|
||||
|
||||
.hero-icon {
|
||||
font-size: 64rpx;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hero-text-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ========== 通用卡片 ========== */
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
border-left: 8rpx solid #e63946;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.8;
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
color: #f44336;
|
||||
border-left-color: #f44336;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #666;
|
||||
background-color: #f9f9f9;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.action-btn-retry {
|
||||
margin-top: 32rpx;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 6rpx 16rpx rgba(230, 57, 70, 0.25);
|
||||
}
|
||||
|
||||
.retry-text {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ========== 分块卡片 ========== */
|
||||
.section-card {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx 20rpx;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #fff 100%);
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 16rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.section-title-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
flex: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.section-body {
|
||||
padding: 24rpx 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.section-body .content-text {
|
||||
font-size: 27rpx;
|
||||
color: #444;
|
||||
line-height: 1.9;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
|
||||
/* ========== 操作按钮 ========== */
|
||||
.action-section {
|
||||
margin-top: 16rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%!important;
|
||||
height: 100rpx;
|
||||
min-height: 100rpx;
|
||||
border-radius: 28rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0;
|
||||
border: none;
|
||||
line-height: 100rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: #fff;
|
||||
border: 2rpx solid #e63946;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-text-outline {
|
||||
font-size: 30rpx;
|
||||
color: #e63946;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== 支付解锁卡片 ========== */
|
||||
.pay-gate-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx 40rpx 48rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pay-gate-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.pay-gate-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.pay-gate-desc {
|
||||
font-size: 28rpx;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.pay-gate-amount {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
background: #fff5f5;
|
||||
border-radius: 20rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.pay-amount-label {
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.pay-amount-value {
|
||||
font-size: 52rpx;
|
||||
font-weight: 800;
|
||||
color: #e63946;
|
||||
}
|
||||
|
||||
.pay-gate-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border-radius: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.35);
|
||||
}
|
||||
|
||||
.pay-gate-btn.paying {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.pay-gate-btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* AI 生成内容标识横幅(监管合规,始终显示) */
|
||||
.ai-disclosure-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
margin: 24rpx 0;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #f0f4ff 0%, #e8ecf8 100%);
|
||||
border: 1rpx solid #c7d2fe;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.ai-disclosure-banner.bottom {
|
||||
margin-top: 16rpx;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ai-disclosure-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
border-radius: 8rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ai-disclosure-text {
|
||||
font-size: 22rpx;
|
||||
color: #4338ca;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
// pages/test-select/index.js - 人脸测试后的详情性格测试入口(MBTI / PDP / DISC)
|
||||
Page({
|
||||
data: {},
|
||||
|
||||
onLoad() {},
|
||||
|
||||
// 进入 MBTI 测试
|
||||
goMBTI() {
|
||||
wx.navigateTo({ url: '/pages/test/mbti' })
|
||||
},
|
||||
|
||||
// 进入 PDP 测试
|
||||
goPDP() {
|
||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
||||
},
|
||||
|
||||
// 进入 DISC 测试
|
||||
goDISC() {
|
||||
wx.navigateTo({ url: '/pages/test/disc' })
|
||||
}
|
||||
})
|
||||
// pages/test-select/index.js - 人脸测试后的详情性格测试入口(MBTI / PDP / DISC)
|
||||
Page({
|
||||
data: {},
|
||||
|
||||
onLoad() {},
|
||||
|
||||
// 进入 MBTI 测试
|
||||
goMBTI() {
|
||||
wx.navigateTo({ url: '/pages/test/mbti' })
|
||||
},
|
||||
|
||||
// 进入 PDP 测试
|
||||
goPDP() {
|
||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
||||
},
|
||||
|
||||
// 进入 DISC 测试
|
||||
goDISC() {
|
||||
wx.navigateTo({ url: '/pages/test/disc' })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "详细性格测试",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "详细性格测试",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
<!--pages/test-select/index.wxml - 详细性格测试入口(MBTI / PDP / DISC)-->
|
||||
<view class="container">
|
||||
<view class="card intro-card">
|
||||
<text class="intro-title">选择一项详细性格测试</text>
|
||||
<text class="intro-desc">完成问卷即可获得对应维度的专业报告,与面相分析结果互补</text>
|
||||
</view>
|
||||
|
||||
<view class="card entry-card" bindtap="goMBTI">
|
||||
<view class="entry-icon-wrap mbti">
|
||||
<text class="entry-emoji">🧠</text>
|
||||
</view>
|
||||
<view class="entry-content">
|
||||
<text class="entry-name">MBTI 性格测试</text>
|
||||
<text class="entry-brief">16型人格 · 发现你的认知与决策风格</text>
|
||||
</view>
|
||||
<text class="entry-arrow">→</text>
|
||||
</view>
|
||||
|
||||
<view class="card entry-card" bindtap="goPDP">
|
||||
<view class="entry-icon-wrap pdp">
|
||||
<text class="entry-emoji">🦁</text>
|
||||
</view>
|
||||
<view class="entry-content">
|
||||
<text class="entry-name">PDP 行为偏好测试</text>
|
||||
<text class="entry-brief">老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格</text>
|
||||
</view>
|
||||
<text class="entry-arrow">→</text>
|
||||
</view>
|
||||
|
||||
<view class="card entry-card" bindtap="goDISC">
|
||||
<view class="entry-icon-wrap disc">
|
||||
<text class="entry-emoji">📊</text>
|
||||
</view>
|
||||
<view class="entry-content">
|
||||
<text class="entry-name">DISC 性格测试</text>
|
||||
<text class="entry-brief">D/I/S/C 四维 · 沟通与行为倾向</text>
|
||||
</view>
|
||||
<text class="entry-arrow">→</text>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/test-select/index.wxml - 详细性格测试入口(MBTI / PDP / DISC)-->
|
||||
<view class="container">
|
||||
<view class="card intro-card">
|
||||
<text class="intro-title">选择一项详细性格测试</text>
|
||||
<text class="intro-desc">完成问卷即可获得对应维度的专业性格报告</text>
|
||||
</view>
|
||||
|
||||
<view class="card entry-card" bindtap="goMBTI">
|
||||
<view class="entry-icon-wrap mbti">
|
||||
<text class="entry-emoji">🧠</text>
|
||||
</view>
|
||||
<view class="entry-content">
|
||||
<text class="entry-name">MBTI 性格测试</text>
|
||||
<text class="entry-brief">16型人格 · 发现你的认知与决策风格</text>
|
||||
</view>
|
||||
<text class="entry-arrow">→</text>
|
||||
</view>
|
||||
|
||||
<view class="card entry-card" bindtap="goPDP">
|
||||
<view class="entry-icon-wrap pdp">
|
||||
<text class="entry-emoji">🦁</text>
|
||||
</view>
|
||||
<view class="entry-content">
|
||||
<text class="entry-name">PDP 行为偏好测试</text>
|
||||
<text class="entry-brief">老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格</text>
|
||||
</view>
|
||||
<text class="entry-arrow">→</text>
|
||||
</view>
|
||||
|
||||
<view class="card entry-card" bindtap="goDISC">
|
||||
<view class="entry-icon-wrap disc">
|
||||
<text class="entry-emoji">📊</text>
|
||||
</view>
|
||||
<view class="entry-content">
|
||||
<text class="entry-name">DISC 性格测试</text>
|
||||
<text class="entry-brief">D/I/S/C 四维 · 沟通与行为倾向</text>
|
||||
</view>
|
||||
<text class="entry-arrow">→</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
/* pages/test-select/index.wxss - 详细性格测试入口,与现有 UI 风格一致 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #fff5f5 0%, #f5f5f5 50%, #ffffff 100%);
|
||||
padding: 24rpx 24rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 顶部说明 */
|
||||
.intro-card {
|
||||
text-align: center;
|
||||
padding: 40rpx 32rpx;
|
||||
}
|
||||
|
||||
.intro-title {
|
||||
display: block;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.intro-desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 入口卡片(与 result 页 next-btn 同色系) */
|
||||
.entry-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx;
|
||||
transition: opacity 0.2s;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid transparent;
|
||||
}
|
||||
|
||||
.entry-card:active {
|
||||
opacity: 0.9;
|
||||
background: linear-gradient(135deg, rgba(230, 57, 70, 0.04) 0%, rgba(255, 107, 157, 0.04) 100%);
|
||||
border-color: rgba(230, 57, 70, 0.15);
|
||||
}
|
||||
|
||||
.entry-icon-wrap {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.entry-icon-wrap.mbti {
|
||||
background: linear-gradient(135deg, rgba(124, 58, 237, 0.15) 0%, rgba(139, 92, 246, 0.2) 100%);
|
||||
}
|
||||
|
||||
.entry-icon-wrap.pdp {
|
||||
background: linear-gradient(135deg, rgba(230, 57, 70, 0.12) 0%, rgba(255, 107, 157, 0.15) 100%);
|
||||
}
|
||||
|
||||
.entry-icon-wrap.disc {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(96, 165, 250, 0.18) 100%);
|
||||
}
|
||||
|
||||
.entry-emoji {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
|
||||
.entry-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.entry-name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.entry-brief {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.entry-arrow {
|
||||
font-size: 32rpx;
|
||||
color: #e63946;
|
||||
font-weight: 600;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* pages/test-select/index.wxss - 详细性格测试入口,与现有 UI 风格一致 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #fff5f5 0%, #f5f5f5 50%, #ffffff 100%);
|
||||
padding: 24rpx 24rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 顶部说明 */
|
||||
.intro-card {
|
||||
text-align: center;
|
||||
padding: 40rpx 32rpx;
|
||||
}
|
||||
|
||||
.intro-title {
|
||||
display: block;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.intro-desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 入口卡片(与 result 页 next-btn 同色系) */
|
||||
.entry-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx;
|
||||
transition: opacity 0.2s;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid transparent;
|
||||
}
|
||||
|
||||
.entry-card:active {
|
||||
opacity: 0.9;
|
||||
background: linear-gradient(135deg, rgba(230, 57, 70, 0.04) 0%, rgba(255, 107, 157, 0.04) 100%);
|
||||
border-color: rgba(230, 57, 70, 0.15);
|
||||
}
|
||||
|
||||
.entry-icon-wrap {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.entry-icon-wrap.mbti {
|
||||
background: linear-gradient(135deg, rgba(124, 58, 237, 0.15) 0%, rgba(139, 92, 246, 0.2) 100%);
|
||||
}
|
||||
|
||||
.entry-icon-wrap.pdp {
|
||||
background: linear-gradient(135deg, rgba(230, 57, 70, 0.12) 0%, rgba(255, 107, 157, 0.15) 100%);
|
||||
}
|
||||
|
||||
.entry-icon-wrap.disc {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(96, 165, 250, 0.18) 100%);
|
||||
}
|
||||
|
||||
.entry-emoji {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
|
||||
.entry-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.entry-name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.entry-brief {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.entry-arrow {
|
||||
font-size: 32rpx;
|
||||
color: #e63946;
|
||||
font-weight: 600;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1,134 +1,134 @@
|
||||
// pages/test/disc.js
|
||||
const { discQuestions, shuffleQuestions } = require('../../utils/questions')
|
||||
const { discDescriptions } = require('../../utils/descriptions')
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
questions: [],
|
||||
currentIndex: 0,
|
||||
currentQuestion: null,
|
||||
answers: {},
|
||||
selectedAnswer: null,
|
||||
total: discQuestions.length,
|
||||
answeredCount: 0,
|
||||
progress: 0,
|
||||
timeRemaining: 15 * 60,
|
||||
formatTime: '15:00',
|
||||
isSubmitting: false
|
||||
},
|
||||
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
const questions = shuffleQuestions(discQuestions)
|
||||
this.setData({ questions, currentQuestion: questions[0] })
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
this.timer = setInterval(() => {
|
||||
let time = this.data.timeRemaining - 1
|
||||
if (time <= 0) {
|
||||
clearInterval(this.timer)
|
||||
this.submitTest()
|
||||
return
|
||||
}
|
||||
const minutes = Math.floor(time / 60)
|
||||
const seconds = time % 60
|
||||
this.setData({
|
||||
timeRemaining: time,
|
||||
formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
selectAnswer(e) {
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
let answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers: answers,
|
||||
answeredCount: Object.keys(answers).length,
|
||||
progress: (Object.keys(answers).length / this.data.total) * 100
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
this.nextQuestion()
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
|
||||
prevQuestion() {
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
nextQuestion() {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
const newIndex = this.data.currentIndex + 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
submitTest() {
|
||||
if (this.data.isSubmitting) return
|
||||
this.setData({ isSubmitting: true })
|
||||
|
||||
const scores = { D: 0, I: 0, S: 0, C: 0 }
|
||||
Object.values(this.data.answers).forEach(value => {
|
||||
if (scores.hasOwnProperty(value)) scores[value]++
|
||||
})
|
||||
|
||||
const total = Object.values(scores).reduce((sum, v) => sum + v, 0)
|
||||
const percentages = {
|
||||
D: Math.round((scores.D / total) * 100),
|
||||
I: Math.round((scores.I / total) * 100),
|
||||
S: Math.round((scores.S / total) * 100),
|
||||
C: Math.round((scores.C / total) * 100)
|
||||
}
|
||||
|
||||
const dominantType = Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0]
|
||||
const secondaryType = Object.entries(scores).sort((a, b) => b[1] - a[1])[1][0]
|
||||
|
||||
const resultData = {
|
||||
scores,
|
||||
percentages,
|
||||
dominantType,
|
||||
secondaryType,
|
||||
description: discDescriptions[dominantType],
|
||||
testDuration: 15 * 60 - this.data.timeRemaining,
|
||||
completedAt: new Date().toISOString(),
|
||||
// 便于后端留存完整答题过程
|
||||
answers: this.data.answers
|
||||
}
|
||||
|
||||
// 本地缓存 + 全局缓存
|
||||
wx.setStorageSync('discResult', resultData)
|
||||
if (app && typeof app.saveTestResult === 'function') {
|
||||
app.saveTestResult('disc', resultData)
|
||||
}
|
||||
|
||||
wx.redirectTo({ url: '/pages/result/disc' })
|
||||
}
|
||||
})
|
||||
// pages/test/disc.js
|
||||
const { discQuestions, shuffleQuestions } = require('../../utils/questions')
|
||||
const { discDescriptions } = require('../../utils/descriptions')
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
questions: [],
|
||||
currentIndex: 0,
|
||||
currentQuestion: null,
|
||||
answers: {},
|
||||
selectedAnswer: null,
|
||||
total: discQuestions.length,
|
||||
answeredCount: 0,
|
||||
progress: 0,
|
||||
timeRemaining: 15 * 60,
|
||||
formatTime: '15:00',
|
||||
isSubmitting: false
|
||||
},
|
||||
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
const questions = shuffleQuestions(discQuestions)
|
||||
this.setData({ questions, currentQuestion: questions[0] })
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
this.timer = setInterval(() => {
|
||||
let time = this.data.timeRemaining - 1
|
||||
if (time <= 0) {
|
||||
clearInterval(this.timer)
|
||||
this.submitTest()
|
||||
return
|
||||
}
|
||||
const minutes = Math.floor(time / 60)
|
||||
const seconds = time % 60
|
||||
this.setData({
|
||||
timeRemaining: time,
|
||||
formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
selectAnswer(e) {
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
let answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers: answers,
|
||||
answeredCount: Object.keys(answers).length,
|
||||
progress: (Object.keys(answers).length / this.data.total) * 100
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
this.nextQuestion()
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
|
||||
prevQuestion() {
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
nextQuestion() {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
const newIndex = this.data.currentIndex + 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
submitTest() {
|
||||
if (this.data.isSubmitting) return
|
||||
this.setData({ isSubmitting: true })
|
||||
|
||||
const scores = { D: 0, I: 0, S: 0, C: 0 }
|
||||
Object.values(this.data.answers).forEach(value => {
|
||||
if (scores.hasOwnProperty(value)) scores[value]++
|
||||
})
|
||||
|
||||
const total = Object.values(scores).reduce((sum, v) => sum + v, 0)
|
||||
const percentages = {
|
||||
D: Math.round((scores.D / total) * 100),
|
||||
I: Math.round((scores.I / total) * 100),
|
||||
S: Math.round((scores.S / total) * 100),
|
||||
C: Math.round((scores.C / total) * 100)
|
||||
}
|
||||
|
||||
const dominantType = Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0]
|
||||
const secondaryType = Object.entries(scores).sort((a, b) => b[1] - a[1])[1][0]
|
||||
|
||||
const resultData = {
|
||||
scores,
|
||||
percentages,
|
||||
dominantType,
|
||||
secondaryType,
|
||||
description: discDescriptions[dominantType],
|
||||
testDuration: 15 * 60 - this.data.timeRemaining,
|
||||
completedAt: new Date().toISOString(),
|
||||
// 便于后端留存完整答题过程
|
||||
answers: this.data.answers
|
||||
}
|
||||
|
||||
// 本地缓存 + 全局缓存
|
||||
wx.setStorageSync('discResult', resultData)
|
||||
if (app && typeof app.saveTestResult === 'function') {
|
||||
app.saveTestResult('disc', resultData)
|
||||
}
|
||||
|
||||
wx.redirectTo({ url: '/pages/result/disc' })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "DISC测试",
|
||||
"navigationBarBackgroundColor": "#3B82F6",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "DISC测试",
|
||||
"navigationBarBackgroundColor": "#3B82F6",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
<!--pages/test/disc.wxml - DISC测试页面(按旧版模板重构)-->
|
||||
<view class="test-page">
|
||||
<view class="progress-section">
|
||||
<view class="progress-info">
|
||||
<text class="question-count">问题 {{currentIndex + 1}}/{{total}}</text>
|
||||
<text class="time-remaining">剩余时间: {{formatTime}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar" style="width: {{progress}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-area">
|
||||
<view class="question-card">
|
||||
<text class="question-text">{{currentQuestion.question}}</text>
|
||||
<view class="options-container">
|
||||
<view
|
||||
class="option-item {{selectedAnswer === option.value ? 'selected' : ''}}"
|
||||
wx:for="{{currentQuestion.options}}"
|
||||
wx:for-item="option"
|
||||
wx:key="value"
|
||||
bindtap="selectAnswer"
|
||||
data-value="{{option.value}}"
|
||||
>
|
||||
<view class="radio-button {{selectedAnswer === option.value ? 'checked' : ''}}">
|
||||
<view wx:if="{{selectedAnswer === option.value}}" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="option-text">{{option.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-buttons">
|
||||
<view class="nav-button secondary {{currentIndex === 0 ? 'disabled' : ''}}" bindtap="prevQuestion">
|
||||
<text class="button-text">上一题</text>
|
||||
</view>
|
||||
<view class="nav-button secondary {{currentIndex === total - 1 ? 'disabled' : ''}}" bindtap="nextQuestion">
|
||||
<text class="button-text">跳过</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-wrap" wx:if="{{answeredCount === total}}">
|
||||
<view class="submit-button" bindtap="submitTest">
|
||||
<text class="submit-text">{{isSubmitting ? '计算中...' : '完成测试,查看结果'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/test/disc.wxml - DISC测试页面(按旧版模板重构)-->
|
||||
<view class="test-page">
|
||||
<view class="progress-section">
|
||||
<view class="progress-info">
|
||||
<text class="question-count">问题 {{currentIndex + 1}}/{{total}}</text>
|
||||
<text class="time-remaining">剩余时间: {{formatTime}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar" style="width: {{progress}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-area">
|
||||
<view class="question-card">
|
||||
<text class="question-text">{{currentQuestion.question}}</text>
|
||||
<view class="options-container">
|
||||
<view
|
||||
class="option-item {{selectedAnswer === option.value ? 'selected' : ''}}"
|
||||
wx:for="{{currentQuestion.options}}"
|
||||
wx:for-item="option"
|
||||
wx:key="value"
|
||||
bindtap="selectAnswer"
|
||||
data-value="{{option.value}}"
|
||||
>
|
||||
<view class="radio-button {{selectedAnswer === option.value ? 'checked' : ''}}">
|
||||
<view wx:if="{{selectedAnswer === option.value}}" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="option-text">{{option.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-buttons">
|
||||
<view class="nav-button secondary {{currentIndex === 0 ? 'disabled' : ''}}" bindtap="prevQuestion">
|
||||
<text class="button-text">上一题</text>
|
||||
</view>
|
||||
<view class="nav-button secondary {{currentIndex === total - 1 ? 'disabled' : ''}}" bindtap="nextQuestion">
|
||||
<text class="button-text">跳过</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-wrap" wx:if="{{answeredCount === total}}">
|
||||
<view class="submit-button" bindtap="submitTest">
|
||||
<text class="submit-text">{{isSubmitting ? '计算中...' : '完成测试,查看结果'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,172 +1,172 @@
|
||||
/* pages/test/disc.wxss - 按旧版模板重构 */
|
||||
.test-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.question-count {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.time-remaining {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 8rpx;
|
||||
background-color: #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%);
|
||||
border-radius: 8rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.question-text {
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
background: #fff;
|
||||
border: 2rpx solid #e5e5e5;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.option-item.selected {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #d1d5db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.radio-button.checked {
|
||||
background-color: #3B82F6;
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
.radio-inner {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 32rpx;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
flex: 1;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-button.secondary {
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #3B82F6;
|
||||
}
|
||||
|
||||
.nav-button.secondary .button-text {
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
.nav-button.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-wrap {
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%);
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
/* pages/test/disc.wxss - 按旧版模板重构 */
|
||||
.test-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.question-count {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.time-remaining {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 8rpx;
|
||||
background-color: #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%);
|
||||
border-radius: 8rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.question-text {
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
background: #fff;
|
||||
border: 2rpx solid #e5e5e5;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.option-item.selected {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #d1d5db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.radio-button.checked {
|
||||
background-color: #3B82F6;
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
.radio-inner {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 32rpx;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
flex: 1;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-button.secondary {
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #3B82F6;
|
||||
}
|
||||
|
||||
.nav-button.secondary .button-text {
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
.nav-button.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-wrap {
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%);
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -1,180 +1,180 @@
|
||||
// pages/test/mbti.js - MBTI测试页面逻辑
|
||||
const { mbtiQuestions, shuffleQuestions } = require('../../utils/questions')
|
||||
const { mbtiDescriptions } = require('../../utils/descriptions')
|
||||
const payment = require('../../utils/payment')
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
questions: [],
|
||||
currentIndex: 0,
|
||||
currentQuestion: null,
|
||||
answers: {},
|
||||
selectedAnswer: null,
|
||||
total: mbtiQuestions.length,
|
||||
answeredCount: 0,
|
||||
progress: 0,
|
||||
timeRemaining: 30 * 60, // 30分钟
|
||||
formatTime: '30:00',
|
||||
isSubmitting: false,
|
||||
canAccess: false
|
||||
},
|
||||
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
const questions = shuffleQuestions(mbtiQuestions)
|
||||
this.setData({
|
||||
questions,
|
||||
currentQuestion: questions[0],
|
||||
canAccess: true
|
||||
})
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
// 检查访问权限
|
||||
checkAccess() {
|
||||
// 当前策略:所有测试免费开放,直接允许访问
|
||||
// 若后续恢复收费,可重新启用 payment.canTakeTest 等校验逻辑
|
||||
return true
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
},
|
||||
|
||||
// 启动计时器
|
||||
startTimer() {
|
||||
this.timer = setInterval(() => {
|
||||
let time = this.data.timeRemaining - 1
|
||||
if (time <= 0) {
|
||||
clearInterval(this.timer)
|
||||
this.submitTest()
|
||||
return
|
||||
}
|
||||
const minutes = Math.floor(time / 60)
|
||||
const seconds = time % 60
|
||||
this.setData({
|
||||
timeRemaining: time,
|
||||
formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 选择答案
|
||||
selectAnswer(e) {
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
|
||||
let answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers: answers,
|
||||
answeredCount: Object.keys(answers).length,
|
||||
progress: (Object.keys(answers).length / this.data.total) * 100
|
||||
})
|
||||
|
||||
// 自动跳转下一题
|
||||
setTimeout(() => {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
this.nextQuestion()
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
|
||||
// 上一题
|
||||
prevQuestion() {
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 下一题
|
||||
nextQuestion() {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
const newIndex = this.data.currentIndex + 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 提交测试
|
||||
submitTest() {
|
||||
if (this.data.isSubmitting) return
|
||||
this.setData({ isSubmitting: true })
|
||||
|
||||
const result = this.calculateResult()
|
||||
|
||||
// 保存结果
|
||||
const resultData = {
|
||||
...result,
|
||||
testDuration: 30 * 60 - this.data.timeRemaining,
|
||||
completedAt: new Date().toISOString(),
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
wx.setStorageSync('mbtiResult', resultData)
|
||||
app.saveTestResult('mbti', resultData)
|
||||
|
||||
// 跳转到结果页
|
||||
wx.redirectTo({
|
||||
url: '/pages/result/mbti'
|
||||
})
|
||||
},
|
||||
|
||||
// 计算MBTI结果
|
||||
calculateResult() {
|
||||
const answers = this.data.answers
|
||||
const scores = { E: 0, I: 0, S: 0, N: 0, T: 0, F: 0, J: 0, P: 0 }
|
||||
|
||||
// 统计各维度得分
|
||||
Object.values(answers).forEach(value => {
|
||||
if (scores.hasOwnProperty(value)) {
|
||||
scores[value]++
|
||||
}
|
||||
})
|
||||
|
||||
// 确定MBTI类型
|
||||
const mbtiType = [
|
||||
scores.E >= scores.I ? 'E' : 'I',
|
||||
scores.S >= scores.N ? 'S' : 'N',
|
||||
scores.T >= scores.F ? 'T' : 'F',
|
||||
scores.J >= scores.P ? 'J' : 'P'
|
||||
].join('')
|
||||
|
||||
// 计算各维度百分比
|
||||
const dimensionScores = {
|
||||
EI: { E: scores.E, I: scores.I, dominant: scores.E >= scores.I ? 'E' : 'I', percentage: Math.round((Math.max(scores.E, scores.I) / (scores.E + scores.I)) * 100) },
|
||||
SN: { S: scores.S, N: scores.N, dominant: scores.S >= scores.N ? 'S' : 'N', percentage: Math.round((Math.max(scores.S, scores.N) / (scores.S + scores.N)) * 100) },
|
||||
TF: { T: scores.T, F: scores.F, dominant: scores.T >= scores.F ? 'T' : 'F', percentage: Math.round((Math.max(scores.T, scores.F) / (scores.T + scores.F)) * 100) },
|
||||
JP: { J: scores.J, P: scores.P, dominant: scores.J >= scores.P ? 'J' : 'P', percentage: Math.round((Math.max(scores.J, scores.P) / (scores.J + scores.P)) * 100) }
|
||||
}
|
||||
|
||||
// 计算置信度
|
||||
const confidence = Math.round(
|
||||
(dimensionScores.EI.percentage + dimensionScores.SN.percentage +
|
||||
dimensionScores.TF.percentage + dimensionScores.JP.percentage) / 4
|
||||
)
|
||||
|
||||
return {
|
||||
mbtiType,
|
||||
scores,
|
||||
dimensionScores,
|
||||
confidence,
|
||||
description: mbtiDescriptions[mbtiType] || {}
|
||||
}
|
||||
}
|
||||
})
|
||||
// pages/test/mbti.js - MBTI测试页面逻辑
|
||||
const { mbtiQuestions, shuffleQuestions } = require('../../utils/questions')
|
||||
const { mbtiDescriptions } = require('../../utils/descriptions')
|
||||
const payment = require('../../utils/payment')
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
questions: [],
|
||||
currentIndex: 0,
|
||||
currentQuestion: null,
|
||||
answers: {},
|
||||
selectedAnswer: null,
|
||||
total: mbtiQuestions.length,
|
||||
answeredCount: 0,
|
||||
progress: 0,
|
||||
timeRemaining: 30 * 60, // 30分钟
|
||||
formatTime: '30:00',
|
||||
isSubmitting: false,
|
||||
canAccess: false
|
||||
},
|
||||
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
const questions = shuffleQuestions(mbtiQuestions)
|
||||
this.setData({
|
||||
questions,
|
||||
currentQuestion: questions[0],
|
||||
canAccess: true
|
||||
})
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
// 检查访问权限
|
||||
checkAccess() {
|
||||
// 当前策略:所有测试免费开放,直接允许访问
|
||||
// 若后续恢复收费,可重新启用 payment.canTakeTest 等校验逻辑
|
||||
return true
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
},
|
||||
|
||||
// 启动计时器
|
||||
startTimer() {
|
||||
this.timer = setInterval(() => {
|
||||
let time = this.data.timeRemaining - 1
|
||||
if (time <= 0) {
|
||||
clearInterval(this.timer)
|
||||
this.submitTest()
|
||||
return
|
||||
}
|
||||
const minutes = Math.floor(time / 60)
|
||||
const seconds = time % 60
|
||||
this.setData({
|
||||
timeRemaining: time,
|
||||
formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 选择答案
|
||||
selectAnswer(e) {
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
|
||||
let answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers: answers,
|
||||
answeredCount: Object.keys(answers).length,
|
||||
progress: (Object.keys(answers).length / this.data.total) * 100
|
||||
})
|
||||
|
||||
// 自动跳转下一题
|
||||
setTimeout(() => {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
this.nextQuestion()
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
|
||||
// 上一题
|
||||
prevQuestion() {
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 下一题
|
||||
nextQuestion() {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
const newIndex = this.data.currentIndex + 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 提交测试
|
||||
submitTest() {
|
||||
if (this.data.isSubmitting) return
|
||||
this.setData({ isSubmitting: true })
|
||||
|
||||
const result = this.calculateResult()
|
||||
|
||||
// 保存结果
|
||||
const resultData = {
|
||||
...result,
|
||||
testDuration: 30 * 60 - this.data.timeRemaining,
|
||||
completedAt: new Date().toISOString(),
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
wx.setStorageSync('mbtiResult', resultData)
|
||||
app.saveTestResult('mbti', resultData)
|
||||
|
||||
// 跳转到结果页
|
||||
wx.redirectTo({
|
||||
url: '/pages/result/mbti'
|
||||
})
|
||||
},
|
||||
|
||||
// 计算MBTI结果
|
||||
calculateResult() {
|
||||
const answers = this.data.answers
|
||||
const scores = { E: 0, I: 0, S: 0, N: 0, T: 0, F: 0, J: 0, P: 0 }
|
||||
|
||||
// 统计各维度得分
|
||||
Object.values(answers).forEach(value => {
|
||||
if (scores.hasOwnProperty(value)) {
|
||||
scores[value]++
|
||||
}
|
||||
})
|
||||
|
||||
// 确定MBTI类型
|
||||
const mbtiType = [
|
||||
scores.E >= scores.I ? 'E' : 'I',
|
||||
scores.S >= scores.N ? 'S' : 'N',
|
||||
scores.T >= scores.F ? 'T' : 'F',
|
||||
scores.J >= scores.P ? 'J' : 'P'
|
||||
].join('')
|
||||
|
||||
// 计算各维度百分比
|
||||
const dimensionScores = {
|
||||
EI: { E: scores.E, I: scores.I, dominant: scores.E >= scores.I ? 'E' : 'I', percentage: Math.round((Math.max(scores.E, scores.I) / (scores.E + scores.I)) * 100) },
|
||||
SN: { S: scores.S, N: scores.N, dominant: scores.S >= scores.N ? 'S' : 'N', percentage: Math.round((Math.max(scores.S, scores.N) / (scores.S + scores.N)) * 100) },
|
||||
TF: { T: scores.T, F: scores.F, dominant: scores.T >= scores.F ? 'T' : 'F', percentage: Math.round((Math.max(scores.T, scores.F) / (scores.T + scores.F)) * 100) },
|
||||
JP: { J: scores.J, P: scores.P, dominant: scores.J >= scores.P ? 'J' : 'P', percentage: Math.round((Math.max(scores.J, scores.P) / (scores.J + scores.P)) * 100) }
|
||||
}
|
||||
|
||||
// 计算置信度
|
||||
const confidence = Math.round(
|
||||
(dimensionScores.EI.percentage + dimensionScores.SN.percentage +
|
||||
dimensionScores.TF.percentage + dimensionScores.JP.percentage) / 4
|
||||
)
|
||||
|
||||
return {
|
||||
mbtiType,
|
||||
scores,
|
||||
dimensionScores,
|
||||
confidence,
|
||||
description: mbtiDescriptions[mbtiType] || {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "MBTI测试",
|
||||
"navigationBarBackgroundColor": "#FF6B8A",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "MBTI测试",
|
||||
"navigationBarBackgroundColor": "#FF6B8A",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
<!--pages/test/mbti.wxml - MBTI测试页面(按旧版模板重构)-->
|
||||
<view class="test-page">
|
||||
<view class="progress-section">
|
||||
<view class="progress-info">
|
||||
<text class="question-count">问题 {{currentIndex + 1}}/{{total}}</text>
|
||||
<text class="time-remaining">剩余时间: {{formatTime}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar" style="width: {{progress}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-area">
|
||||
<view class="question-card">
|
||||
<text class="question-text">{{currentQuestion.question}}</text>
|
||||
<view class="options-container">
|
||||
<view
|
||||
class="option-item {{selectedAnswer === option.value ? 'selected' : ''}}"
|
||||
wx:for="{{currentQuestion.options}}"
|
||||
wx:for-item="option"
|
||||
wx:key="value"
|
||||
bindtap="selectAnswer"
|
||||
data-value="{{option.value}}"
|
||||
>
|
||||
<view class="radio-button {{selectedAnswer === option.value ? 'checked' : ''}}">
|
||||
<view wx:if="{{selectedAnswer === option.value}}" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="option-text">{{option.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-buttons">
|
||||
<view class="nav-button secondary {{currentIndex === 0 ? 'disabled' : ''}}" bindtap="prevQuestion">
|
||||
<text class="button-text">上一题</text>
|
||||
</view>
|
||||
<view class="nav-button secondary {{currentIndex === total - 1 ? 'disabled' : ''}}" bindtap="nextQuestion">
|
||||
<text class="button-text">跳过</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-wrap" wx:if="{{answeredCount === total}}">
|
||||
<view class="submit-button" bindtap="submitTest">
|
||||
<text class="submit-text">{{isSubmitting ? '计算中...' : '完成测试,查看结果'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/test/mbti.wxml - MBTI测试页面(按旧版模板重构)-->
|
||||
<view class="test-page">
|
||||
<view class="progress-section">
|
||||
<view class="progress-info">
|
||||
<text class="question-count">问题 {{currentIndex + 1}}/{{total}}</text>
|
||||
<text class="time-remaining">剩余时间: {{formatTime}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar" style="width: {{progress}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-area">
|
||||
<view class="question-card">
|
||||
<text class="question-text">{{currentQuestion.question}}</text>
|
||||
<view class="options-container">
|
||||
<view
|
||||
class="option-item {{selectedAnswer === option.value ? 'selected' : ''}}"
|
||||
wx:for="{{currentQuestion.options}}"
|
||||
wx:for-item="option"
|
||||
wx:key="value"
|
||||
bindtap="selectAnswer"
|
||||
data-value="{{option.value}}"
|
||||
>
|
||||
<view class="radio-button {{selectedAnswer === option.value ? 'checked' : ''}}">
|
||||
<view wx:if="{{selectedAnswer === option.value}}" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="option-text">{{option.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-buttons">
|
||||
<view class="nav-button secondary {{currentIndex === 0 ? 'disabled' : ''}}" bindtap="prevQuestion">
|
||||
<text class="button-text">上一题</text>
|
||||
</view>
|
||||
<view class="nav-button secondary {{currentIndex === total - 1 ? 'disabled' : ''}}" bindtap="nextQuestion">
|
||||
<text class="button-text">跳过</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-wrap" wx:if="{{answeredCount === total}}">
|
||||
<view class="submit-button" bindtap="submitTest">
|
||||
<text class="submit-text">{{isSubmitting ? '计算中...' : '完成测试,查看结果'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,172 +1,172 @@
|
||||
/* pages/test/mbti.wxss - 按旧版模板重构 */
|
||||
.test-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.question-count {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.time-remaining {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 8rpx;
|
||||
background-color: #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #FF6B8A 0%, #ff8fa3 100%);
|
||||
border-radius: 8rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.question-text {
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
border: 2rpx solid #e5e5e5;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.option-item.selected {
|
||||
background-color: rgba(255, 107, 138, 0.12);
|
||||
border-color: #FF6B8A;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #d1d5db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.radio-button.checked {
|
||||
background-color: #FF6B8A;
|
||||
border-color: #FF6B8A;
|
||||
}
|
||||
|
||||
.radio-inner {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 32rpx;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
flex: 1;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-button.secondary {
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #FF6B8A;
|
||||
}
|
||||
|
||||
.nav-button.secondary .button-text {
|
||||
color: #FF6B8A;
|
||||
}
|
||||
|
||||
.nav-button.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-wrap {
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background: linear-gradient(135deg, #FF6B8A 0%, #ff8fa3 100%);
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
/* pages/test/mbti.wxss - 按旧版模板重构 */
|
||||
.test-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.question-count {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.time-remaining {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 8rpx;
|
||||
background-color: #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #FF6B8A 0%, #ff8fa3 100%);
|
||||
border-radius: 8rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.question-text {
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
border: 2rpx solid #e5e5e5;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.option-item.selected {
|
||||
background-color: rgba(255, 107, 138, 0.12);
|
||||
border-color: #FF6B8A;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #d1d5db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.radio-button.checked {
|
||||
background-color: #FF6B8A;
|
||||
border-color: #FF6B8A;
|
||||
}
|
||||
|
||||
.radio-inner {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 32rpx;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
flex: 1;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-button.secondary {
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #FF6B8A;
|
||||
}
|
||||
|
||||
.nav-button.secondary .button-text {
|
||||
color: #FF6B8A;
|
||||
}
|
||||
|
||||
.nav-button.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-wrap {
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background: linear-gradient(135deg, #FF6B8A 0%, #ff8fa3 100%);
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -1,137 +1,137 @@
|
||||
// pages/test/pdp.js
|
||||
const { pdpQuestions, shuffleQuestions } = require('../../utils/questions')
|
||||
const { pdpDescriptions } = require('../../utils/descriptions')
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
questions: [],
|
||||
currentIndex: 0,
|
||||
currentQuestion: null,
|
||||
answers: {},
|
||||
selectedAnswer: null,
|
||||
total: pdpQuestions.length,
|
||||
answeredCount: 0,
|
||||
progress: 0,
|
||||
timeRemaining: 15 * 60,
|
||||
formatTime: '15:00',
|
||||
isSubmitting: false
|
||||
},
|
||||
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
const questions = shuffleQuestions(pdpQuestions)
|
||||
this.setData({ questions, currentQuestion: questions[0] })
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
this.timer = setInterval(() => {
|
||||
let time = this.data.timeRemaining - 1
|
||||
if (time <= 0) {
|
||||
clearInterval(this.timer)
|
||||
this.submitTest()
|
||||
return
|
||||
}
|
||||
const minutes = Math.floor(time / 60)
|
||||
const seconds = time % 60
|
||||
this.setData({
|
||||
timeRemaining: time,
|
||||
formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
getEmoji(value) {
|
||||
const emojis = { Tiger: '🐅', Peacock: '🦚', Koala: '🐨', Owl: '🦉', Chameleon: '🦎' }
|
||||
return emojis[value] || '🔹'
|
||||
},
|
||||
|
||||
selectAnswer(e) {
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
let answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers: answers,
|
||||
answeredCount: Object.keys(answers).length,
|
||||
progress: (Object.keys(answers).length / this.data.total) * 100
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
this.nextQuestion()
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
|
||||
prevQuestion() {
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
nextQuestion() {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
const newIndex = this.data.currentIndex + 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
submitTest() {
|
||||
if (this.data.isSubmitting) return
|
||||
this.setData({ isSubmitting: true })
|
||||
|
||||
const scores = { Tiger: 0, Peacock: 0, Koala: 0, Owl: 0, Chameleon: 0 }
|
||||
Object.values(this.data.answers).forEach(value => {
|
||||
if (scores.hasOwnProperty(value)) scores[value]++
|
||||
})
|
||||
|
||||
const total = Object.values(scores).reduce((sum, v) => sum + v, 0)
|
||||
const percentages = {}
|
||||
Object.keys(scores).forEach(key => {
|
||||
percentages[key] = Math.round((scores[key] / total) * 100)
|
||||
})
|
||||
|
||||
const dominantType = Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0]
|
||||
const secondaryType = Object.entries(scores).sort((a, b) => b[1] - a[1])[1][0]
|
||||
|
||||
const resultData = {
|
||||
scores,
|
||||
percentages,
|
||||
dominantType,
|
||||
secondaryType,
|
||||
description: pdpDescriptions[dominantType],
|
||||
testDuration: 15 * 60 - this.data.timeRemaining,
|
||||
completedAt: new Date().toISOString(),
|
||||
// 便于后端留存完整答题过程
|
||||
answers: this.data.answers
|
||||
}
|
||||
|
||||
// 本地缓存 + 全局缓存
|
||||
wx.setStorageSync('pdpResult', resultData)
|
||||
if (app && typeof app.saveTestResult === 'function') {
|
||||
app.saveTestResult('pdp', resultData)
|
||||
}
|
||||
|
||||
wx.redirectTo({ url: '/pages/result/pdp' })
|
||||
}
|
||||
})
|
||||
// pages/test/pdp.js
|
||||
const { pdpQuestions, shuffleQuestions } = require('../../utils/questions')
|
||||
const { pdpDescriptions } = require('../../utils/descriptions')
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
questions: [],
|
||||
currentIndex: 0,
|
||||
currentQuestion: null,
|
||||
answers: {},
|
||||
selectedAnswer: null,
|
||||
total: pdpQuestions.length,
|
||||
answeredCount: 0,
|
||||
progress: 0,
|
||||
timeRemaining: 15 * 60,
|
||||
formatTime: '15:00',
|
||||
isSubmitting: false
|
||||
},
|
||||
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
const questions = shuffleQuestions(pdpQuestions)
|
||||
this.setData({ questions, currentQuestion: questions[0] })
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
this.timer = setInterval(() => {
|
||||
let time = this.data.timeRemaining - 1
|
||||
if (time <= 0) {
|
||||
clearInterval(this.timer)
|
||||
this.submitTest()
|
||||
return
|
||||
}
|
||||
const minutes = Math.floor(time / 60)
|
||||
const seconds = time % 60
|
||||
this.setData({
|
||||
timeRemaining: time,
|
||||
formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
getEmoji(value) {
|
||||
const emojis = { Tiger: '🐅', Peacock: '🦚', Koala: '🐨', Owl: '🦉', Chameleon: '🦎' }
|
||||
return emojis[value] || '🔹'
|
||||
},
|
||||
|
||||
selectAnswer(e) {
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
let answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers: answers,
|
||||
answeredCount: Object.keys(answers).length,
|
||||
progress: (Object.keys(answers).length / this.data.total) * 100
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
this.nextQuestion()
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
|
||||
prevQuestion() {
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
nextQuestion() {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
const newIndex = this.data.currentIndex + 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
this.setData({
|
||||
currentIndex: newIndex,
|
||||
currentQuestion: newQuestion,
|
||||
selectedAnswer: this.data.answers[newQuestion.id] || null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
submitTest() {
|
||||
if (this.data.isSubmitting) return
|
||||
this.setData({ isSubmitting: true })
|
||||
|
||||
const scores = { Tiger: 0, Peacock: 0, Koala: 0, Owl: 0, Chameleon: 0 }
|
||||
Object.values(this.data.answers).forEach(value => {
|
||||
if (scores.hasOwnProperty(value)) scores[value]++
|
||||
})
|
||||
|
||||
const total = Object.values(scores).reduce((sum, v) => sum + v, 0)
|
||||
const percentages = {}
|
||||
Object.keys(scores).forEach(key => {
|
||||
percentages[key] = Math.round((scores[key] / total) * 100)
|
||||
})
|
||||
|
||||
const dominantType = Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0]
|
||||
const secondaryType = Object.entries(scores).sort((a, b) => b[1] - a[1])[1][0]
|
||||
|
||||
const resultData = {
|
||||
scores,
|
||||
percentages,
|
||||
dominantType,
|
||||
secondaryType,
|
||||
description: pdpDescriptions[dominantType],
|
||||
testDuration: 15 * 60 - this.data.timeRemaining,
|
||||
completedAt: new Date().toISOString(),
|
||||
// 便于后端留存完整答题过程
|
||||
answers: this.data.answers
|
||||
}
|
||||
|
||||
// 本地缓存 + 全局缓存
|
||||
wx.setStorageSync('pdpResult', resultData)
|
||||
if (app && typeof app.saveTestResult === 'function') {
|
||||
app.saveTestResult('pdp', resultData)
|
||||
}
|
||||
|
||||
wx.redirectTo({ url: '/pages/result/pdp' })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "PDP测试",
|
||||
"navigationBarBackgroundColor": "#F59E0B",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "PDP测试",
|
||||
"navigationBarBackgroundColor": "#F59E0B",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
<!--pages/test/pdp.wxml - PDP测试页面(按旧版模板重构)-->
|
||||
<view class="test-page">
|
||||
<view class="progress-section">
|
||||
<view class="progress-info">
|
||||
<text class="question-count">问题 {{currentIndex + 1}}/{{total}}</text>
|
||||
<text class="time-remaining">剩余时间: {{formatTime}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar" style="width: {{progress}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-area">
|
||||
<view class="question-card">
|
||||
<text class="question-text">{{currentQuestion.question}}</text>
|
||||
<view class="options-container">
|
||||
<view
|
||||
class="option-item {{selectedAnswer === option.value ? 'selected' : ''}}"
|
||||
wx:for="{{currentQuestion.options}}"
|
||||
wx:for-item="option"
|
||||
wx:key="value"
|
||||
bindtap="selectAnswer"
|
||||
data-value="{{option.value}}"
|
||||
>
|
||||
<view class="radio-button {{selectedAnswer === option.value ? 'checked' : ''}}">
|
||||
<view wx:if="{{selectedAnswer === option.value}}" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="option-text">{{option.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-buttons">
|
||||
<view class="nav-button secondary {{currentIndex === 0 ? 'disabled' : ''}}" bindtap="prevQuestion">
|
||||
<text class="button-text">上一题</text>
|
||||
</view>
|
||||
<view class="nav-button secondary {{currentIndex === total - 1 ? 'disabled' : ''}}" bindtap="nextQuestion">
|
||||
<text class="button-text">跳过</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-wrap" wx:if="{{answeredCount === total}}">
|
||||
<view class="submit-button" bindtap="submitTest">
|
||||
<text class="submit-text">{{isSubmitting ? '计算中...' : '完成测试,查看结果'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/test/pdp.wxml - PDP测试页面(按旧版模板重构)-->
|
||||
<view class="test-page">
|
||||
<view class="progress-section">
|
||||
<view class="progress-info">
|
||||
<text class="question-count">问题 {{currentIndex + 1}}/{{total}}</text>
|
||||
<text class="time-remaining">剩余时间: {{formatTime}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-container">
|
||||
<view class="progress-bar" style="width: {{progress}}%"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-area">
|
||||
<view class="question-card">
|
||||
<text class="question-text">{{currentQuestion.question}}</text>
|
||||
<view class="options-container">
|
||||
<view
|
||||
class="option-item {{selectedAnswer === option.value ? 'selected' : ''}}"
|
||||
wx:for="{{currentQuestion.options}}"
|
||||
wx:for-item="option"
|
||||
wx:key="value"
|
||||
bindtap="selectAnswer"
|
||||
data-value="{{option.value}}"
|
||||
>
|
||||
<view class="radio-button {{selectedAnswer === option.value ? 'checked' : ''}}">
|
||||
<view wx:if="{{selectedAnswer === option.value}}" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="option-text">{{option.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-buttons">
|
||||
<view class="nav-button secondary {{currentIndex === 0 ? 'disabled' : ''}}" bindtap="prevQuestion">
|
||||
<text class="button-text">上一题</text>
|
||||
</view>
|
||||
<view class="nav-button secondary {{currentIndex === total - 1 ? 'disabled' : ''}}" bindtap="nextQuestion">
|
||||
<text class="button-text">跳过</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-wrap" wx:if="{{answeredCount === total}}">
|
||||
<view class="submit-button" bindtap="submitTest">
|
||||
<text class="submit-text">{{isSubmitting ? '计算中...' : '完成测试,查看结果'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,171 +1,171 @@
|
||||
/* pages/test/pdp.wxss - 按旧版模板重构 */
|
||||
.test-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.question-count {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.time-remaining {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 8rpx;
|
||||
background-color: #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%);
|
||||
border-radius: 8rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.question-text {
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
border: 2rpx solid #e5e5e5;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.option-item.selected {
|
||||
background-color: rgba(245, 158, 11, 0.12);
|
||||
border-color: #F59E0B;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #d1d5db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.radio-button.checked {
|
||||
background-color: #F59E0B;
|
||||
border-color: #F59E0B;
|
||||
}
|
||||
|
||||
.radio-inner {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 32rpx;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
flex: 1;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-button.secondary {
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #F59E0B;
|
||||
}
|
||||
|
||||
.nav-button.secondary .button-text {
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
.nav-button.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-wrap {
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%);
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
/* pages/test/pdp.wxss - 按旧版模板重构 */
|
||||
.test-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.question-count {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.time-remaining {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 8rpx;
|
||||
background-color: #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%);
|
||||
border-radius: 8rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.question-text {
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
border: 2rpx solid #e5e5e5;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.option-item.selected {
|
||||
background-color: rgba(245, 158, 11, 0.12);
|
||||
border-color: #F59E0B;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #d1d5db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.radio-button.checked {
|
||||
background-color: #F59E0B;
|
||||
border-color: #F59E0B;
|
||||
}
|
||||
|
||||
.radio-inner {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 32rpx;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
flex: 1;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-button.secondary {
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #F59E0B;
|
||||
}
|
||||
|
||||
.nav-button.secondary .button-text {
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
.nav-button.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-wrap {
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%);
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -1,205 +1,205 @@
|
||||
// pages/user-profile/index.js - 个人资料页
|
||||
const app = getApp()
|
||||
const { request } = require('../../utils/request')
|
||||
const { bindPhoneByCode } = require('../../utils/phoneAuth')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
userInfo: null,
|
||||
nickname: '',
|
||||
avatar: '',
|
||||
birthday: '',
|
||||
gender: 0,
|
||||
genderIndex: 0,
|
||||
genderText: '',
|
||||
genderOptions: ['保密', '男', '女'],
|
||||
phone: '',
|
||||
avatarLetter: '我',
|
||||
avatarBgColor: '#6366f1',
|
||||
nicknameFocused: false,
|
||||
saving: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadUserInfo()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 从其他页返回时刷新
|
||||
if (app.globalData.userInfo) {
|
||||
this.loadUserInfo()
|
||||
}
|
||||
},
|
||||
|
||||
loadUserInfo() {
|
||||
const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo')
|
||||
if (!userInfo) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
return
|
||||
}
|
||||
|
||||
const nickname = (userInfo.nickname || userInfo.nickName || '').trim()
|
||||
const avatar = userInfo.avatar || userInfo.avatarUrl || ''
|
||||
const birthday = userInfo.birthday || ''
|
||||
const gender = (userInfo.gender !== undefined && userInfo.gender !== null) ? Number(userInfo.gender) : 0
|
||||
const genderText = this._genderText(gender)
|
||||
const genderIndex = Math.min(Math.max(0, gender), 2)
|
||||
const phone = userInfo.phone || ''
|
||||
|
||||
const { avatarLetter, avatarBgColor } = this._avatarFromNickname(nickname || '我')
|
||||
|
||||
this.setData({
|
||||
userInfo,
|
||||
nickname,
|
||||
avatar,
|
||||
birthday,
|
||||
gender,
|
||||
genderIndex,
|
||||
genderText,
|
||||
phone,
|
||||
avatarLetter,
|
||||
avatarBgColor
|
||||
})
|
||||
},
|
||||
|
||||
_genderText(g) {
|
||||
const map = { 0: '保密', 1: '男', 2: '女' }
|
||||
return map[g] || '保密'
|
||||
},
|
||||
|
||||
_avatarFromNickname(name) {
|
||||
const str = (name && String(name).trim()) || '我'
|
||||
const letter = str.charAt(0).toUpperCase() || '我'
|
||||
const palette = ['#6366f1', '#8b5cf6', '#ec4899', '#f43f5e', '#14b8a6', '#0ea5e9', '#3b82f6', '#eab308']
|
||||
let hash = 0
|
||||
for (let i = 0; i < str.length; i++) hash += str.charCodeAt(i)
|
||||
const bgColor = palette[Math.abs(hash) % palette.length]
|
||||
return { avatarLetter: letter, avatarBgColor: bgColor }
|
||||
},
|
||||
|
||||
onChooseAvatar(e) {
|
||||
const { avatarUrl } = e.detail
|
||||
if (!avatarUrl || !app.globalData.token) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '上传中...' })
|
||||
const token = app.globalData.token
|
||||
const apiBase = app.globalData.apiBase
|
||||
wx.uploadFile({
|
||||
url: `${apiBase}/api/upload/image`,
|
||||
filePath: avatarUrl,
|
||||
name: 'file',
|
||||
header: { 'Authorization': `Bearer ${token}` },
|
||||
success: (res) => {
|
||||
const data = res.data
|
||||
let json = {}
|
||||
try { json = typeof data === 'string' ? JSON.parse(data) : data } catch (_) {}
|
||||
if (json.code === 200 && json.data && json.data.url) {
|
||||
const avatar = json.data.url
|
||||
this.setData({ avatar })
|
||||
this._saveProfile({ avatar })
|
||||
} else {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: json.message || '上传失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '上传失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onGetPhoneNumber(e) {
|
||||
const { errMsg, code } = e.detail || {}
|
||||
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
|
||||
wx.showToast({ title: '需要授权手机号才能获取', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!code) {
|
||||
wx.showToast({ title: '获取手机号失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
bindPhoneByCode(code).then((user) => {
|
||||
this.setData({ phone: user.phone || '' })
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
onNicknameRowTap() {
|
||||
this.setData({ nicknameFocused: true })
|
||||
},
|
||||
|
||||
onNicknameBlur() {
|
||||
this.setData({ nicknameFocused: false })
|
||||
},
|
||||
|
||||
onNicknameChange(e) {
|
||||
const nickname = (e?.detail?.value || '').trim()
|
||||
const { avatarLetter, avatarBgColor } = this._avatarFromNickname(nickname || '我')
|
||||
this.setData({ nickname, avatarLetter, avatarBgColor })
|
||||
},
|
||||
|
||||
onBirthdayChange(e) {
|
||||
const birthday = (e?.detail?.value || '').trim()
|
||||
this.setData({ birthday })
|
||||
},
|
||||
|
||||
onGenderChange(e) {
|
||||
const idx = parseInt(e?.detail?.value, 10) || 0
|
||||
const gender = idx
|
||||
const genderText = this.data.genderOptions[idx] || '保密'
|
||||
this.setData({ gender, genderIndex: idx, genderText })
|
||||
},
|
||||
|
||||
onSave() {
|
||||
const { nickname, birthday, gender, userInfo } = this.data
|
||||
const profile = {}
|
||||
const origNickname = (userInfo?.nickname || userInfo?.nickName || '').trim()
|
||||
const origBirthday = userInfo?.birthday || ''
|
||||
const origGender = (userInfo?.gender !== undefined && userInfo?.gender !== null) ? Number(userInfo.gender) : 0
|
||||
if (nickname !== origNickname) profile.nickname = nickname
|
||||
if (birthday !== origBirthday) profile.birthday = birthday
|
||||
if (gender !== origGender) profile.gender = gender
|
||||
if (Object.keys(profile).length === 0) {
|
||||
wx.showToast({ title: '暂无修改', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this._saveProfile(profile)
|
||||
},
|
||||
|
||||
_saveProfile(profile) {
|
||||
if (!app.globalData.token || !profile || Object.keys(profile).length === 0) return
|
||||
if (this.data.saving) return
|
||||
this.setData({ saving: true })
|
||||
wx.showLoading({ title: '保存中...' })
|
||||
request({
|
||||
url: '/api/auth/wechat/profile',
|
||||
method: 'PUT',
|
||||
data: profile,
|
||||
success: (res) => {
|
||||
wx.hideLoading()
|
||||
this.setData({ saving: false })
|
||||
const payload = res && res.data
|
||||
if (payload && payload.code === 200) {
|
||||
const updated = { ...(app.globalData.userInfo || {}), ...(payload.data || {}), ...profile }
|
||||
if (profile.nickname !== undefined) updated.nickname = profile.nickname
|
||||
if (profile.avatar !== undefined) updated.avatar = profile.avatar
|
||||
if (profile.birthday !== undefined) updated.birthday = profile.birthday
|
||||
if (profile.gender !== undefined) updated.gender = profile.gender
|
||||
app.globalData.userInfo = updated
|
||||
wx.setStorageSync('userInfo', updated)
|
||||
wx.showToast({ title: '已保存', icon: 'success' })
|
||||
} else {
|
||||
wx.showToast({ title: payload?.message || '保存失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
this.setData({ saving: false })
|
||||
wx.showToast({ title: '网络错误', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
// pages/user-profile/index.js - 个人资料页
|
||||
const app = getApp()
|
||||
const { request } = require('../../utils/request')
|
||||
const { bindPhoneByCode } = require('../../utils/phoneAuth')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
userInfo: null,
|
||||
nickname: '',
|
||||
avatar: '',
|
||||
birthday: '',
|
||||
gender: 0,
|
||||
genderIndex: 0,
|
||||
genderText: '',
|
||||
genderOptions: ['保密', '男', '女'],
|
||||
phone: '',
|
||||
avatarLetter: '我',
|
||||
avatarBgColor: '#6366f1',
|
||||
nicknameFocused: false,
|
||||
saving: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadUserInfo()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 从其他页返回时刷新
|
||||
if (app.globalData.userInfo) {
|
||||
this.loadUserInfo()
|
||||
}
|
||||
},
|
||||
|
||||
loadUserInfo() {
|
||||
const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo')
|
||||
if (!userInfo) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
return
|
||||
}
|
||||
|
||||
const nickname = (userInfo.nickname || userInfo.nickName || '').trim()
|
||||
const avatar = userInfo.avatar || userInfo.avatarUrl || ''
|
||||
const birthday = userInfo.birthday || ''
|
||||
const gender = (userInfo.gender !== undefined && userInfo.gender !== null) ? Number(userInfo.gender) : 0
|
||||
const genderText = this._genderText(gender)
|
||||
const genderIndex = Math.min(Math.max(0, gender), 2)
|
||||
const phone = userInfo.phone || ''
|
||||
|
||||
const { avatarLetter, avatarBgColor } = this._avatarFromNickname(nickname || '我')
|
||||
|
||||
this.setData({
|
||||
userInfo,
|
||||
nickname,
|
||||
avatar,
|
||||
birthday,
|
||||
gender,
|
||||
genderIndex,
|
||||
genderText,
|
||||
phone,
|
||||
avatarLetter,
|
||||
avatarBgColor
|
||||
})
|
||||
},
|
||||
|
||||
_genderText(g) {
|
||||
const map = { 0: '保密', 1: '男', 2: '女' }
|
||||
return map[g] || '保密'
|
||||
},
|
||||
|
||||
_avatarFromNickname(name) {
|
||||
const str = (name && String(name).trim()) || '我'
|
||||
const letter = str.charAt(0).toUpperCase() || '我'
|
||||
const palette = ['#6366f1', '#8b5cf6', '#ec4899', '#f43f5e', '#14b8a6', '#0ea5e9', '#3b82f6', '#eab308']
|
||||
let hash = 0
|
||||
for (let i = 0; i < str.length; i++) hash += str.charCodeAt(i)
|
||||
const bgColor = palette[Math.abs(hash) % palette.length]
|
||||
return { avatarLetter: letter, avatarBgColor: bgColor }
|
||||
},
|
||||
|
||||
onChooseAvatar(e) {
|
||||
const { avatarUrl } = e.detail
|
||||
if (!avatarUrl || !app.globalData.token) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '上传中...' })
|
||||
const token = app.globalData.token
|
||||
const apiBase = app.globalData.apiBase
|
||||
wx.uploadFile({
|
||||
url: `${apiBase}/api/upload/image`,
|
||||
filePath: avatarUrl,
|
||||
name: 'file',
|
||||
header: { 'Authorization': `Bearer ${token}` },
|
||||
success: (res) => {
|
||||
const data = res.data
|
||||
let json = {}
|
||||
try { json = typeof data === 'string' ? JSON.parse(data) : data } catch (_) {}
|
||||
if (json.code === 200 && json.data && json.data.url) {
|
||||
const avatar = json.data.url
|
||||
this.setData({ avatar })
|
||||
this._saveProfile({ avatar })
|
||||
} else {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: json.message || '上传失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '上传失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onGetPhoneNumber(e) {
|
||||
const { errMsg, code } = e.detail || {}
|
||||
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
|
||||
wx.showToast({ title: '需要授权手机号才能获取', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!code) {
|
||||
wx.showToast({ title: '获取手机号失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
bindPhoneByCode(code).then((user) => {
|
||||
this.setData({ phone: user.phone || '' })
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
onNicknameRowTap() {
|
||||
this.setData({ nicknameFocused: true })
|
||||
},
|
||||
|
||||
onNicknameBlur() {
|
||||
this.setData({ nicknameFocused: false })
|
||||
},
|
||||
|
||||
onNicknameChange(e) {
|
||||
const nickname = (e?.detail?.value || '').trim()
|
||||
const { avatarLetter, avatarBgColor } = this._avatarFromNickname(nickname || '我')
|
||||
this.setData({ nickname, avatarLetter, avatarBgColor })
|
||||
},
|
||||
|
||||
onBirthdayChange(e) {
|
||||
const birthday = (e?.detail?.value || '').trim()
|
||||
this.setData({ birthday })
|
||||
},
|
||||
|
||||
onGenderChange(e) {
|
||||
const idx = parseInt(e?.detail?.value, 10) || 0
|
||||
const gender = idx
|
||||
const genderText = this.data.genderOptions[idx] || '保密'
|
||||
this.setData({ gender, genderIndex: idx, genderText })
|
||||
},
|
||||
|
||||
onSave() {
|
||||
const { nickname, birthday, gender, userInfo } = this.data
|
||||
const profile = {}
|
||||
const origNickname = (userInfo?.nickname || userInfo?.nickName || '').trim()
|
||||
const origBirthday = userInfo?.birthday || ''
|
||||
const origGender = (userInfo?.gender !== undefined && userInfo?.gender !== null) ? Number(userInfo.gender) : 0
|
||||
if (nickname !== origNickname) profile.nickname = nickname
|
||||
if (birthday !== origBirthday) profile.birthday = birthday
|
||||
if (gender !== origGender) profile.gender = gender
|
||||
if (Object.keys(profile).length === 0) {
|
||||
wx.showToast({ title: '暂无修改', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this._saveProfile(profile)
|
||||
},
|
||||
|
||||
_saveProfile(profile) {
|
||||
if (!app.globalData.token || !profile || Object.keys(profile).length === 0) return
|
||||
if (this.data.saving) return
|
||||
this.setData({ saving: true })
|
||||
wx.showLoading({ title: '保存中...' })
|
||||
request({
|
||||
url: '/api/auth/wechat/profile',
|
||||
method: 'PUT',
|
||||
data: profile,
|
||||
success: (res) => {
|
||||
wx.hideLoading()
|
||||
this.setData({ saving: false })
|
||||
const payload = res && res.data
|
||||
if (payload && payload.code === 200) {
|
||||
const updated = { ...(app.globalData.userInfo || {}), ...(payload.data || {}), ...profile }
|
||||
if (profile.nickname !== undefined) updated.nickname = profile.nickname
|
||||
if (profile.avatar !== undefined) updated.avatar = profile.avatar
|
||||
if (profile.birthday !== undefined) updated.birthday = profile.birthday
|
||||
if (profile.gender !== undefined) updated.gender = profile.gender
|
||||
app.globalData.userInfo = updated
|
||||
wx.setStorageSync('userInfo', updated)
|
||||
wx.showToast({ title: '已保存', icon: 'success' })
|
||||
} else {
|
||||
wx.showToast({ title: payload?.message || '保存失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
this.setData({ saving: false })
|
||||
wx.showToast({ title: '网络错误', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "个人资料",
|
||||
"usingComponents": {}
|
||||
}
|
||||
{
|
||||
"navigationBarTitleText": "个人资料",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
<!--pages/user-profile/index.wxml - 个人资料页 -->
|
||||
<view class="page">
|
||||
<view class="profile-card">
|
||||
<!-- 头像 -->
|
||||
<view class="avatar-section">
|
||||
<button class="avatar-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
|
||||
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill"/>
|
||||
<view wx:else class="avatar-letter-wrap" style="background:{{avatarBgColor}}">
|
||||
<text class="avatar-letter">{{avatarLetter}}</text>
|
||||
</view>
|
||||
</button>
|
||||
<text class="avatar-hint">点击更换头像</text>
|
||||
</view>
|
||||
|
||||
<!-- 必填提示 -->
|
||||
<view class="required-tip">头像、昵称、手机号为必填项</view>
|
||||
|
||||
<!-- 表单 -->
|
||||
<view class="form-section">
|
||||
<!-- 昵称:整行可点击聚焦输入 -->
|
||||
<view class="form-item form-item-tappable" bindtap="onNicknameRowTap">
|
||||
<text class="form-label">昵称</text>
|
||||
<input class="form-input"
|
||||
id="nicknameInput"
|
||||
type="nickname"
|
||||
placeholder="请输入昵称"
|
||||
value="{{nickname}}"
|
||||
focus="{{nicknameFocused}}"
|
||||
bindinput="onNicknameChange"
|
||||
bindblur="onNicknameBlur"/>
|
||||
<text class="form-arrow">›</text>
|
||||
</view>
|
||||
<!-- 生日:整行可点击打开日期选择 -->
|
||||
<picker mode="date" value="{{birthday}}" bindchange="onBirthdayChange" class="picker-full-row">
|
||||
<view class="form-item form-item-tappable">
|
||||
<text class="form-label">生日</text>
|
||||
<view class="form-picker">
|
||||
<text class="{{birthday ? 'form-value' : 'form-placeholder'}}">{{birthday || '请选择生日'}}</text>
|
||||
<text class="form-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- 性别:整行可点击选择 -->
|
||||
<picker mode="selector" range="{{genderOptions}}" value="{{genderIndex}}" bindchange="onGenderChange" class="picker-full-row">
|
||||
<view class="form-item form-item-tappable">
|
||||
<text class="form-label">性别</text>
|
||||
<view class="form-picker">
|
||||
<text class="form-value">{{genderText}}</text>
|
||||
<text class="form-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- 手机号:整行可点击重新授权 -->
|
||||
<view class="form-item form-item-phone-auth">
|
||||
<text class="form-label">手机号</text>
|
||||
<button class="phone-auth-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">
|
||||
<text class="{{phone ? 'form-value' : 'phone-auth-text'}}">{{phone || '点击授权获取手机号'}}</text>
|
||||
<text class="form-arrow">›</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<button class="save-btn" bindtap="onSave" disabled="{{saving}}">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/user-profile/index.wxml - 个人资料页 -->
|
||||
<view class="page">
|
||||
<view class="profile-card">
|
||||
<!-- 头像 -->
|
||||
<view class="avatar-section">
|
||||
<button class="avatar-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
|
||||
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill"/>
|
||||
<view wx:else class="avatar-letter-wrap" style="background:{{avatarBgColor}}">
|
||||
<text class="avatar-letter">{{avatarLetter}}</text>
|
||||
</view>
|
||||
</button>
|
||||
<text class="avatar-hint">点击更换头像</text>
|
||||
</view>
|
||||
|
||||
<!-- 必填提示 -->
|
||||
<view class="required-tip">头像、昵称、手机号为必填项</view>
|
||||
|
||||
<!-- 表单 -->
|
||||
<view class="form-section">
|
||||
<!-- 昵称:整行可点击聚焦输入 -->
|
||||
<view class="form-item form-item-tappable" bindtap="onNicknameRowTap">
|
||||
<text class="form-label">昵称</text>
|
||||
<input class="form-input"
|
||||
id="nicknameInput"
|
||||
type="nickname"
|
||||
placeholder="请输入昵称"
|
||||
value="{{nickname}}"
|
||||
focus="{{nicknameFocused}}"
|
||||
bindinput="onNicknameChange"
|
||||
bindblur="onNicknameBlur"/>
|
||||
<text class="form-arrow">›</text>
|
||||
</view>
|
||||
<!-- 生日:整行可点击打开日期选择 -->
|
||||
<picker mode="date" value="{{birthday}}" bindchange="onBirthdayChange" class="picker-full-row">
|
||||
<view class="form-item form-item-tappable">
|
||||
<text class="form-label">生日</text>
|
||||
<view class="form-picker">
|
||||
<text class="{{birthday ? 'form-value' : 'form-placeholder'}}">{{birthday || '请选择生日'}}</text>
|
||||
<text class="form-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- 性别:整行可点击选择 -->
|
||||
<picker mode="selector" range="{{genderOptions}}" value="{{genderIndex}}" bindchange="onGenderChange" class="picker-full-row">
|
||||
<view class="form-item form-item-tappable">
|
||||
<text class="form-label">性别</text>
|
||||
<view class="form-picker">
|
||||
<text class="form-value">{{genderText}}</text>
|
||||
<text class="form-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- 手机号:整行可点击重新授权 -->
|
||||
<view class="form-item form-item-phone-auth">
|
||||
<text class="form-label">手机号</text>
|
||||
<button class="phone-auth-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">
|
||||
<text class="{{phone ? 'form-value' : 'phone-auth-text'}}">{{phone || '点击授权获取手机号'}}</text>
|
||||
<text class="form-arrow">›</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<button class="save-btn" bindtap="onSave" disabled="{{saving}}">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,206 +1,206 @@
|
||||
/* pages/user-profile/index.wxss */
|
||||
page {
|
||||
background-color: #F9FAFB;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 28rpx;
|
||||
padding: 48rpx 36rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.avatar-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.avatar-btn {
|
||||
width: 160rpx !important;
|
||||
height: 160rpx !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.avatar-btn::after {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
border: 4rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.avatar-letter-wrap {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 4rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.avatar-letter {
|
||||
font-size: 72rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.avatar-hint {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.required-tip {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
border-top: 1rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 1rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
width: 140rpx;
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #111827;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #111827;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-value {
|
||||
font-size: 30rpx;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.form-placeholder {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.form-readonly {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #D1D5DB;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 整行可点击 */
|
||||
.form-item-tappable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-item-tappable:active {
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
.form-item-readonly {
|
||||
/* 仅展示,点击有提示 */
|
||||
}
|
||||
|
||||
.form-item-readonly:active {
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
/* 手机号授权按钮 */
|
||||
.form-item-phone-auth {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.phone-auth-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 0;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.phone-auth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.phone-auth-text {
|
||||
font-size: 30rpx;
|
||||
color: #7C3AED;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.picker-full-row {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 保存按钮 */
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
margin-top: 48rpx;
|
||||
padding: 28rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
background: linear-gradient(135deg, #7C3AED 0%, #6366F1 100%);
|
||||
border-radius: 16rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.save-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.save-btn[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
/* pages/user-profile/index.wxss */
|
||||
page {
|
||||
background-color: #F9FAFB;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 28rpx;
|
||||
padding: 48rpx 36rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.avatar-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.avatar-btn {
|
||||
width: 160rpx !important;
|
||||
height: 160rpx !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.avatar-btn::after {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
border: 4rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.avatar-letter-wrap {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 4rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.avatar-letter {
|
||||
font-size: 72rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.avatar-hint {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.required-tip {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
border-top: 1rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 1rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
width: 140rpx;
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #111827;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #111827;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-value {
|
||||
font-size: 30rpx;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.form-placeholder {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.form-readonly {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #D1D5DB;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 整行可点击 */
|
||||
.form-item-tappable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-item-tappable:active {
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
.form-item-readonly {
|
||||
/* 仅展示,点击有提示 */
|
||||
}
|
||||
|
||||
.form-item-readonly:active {
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
/* 手机号授权按钮 */
|
||||
.form-item-phone-auth {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.phone-auth-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 0;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.phone-auth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.phone-auth-text {
|
||||
font-size: 30rpx;
|
||||
color: #7C3AED;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.picker-full-row {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 保存按钮 */
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
margin-top: 48rpx;
|
||||
padding: 28rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
background: linear-gradient(135deg, #7C3AED 0%, #6366F1 100%);
|
||||
border-radius: 16rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.save-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.save-btn[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user