Merge remote-tracking branch 'origin/main' into zuncle
This commit is contained in:
commit
49027862b3
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,3 +10,4 @@ node_modules/
|
|||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
.hbuilderx/project.config.json
|
.hbuilderx/project.config.json
|
||||||
clean-cache.bat
|
clean-cache.bat
|
||||||
|
.hbuilderx/launch.json
|
||||||
|
|||||||
1
App.vue
1
App.vue
@ -4,6 +4,7 @@ import { getPublicConfig } from '@/api/appUser'
|
|||||||
export default {
|
export default {
|
||||||
onLaunch: function(options) {
|
onLaunch: function(options) {
|
||||||
console.log('App Launch', options)
|
console.log('App Launch', options)
|
||||||
|
|
||||||
try { uni.setStorageSync('app_session_id', String(Date.now())) } catch (_) {}
|
try { uni.setStorageSync('app_session_id', String(Date.now())) } catch (_) {}
|
||||||
if (options && options.query && options.query.invite_code) {
|
if (options && options.query && options.query.invite_code) {
|
||||||
console.log('App Launch captured invite_code:', options.query.invite_code)
|
console.log('App Launch captured invite_code:', options.query.invite_code)
|
||||||
|
|||||||
@ -224,6 +224,10 @@ export function createWechatOrder(data) {
|
|||||||
return authRequest({ url: '/api/app/pay/wechat/jsapi/preorder', method: 'POST', data })
|
return authRequest({ url: '/api/app/pay/wechat/jsapi/preorder', method: 'POST', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createWechatAppOrder(data) {
|
||||||
|
return authRequest({ url: '/api/app/pay/wechat/app/preorder', method: 'POST', data })
|
||||||
|
}
|
||||||
|
|
||||||
export function getLotteryResult(order_no) {
|
export function getLotteryResult(order_no) {
|
||||||
return authRequest({ url: '/api/app/lottery/result', method: 'GET', data: { order_no } })
|
return authRequest({ url: '/api/app/lottery/result', method: 'GET', data: { order_no } })
|
||||||
}
|
}
|
||||||
|
|||||||
190
components/PrivacyPopup.vue
Normal file
190
components/PrivacyPopup.vue
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="showPrivacy" class="privacy-popup-mask">
|
||||||
|
<view class="privacy-popup-content">
|
||||||
|
<view class="privacy-title">用户隐私保护提示</view>
|
||||||
|
<view class="privacy-desc">
|
||||||
|
感谢您使用柯大鸭盲盒小程序。我们非常重视您的隐私保护和用户权益。
|
||||||
|
<br /><br />
|
||||||
|
在您使用我们的服务前,请您仔细阅读并充分理解
|
||||||
|
<text class="privacy-link" @tap="openPrivacyContract">《用户隐私保护指引》</text>。
|
||||||
|
<br /><br />
|
||||||
|
当您点击同意并开始使用我们的服务时,即表示您已理解并同意该指引内容,我们将按照指引内容处理您的个人信息。
|
||||||
|
</view>
|
||||||
|
<view class="privacy-buttons">
|
||||||
|
<button class="btn-reject" @tap="handleDisagree">拒绝</button>
|
||||||
|
<button
|
||||||
|
class="btn-agree"
|
||||||
|
id="agree-btn"
|
||||||
|
open-type="agreePrivacyAuthorization"
|
||||||
|
@agreeprivacyauthorization="handleAgree"
|
||||||
|
>
|
||||||
|
同意
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PrivacyPopup',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showPrivacy: false,
|
||||||
|
resolvePrivacyAuthorization: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
this.initPrivacy()
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initPrivacy() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
wx.onNeedPrivacyAuthorization((resolve, eventInfo) => {
|
||||||
|
console.log('[隐私协议] 触发隐私授权', eventInfo)
|
||||||
|
this.resolvePrivacyAuthorization = resolve
|
||||||
|
this.showPrivacy = true
|
||||||
|
})
|
||||||
|
|
||||||
|
wx.getPrivacySetting({
|
||||||
|
success: (res) => {
|
||||||
|
console.log('[隐私协议] 隐私设置', res)
|
||||||
|
if (res.needAuthorization) {
|
||||||
|
// 需要弹出隐私协议
|
||||||
|
} else {
|
||||||
|
// 用户已同意隐私协议
|
||||||
|
this.showPrivacy = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('[隐私协议] 获取隐私设置失败', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
openPrivacyContract() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
wx.openPrivacyContract({
|
||||||
|
success: () => {
|
||||||
|
console.log('[隐私协议] 打开隐私协议成功')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('[隐私协议] 打开隐私协议失败', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
handleAgree() {
|
||||||
|
console.log('[隐私协议] 用户同意')
|
||||||
|
this.showPrivacy = false
|
||||||
|
if (this.resolvePrivacyAuthorization) {
|
||||||
|
this.resolvePrivacyAuthorization({
|
||||||
|
event: 'agree',
|
||||||
|
buttonId: 'agree-btn'
|
||||||
|
})
|
||||||
|
this.resolvePrivacyAuthorization = null
|
||||||
|
}
|
||||||
|
this.$emit('agree')
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDisagree() {
|
||||||
|
console.log('[隐私协议] 用户拒绝')
|
||||||
|
this.showPrivacy = false
|
||||||
|
if (this.resolvePrivacyAuthorization) {
|
||||||
|
this.resolvePrivacyAuthorization({
|
||||||
|
event: 'disagree'
|
||||||
|
})
|
||||||
|
this.resolvePrivacyAuthorization = null
|
||||||
|
}
|
||||||
|
this.$emit('disagree')
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '您拒绝了隐私协议,部分功能可能无法正常使用',
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.privacy-popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-popup-content {
|
||||||
|
width: 600rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 48rpx 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-desc {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-link {
|
||||||
|
color: #FF6B00;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-reject {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #666;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: none;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-agree {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, #FF9500, #FF6B00);
|
||||||
|
border: none;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -17,7 +17,9 @@
|
|||||||
"delay" : 0
|
"delay" : 0
|
||||||
},
|
},
|
||||||
/* 模块配置 */
|
/* 模块配置 */
|
||||||
"modules" : {},
|
"modules" : {
|
||||||
|
"Payment" : {}
|
||||||
|
},
|
||||||
/* 应用发布信息 */
|
/* 应用发布信息 */
|
||||||
"distribute" : {
|
"distribute" : {
|
||||||
/* android打包配置 */
|
/* android打包配置 */
|
||||||
@ -37,7 +39,10 @@
|
|||||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
/* ios打包配置 */
|
/* ios打包配置 */
|
||||||
@ -45,7 +50,22 @@
|
|||||||
"dSYMs" : false
|
"dSYMs" : false
|
||||||
},
|
},
|
||||||
/* SDK配置 */
|
/* SDK配置 */
|
||||||
"sdkConfigs" : {}
|
"sdkConfigs" : {
|
||||||
|
"payment" : {
|
||||||
|
"weixin" : {
|
||||||
|
"__platform__" : ["android", "ios"],
|
||||||
|
"appid" : "wx26ad074017e1e63f",
|
||||||
|
"UniversalLinks" : ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"share" : {},
|
||||||
|
"oauth" : {
|
||||||
|
"weixin" : {
|
||||||
|
"appid" : "wx26ad074017e1e63f",
|
||||||
|
"UniversalLinks" : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/* 快应用特有相关 */
|
/* 快应用特有相关 */
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<view v-else-if="isOutOfStock" class="empty">商品库存不足,由于市场价格存在波动,请联系客服核实价格和补充库存</view>
|
<view v-else-if="isOutOfStock" class="empty">商品库存不足,由于市场价格存在波动,请联系客服核实价格和补充库存</view>
|
||||||
<view v-else-if="detail.id" class="detail-wrap">
|
<view v-else-if="detail.id" class="detail-wrap">
|
||||||
<!-- 商品图片轮播 -->
|
<!-- 商品图片轮播 -->
|
||||||
<swiper class="main-image-swiper" v-if="imageList.length > 0" circular autoplay interval="3000" duration="500">
|
<swiper v-if="imageList.length > 0" :key="'detail-' + swiperKey" class="main-image-swiper" :circular="swiperAutoplay" :autoplay="swiperAutoplay" interval="3000" duration="500">
|
||||||
<swiper-item v-for="(img, index) in imageList" :key="index">
|
<swiper-item v-for="(img, index) in imageList" :key="index">
|
||||||
<image class="main-image" :src="img" mode="aspectFill" @tap="previewImage(index)" />
|
<image class="main-image" :src="img" mode="aspectFill" @tap="previewImage(index)" />
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
@ -45,13 +45,15 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
|
||||||
import { getProductDetail } from '../../api/appUser'
|
import { getProductDetail } from '../../api/appUser'
|
||||||
import { redeemProductByPoints } from '../../utils/request.js'
|
import { redeemProductByPoints } from '../../utils/request.js'
|
||||||
|
|
||||||
const detail = ref({})
|
const detail = ref({})
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const isOutOfStock = ref(false)
|
const isOutOfStock = ref(false)
|
||||||
|
const swiperAutoplay = ref(true)
|
||||||
|
const swiperKey = ref(0)
|
||||||
|
|
||||||
// 计算属性:处理商品图片
|
// 计算属性:处理商品图片
|
||||||
const imageList = computed(() => {
|
const imageList = computed(() => {
|
||||||
@ -221,6 +223,19 @@ onLoad((opts) => {
|
|||||||
const id = opts && opts.id
|
const id = opts && opts.id
|
||||||
if (id) fetchDetail(id)
|
if (id) fetchDetail(id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
swiperKey.value++
|
||||||
|
swiperAutoplay.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
onHide(() => {
|
||||||
|
swiperAutoplay.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnload(() => {
|
||||||
|
swiperAutoplay.value = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -235,9 +235,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
"custom": true,
|
"custom": false,
|
||||||
"color": "#7A7E83",
|
"color": "#7A7E83",
|
||||||
"selectedColor": "#007AFF",
|
"selectedColor": "#FF6B00",
|
||||||
"backgroundColor": "#FFFFFF",
|
"backgroundColor": "#FFFFFF",
|
||||||
"borderStyle": "black",
|
"borderStyle": "black",
|
||||||
"list": [
|
"list": [
|
||||||
@ -279,5 +279,8 @@
|
|||||||
"^BlessingAnimation": "@/components/BlessingAnimation.vue"
|
"^BlessingAnimation": "@/components/BlessingAnimation.vue"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mp-weixin": {
|
||||||
|
"__usePrivacyCheck__": true
|
||||||
|
},
|
||||||
"uniIdRouter": {}
|
"uniIdRouter": {}
|
||||||
}
|
}
|
||||||
@ -3,13 +3,10 @@
|
|||||||
<!-- 顶部装饰背景 - 漂浮光球 -->
|
<!-- 顶部装饰背景 - 漂浮光球 -->
|
||||||
<view class="bg-decoration"></view>
|
<view class="bg-decoration"></view>
|
||||||
|
|
||||||
<!-- 自定义 tabBar -->
|
<!-- 自定义 tabBar (仅抖音小程序) -->
|
||||||
<!-- #ifdef MP-TOUTIAO -->
|
<!-- #ifdef MP-TOUTIAO -->
|
||||||
<customTabBarToutiao />
|
<customTabBarToutiao />
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<!-- #ifndef MP-TOUTIAO -->
|
|
||||||
<customTabBar />
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 顶部 Tab -->
|
<!-- 顶部 Tab -->
|
||||||
<view class="tabs glass-card">
|
<view class="tabs glass-card">
|
||||||
@ -296,9 +293,6 @@ import { executePaymentFlow } from '@/utils/payment.js'
|
|||||||
// #ifdef MP-TOUTIAO
|
// #ifdef MP-TOUTIAO
|
||||||
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
|
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-TOUTIAO
|
|
||||||
import customTabBar from '@/components/app-tab-bar.vue'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
const currentTab = ref(0)
|
const currentTab = ref(0)
|
||||||
const aggregatedList = ref([])
|
const aggregatedList = ref([])
|
||||||
@ -375,7 +369,9 @@ onShow(async () => {
|
|||||||
if (!token) {
|
if (!token) {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '请先登录',
|
content: '请先登录后查看盒柜内容',
|
||||||
|
showCancel: true,
|
||||||
|
cancelText: '稍后再说',
|
||||||
confirmText: '去登录',
|
confirmText: '去登录',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
|
|||||||
@ -6,13 +6,10 @@
|
|||||||
<!-- 品牌级背景装饰系统 - 统一漂浮光球 -->
|
<!-- 品牌级背景装饰系统 - 统一漂浮光球 -->
|
||||||
<view class="bg-decoration"></view>
|
<view class="bg-decoration"></view>
|
||||||
|
|
||||||
<!-- 自定义 tabBar -->
|
<!-- 自定义 tabBar (仅抖音小程序) -->
|
||||||
<!-- #ifdef MP-TOUTIAO -->
|
<!-- #ifdef MP-TOUTIAO -->
|
||||||
<customTabBarToutiao />
|
<customTabBarToutiao />
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<!-- #ifndef MP-TOUTIAO -->
|
|
||||||
<customTabBar />
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 顶部导航栏 (搜索) -->
|
<!-- 顶部导航栏 (搜索) -->
|
||||||
<view class="nav-header">
|
<view class="nav-header">
|
||||||
@ -23,7 +20,7 @@
|
|||||||
<scroll-view class="main-content" scroll-y>
|
<scroll-view class="main-content" scroll-y>
|
||||||
<!-- Banner 区域 (现代级浮动设计) -->
|
<!-- Banner 区域 (现代级浮动设计) -->
|
||||||
<view class="banner-container">
|
<view class="banner-container">
|
||||||
<swiper class="banner-swiper" circular autoplay interval="5000" duration="600" :indicator-dots="false" @change="onBannerChange">
|
<swiper :key="'banner-' + swiperKey" class="banner-swiper" :current="bannerIndex" :circular="swiperAutoplay" :autoplay="swiperAutoplay" interval="5000" duration="600" :indicator-dots="false" @change="onBannerChange">
|
||||||
<swiper-item v-for="(b, index) in displayBanners" :key="b.id">
|
<swiper-item v-for="(b, index) in displayBanners" :key="b.id">
|
||||||
<view class="banner-card" :class="{ 'active': bannerIndex === index }">
|
<view class="banner-card" :class="{ 'active': bannerIndex === index }">
|
||||||
<image v-if="b.image" class="banner-image" :src="b.image" mode="aspectFit" @tap="onBannerTap(b)" />
|
<image v-if="b.image" class="banner-image" :src="b.image" mode="aspectFit" @tap="onBannerTap(b)" />
|
||||||
@ -45,7 +42,7 @@
|
|||||||
<!-- 品牌动态栏 (极简风格) -->
|
<!-- 品牌动态栏 (极简风格) -->
|
||||||
<view class="notice-bar-v2" @tap="onNoticeTap">
|
<view class="notice-bar-v2" @tap="onNoticeTap">
|
||||||
<view class="notice-icon">📢</view>
|
<view class="notice-icon">📢</view>
|
||||||
<swiper class="notice-swiper" vertical circular autoplay interval="3500">
|
<swiper :key="'notice-' + swiperKey" class="notice-swiper" vertical :circular="swiperAutoplay" :autoplay="swiperAutoplay" interval="3500">
|
||||||
<swiper-item v-for="n in displayNotices" :key="n.id">
|
<swiper-item v-for="n in displayNotices" :key="n.id">
|
||||||
<view class="notice-item">{{ n.text }}</view>
|
<view class="notice-item">{{ n.text }}</view>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
@ -147,9 +144,6 @@ import SplashScreen from '@/components/SplashScreen.vue'
|
|||||||
// #ifdef MP-TOUTIAO
|
// #ifdef MP-TOUTIAO
|
||||||
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
|
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-TOUTIAO
|
|
||||||
import customTabBar from '@/components/app-tab-bar.vue'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -157,9 +151,6 @@ export default {
|
|||||||
// #ifdef MP-TOUTIAO
|
// #ifdef MP-TOUTIAO
|
||||||
, customTabBarToutiao
|
, customTabBarToutiao
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-TOUTIAO
|
|
||||||
, customTabBar
|
|
||||||
// #endif
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -168,7 +159,9 @@ export default {
|
|||||||
activities: [],
|
activities: [],
|
||||||
selectedGroupName: '',
|
selectedGroupName: '',
|
||||||
bannerIndex: 0,
|
bannerIndex: 0,
|
||||||
isHomeLoading: false
|
isHomeLoading: false,
|
||||||
|
swiperAutoplay: true,
|
||||||
|
swiperKey: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -221,11 +214,21 @@ export default {
|
|||||||
this.loadHomeData(true)
|
this.loadHomeData(true)
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
// 只有非首次进入或数据为空时才触发刷新,避免 onLoad/onShow 双重触发
|
this.swiperKey++
|
||||||
|
this.swiperAutoplay = true
|
||||||
if (this.activities.length === 0 && !this.isHomeLoading) {
|
if (this.activities.length === 0 && !this.isHomeLoading) {
|
||||||
this.loadHomeData()
|
this.loadHomeData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onHide() {
|
||||||
|
this.swiperAutoplay = false
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
this.swiperAutoplay = false
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.swiperAutoplay = false
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onBannerChange(e) {
|
onBannerChange(e) {
|
||||||
this.bannerIndex = e.detail.current
|
this.bannerIndex = e.detail.current
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page-login">
|
<view class="page-login">
|
||||||
|
<!-- 隐私协议弹窗 (仅微信小程序) -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<PrivacyPopup ref="privacyPopup" @agree="onPrivacyAgree" @disagree="onPrivacyDisagree" />
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- 背景装饰 -->
|
<!-- 背景装饰 -->
|
||||||
<view class="bg-decoration"></view>
|
<view class="bg-decoration"></view>
|
||||||
|
|
||||||
@ -66,6 +71,10 @@
|
|||||||
>
|
>
|
||||||
{{ loading ? '获取中...' : '一键获取手机号' }}
|
{{ loading ? '获取中...' : '一键获取手机号' }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button class="btn-cancel" @tap="handleCancelLogin">
|
||||||
|
取消登录
|
||||||
|
</button>
|
||||||
</view>
|
</view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
@ -89,11 +98,15 @@
|
|||||||
>
|
>
|
||||||
{{ loading ? '登录中...' : '抖音用户授权登录' }}
|
{{ loading ? '登录中...' : '抖音用户授权登录' }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button class="btn-cancel" @tap="handleCancelLogin">
|
||||||
|
取消登录
|
||||||
|
</button>
|
||||||
</view>
|
</view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- 短信登录 -->
|
<!-- 短信登录 -->
|
||||||
<view v-else class="login-panel sms-panel">
|
<view v-if="loginMode === 'sms'" class="login-panel sms-panel">
|
||||||
<text class="panel-title">手机号验证码登录</text>
|
<text class="panel-title">手机号验证码登录</text>
|
||||||
<text class="panel-desc">输入手机号,获取验证码完成登录</text>
|
<text class="panel-desc">输入手机号,获取验证码完成登录</text>
|
||||||
|
|
||||||
@ -144,6 +157,10 @@
|
|||||||
{{ loading ? '登录中...' : '登录 / 注册' }}
|
{{ loading ? '登录中...' : '登录 / 注册' }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button class="btn-cancel" @tap="handleCancelLogin">
|
||||||
|
取消登录
|
||||||
|
</button>
|
||||||
|
|
||||||
<text class="hint-text">未注册的手机号将自动创建账号</text>
|
<text class="hint-text">未注册的手机号将自动创建账号</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -179,10 +196,38 @@ import { request } from '../../utils/request'
|
|||||||
import { wechatLogin, douyinLogin, bindPhone, bindDouyinPhone, getUserStats, getPointsBalance, sendSmsCode, smsLogin, getUserInfo } from '../../api/appUser'
|
import { wechatLogin, douyinLogin, bindPhone, bindDouyinPhone, getUserStats, getPointsBalance, sendSmsCode, smsLogin, getUserInfo } from '../../api/appUser'
|
||||||
import { authRequest } from '../../utils/request'
|
import { authRequest } from '../../utils/request'
|
||||||
import { vibrateShort } from '@/utils/vibrate.js'
|
import { vibrateShort } from '@/utils/vibrate.js'
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
import PrivacyPopup from '@/components/PrivacyPopup.vue'
|
||||||
|
// #endif
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const agreementChecked = ref(false)
|
const agreementChecked = ref(false)
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
const privacyPopup = ref(null)
|
||||||
|
|
||||||
|
function onPrivacyAgree() {
|
||||||
|
console.log('[隐私协议] 用户已同意隐私协议')
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPrivacyDisagree() {
|
||||||
|
console.log('[隐私协议] 用户拒绝隐私协议')
|
||||||
|
uni.showToast({
|
||||||
|
title: '您拒绝了隐私协议,部分功能可能无法正常使用',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
function handleCancelLogin() {
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
if (pages.length > 1) {
|
||||||
|
uni.navigateBack()
|
||||||
|
} else {
|
||||||
|
uni.switchTab({ url: '/pages/index/index' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 登录模式:根据平台设置默认值
|
// 登录模式:根据平台设置默认值
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
const loginMode = ref('wechat')
|
const loginMode = ref('wechat')
|
||||||
@ -1017,6 +1062,20 @@ function fetchExtraData(userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 取消登录按钮 */
|
||||||
|
.btn-cancel {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $text-sub;
|
||||||
|
background: transparent;
|
||||||
|
border: 2rpx solid $border-color;
|
||||||
|
border-radius: $radius-round;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
|
||||||
|
&::after { border: none; }
|
||||||
|
}
|
||||||
|
|
||||||
.hint-text {
|
.hint-text {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@ -3,13 +3,10 @@
|
|||||||
<!-- 顶部背景装饰 -->
|
<!-- 顶部背景装饰 -->
|
||||||
<view class="bg-decoration"></view>
|
<view class="bg-decoration"></view>
|
||||||
|
|
||||||
<!-- 自定义 tabBar -->
|
<!-- 自定义 tabBar (仅抖音小程序) -->
|
||||||
<!-- #ifdef MP-TOUTIAO -->
|
<!-- #ifdef MP-TOUTIAO -->
|
||||||
<customTabBarToutiao />
|
<customTabBarToutiao />
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<!-- #ifndef MP-TOUTIAO -->
|
|
||||||
<customTabBar />
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 头部区域 -->
|
<!-- 头部区域 -->
|
||||||
<view class="header-section">
|
<view class="header-section">
|
||||||
@ -499,22 +496,15 @@ import {
|
|||||||
getUserInfo, getUserStats, getPointsBalance, getUserPoints, getUserCoupons, getItemCards,
|
getUserInfo, getUserStats, getPointsBalance, getUserPoints, getUserCoupons, getItemCards,
|
||||||
getUserTasks, getTaskProgress, getInviteRecords, modifyUser, getUserProfile, bindDouyinID, getPublicConfig
|
getUserTasks, getTaskProgress, getInviteRecords, modifyUser, getUserProfile, bindDouyinID, getPublicConfig
|
||||||
} from '../../api/appUser.js'
|
} from '../../api/appUser.js'
|
||||||
import { checkPhoneBoundSync } from '../../utils/checkPhone.js'
|
|
||||||
// #ifdef MP-TOUTIAO
|
// #ifdef MP-TOUTIAO
|
||||||
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
|
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-TOUTIAO
|
|
||||||
import customTabBar from '@/components/app-tab-bar.vue'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
// #ifdef MP-TOUTIAO
|
// #ifdef MP-TOUTIAO
|
||||||
customTabBarToutiao,
|
customTabBarToutiao,
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-TOUTIAO
|
|
||||||
customTabBar
|
|
||||||
// #endif
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -595,9 +585,6 @@ export default {
|
|||||||
}).catch(console.error)
|
}).catch(console.error)
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
// 检查手机号绑定状态(快速检查本地缓存)
|
|
||||||
if (!checkPhoneBoundSync()) return
|
|
||||||
|
|
||||||
this.loadUserInfo()
|
this.loadUserInfo()
|
||||||
},
|
},
|
||||||
onShareAppMessage() {
|
onShareAppMessage() {
|
||||||
@ -617,16 +604,18 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 检查是否已绑定手机号
|
|
||||||
checkPhoneBound() {
|
checkPhoneBound() {
|
||||||
if (!this.mobile) {
|
if (!this.mobile) {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '需要绑定手机号',
|
title: '需要绑定手机号',
|
||||||
content: '为了账号安全,请先绑定手机号',
|
content: '为了账号安全,请先绑定手机号',
|
||||||
showCancel: false,
|
showCancel: true,
|
||||||
|
cancelText: '取消',
|
||||||
confirmText: '去绑定',
|
confirmText: '去绑定',
|
||||||
success: () => {
|
success: (res) => {
|
||||||
uni.navigateTo({ url: '/pages/login/index?mode=sms' })
|
if (res.confirm) {
|
||||||
|
uni.navigateTo({ url: '/pages/login/index?mode=sms' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -2,13 +2,10 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="bg-decoration"></view>
|
<view class="bg-decoration"></view>
|
||||||
|
|
||||||
<!-- 自定义 tabBar -->
|
<!-- 自定义 tabBar (仅抖音小程序) -->
|
||||||
<!-- #ifdef MP-TOUTIAO -->
|
<!-- #ifdef MP-TOUTIAO -->
|
||||||
<customTabBarToutiao />
|
<customTabBarToutiao />
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<!-- #ifndef MP-TOUTIAO -->
|
|
||||||
<customTabBar />
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- [NEW] 全新左右布局布局容器 -->
|
<!-- [NEW] 全新左右布局布局容器 -->
|
||||||
<view class="shop-layout">
|
<view class="shop-layout">
|
||||||
@ -171,9 +168,6 @@ import { vibrateShort } from '@/utils/vibrate.js'
|
|||||||
// #ifdef MP-TOUTIAO
|
// #ifdef MP-TOUTIAO
|
||||||
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
|
import customTabBarToutiao from '@/components/app-tab-bar-toutiao.vue'
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-TOUTIAO
|
|
||||||
import customTabBar from '@/components/app-tab-bar.vue'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 由于是 setup 语法,组件会自动注册,无需手动声明
|
// 由于是 setup 语法,组件会自动注册,无需手动声明
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* 用于统一 一番赏、无限赏、对对碰 三种玩法的支付流程
|
* 用于统一 一番赏、无限赏、对对碰 三种玩法的支付流程
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createWechatOrder } from '../api/appUser'
|
import { createWechatOrder, createWechatAppOrder } from '../api/appUser'
|
||||||
import { hasPhoneBound } from './checkPhone'
|
import { hasPhoneBound } from './checkPhone'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,6 +17,16 @@ export function extractOrderNo(res) {
|
|||||||
return res.order_no || res.orderNo || res.data?.order_no || res.data?.orderNo || res.result?.order_no || res.result?.orderNo || null
|
return res.order_no || res.orderNo || res.data?.order_no || res.data?.orderNo || res.result?.order_no || res.result?.orderNo || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为 App 平台
|
||||||
|
*/
|
||||||
|
function isAppPlatform() {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
return true
|
||||||
|
// #endif
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行微信支付流程
|
* 执行微信支付流程
|
||||||
*
|
*
|
||||||
@ -30,6 +40,12 @@ export async function doWechatPayment({ orderNo, openid }) {
|
|||||||
throw new Error('订单号不能为空')
|
throw new Error('订单号不能为空')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// App 端支付流程
|
||||||
|
if (isAppPlatform()) {
|
||||||
|
return doAppWechatPayment({ orderNo })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小程序端支付流程
|
||||||
const finalOpenid = openid || uni.getStorageSync('openid')
|
const finalOpenid = openid || uni.getStorageSync('openid')
|
||||||
if (!finalOpenid) {
|
if (!finalOpenid) {
|
||||||
throw new Error('缺少OpenID,请重新登录')
|
throw new Error('缺少OpenID,请重新登录')
|
||||||
@ -64,6 +80,47 @@ export async function doWechatPayment({ orderNo, openid }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App 端微信支付流程
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {string} options.orderNo - 订单号
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function doAppWechatPayment({ orderNo }) {
|
||||||
|
// 1. 获取 APP 支付参数
|
||||||
|
const payRes = await createWechatAppOrder({ order_no: orderNo })
|
||||||
|
if (!payRes || !payRes.prepayid) {
|
||||||
|
throw new Error('获取支付参数失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 调起微信支付 (App 端使用 orderInfo 对象)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.requestPayment({
|
||||||
|
provider: 'wxpay',
|
||||||
|
orderInfo: {
|
||||||
|
appid: payRes.appid,
|
||||||
|
partnerid: payRes.partnerid,
|
||||||
|
prepayid: payRes.prepayid,
|
||||||
|
package: payRes.package || 'Sign=WXPay',
|
||||||
|
noncestr: payRes.noncestr,
|
||||||
|
timestamp: payRes.timestamp,
|
||||||
|
sign: payRes.sign
|
||||||
|
},
|
||||||
|
success: resolve,
|
||||||
|
fail: (err) => {
|
||||||
|
if (err?.errMsg && String(err.errMsg).includes('cancel')) {
|
||||||
|
const cancelErr = new Error('支付已取消')
|
||||||
|
cancelErr.cancelled = true
|
||||||
|
reject(cancelErr)
|
||||||
|
} else {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 完整支付流程(创建订单 + 支付)
|
* 完整支付流程(创建订单 + 支付)
|
||||||
*
|
*
|
||||||
@ -99,7 +156,6 @@ export async function executePaymentFlow({ createOrder, openid, onOrderCreated }
|
|||||||
*/
|
*/
|
||||||
export function checkLoginStatus() {
|
export function checkLoginStatus() {
|
||||||
const token = uni.getStorageSync('token')
|
const token = uni.getStorageSync('token')
|
||||||
const openid = uni.getStorageSync('openid')
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return { ok: false, message: '请先登录' }
|
return { ok: false, message: '请先登录' }
|
||||||
@ -110,6 +166,12 @@ export function checkLoginStatus() {
|
|||||||
return { ok: false, message: '请先绑定手机号' }
|
return { ok: false, message: '请先绑定手机号' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// App 端不需要 openid
|
||||||
|
if (isAppPlatform()) {
|
||||||
|
return { ok: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
const openid = uni.getStorageSync('openid')
|
||||||
if (!openid) {
|
if (!openid) {
|
||||||
return { ok: false, message: '缺少OpenID,请重新登录' }
|
return { ok: false, message: '缺少OpenID,请重新登录' }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const BASE_URL = 'http://127.0.0.1:9991'
|
//const BASE_URL = 'http://127.0.0.1:9991'
|
||||||
// const BASE_URL = 'https://kdy.1024tool.vip'
|
const BASE_URL = 'https://kdy.1024tool.vip'
|
||||||
let authModalShown = false
|
let authModalShown = false
|
||||||
|
|
||||||
function handleAuthExpired() {
|
function handleAuthExpired() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user