Browse Source

feat: 添加H5环境兼容性修复和功能优化

refactor(router): 重构H5路由处理逻辑
fix(share): 修复微信分享URL问题
feat(payment): 添加H5环境微信支付支持
style: 统一页面标题显示
docs: 更新页面配置文档
pull/9/head
前端-胡立永 3 weeks ago
parent
commit
7bf9c02b10
15 changed files with 1112 additions and 407 deletions
  1. +11
    -1
      components/base/navbar.vue
  2. +64
    -19
      main.js
  3. +4
    -2
      manifest.json
  4. +37
    -0
      mixins/configList.js
  5. +105
    -25
      pages.json
  6. +86
    -14
      pages/index/center.vue
  7. +1
    -2
      pages/index/index.vue
  8. +1
    -1
      pages_order/mine/coupon.vue
  9. +24
    -32
      pages_order/mine/memberCenter.vue
  10. +75
    -13
      pages_order/mine/runningWater.vue
  11. +115
    -5
      pages_order/mine/withdraw.vue
  12. +333
    -292
      pages_order/product/productDetail.vue
  13. +107
    -0
      utils/h5-fix.js
  14. +146
    -0
      utils/h5-router-fix.js
  15. +3
    -1
      utils/share.js

+ 11
- 1
components/base/navbar.vue View File

@ -90,10 +90,20 @@
},
data() {
return {
length : getCurrentPages().length
length : this.getPageLength()
};
},
methods : {
getPageLength() {
try {
const pages = getCurrentPages();
return pages ? pages.length : 1;
} catch(e) {
// H5 1
console.warn('获取页面栈长度失败:', e);
return 1;
}
},
toHome(){
if(this.length != 1){
return


+ 64
- 19
main.js View File

@ -27,31 +27,76 @@ Vue.component('navbar',navbar)
// #ifdef H5
//获取url中参数的方法
function GetQueryString(name) {
var url = window.location.href;
try {
var cs = url.split('?')[1]; //获取?之后的参数字符串
var cs_arr = cs.split('&'); //参数字符串分割为数组
for (var i = 0; i < cs_arr.length; i++) { //遍历数组,拿到json对象
if (cs_arr[i].split('=')[0] == name) {
sessionStorage.setItem('vid',cs_arr[i].split('=')[1]);
}
}
}catch(e){}
}
GetQueryString('vid');
import { fixH5Router } from '@/utils/h5-router-fix.js'
import { fixUrlParams } from '@/utils/h5-fix.js'
// 立即执行 H5 修复
fixH5Router();
fixUrlParams();
// 设置 Vue 全局错误处理器
Vue.config.errorHandler = function (err, vm, info) {
// 忽略已知的 H5 兼容性错误
if (err.message && (
err.message.includes("Cannot read property 'meta'") ||
err.message.includes('replaceState') ||
err.message.includes('showTabBar') ||
err.message.includes('History')
)) {
console.warn('H5兼容性错误已忽略:', err.message);
return;
}
// 其他错误正常输出
console.error('Vue Error:', err, vm, info);
};
import share from '@/utils/share.js'
share()
// #endif
const app = new Vue({
...App,
store,
})
app.$mount()
// H5 环境下的特殊处理
// #ifdef H5
Vue.config.silent = false;
// 在创建 Vue 实例前添加额外的错误处理
const originalConsoleError = console.error;
console.error = function(...args) {
// 过滤掉一些已知的 H5 兼容性错误
const message = args.join(' ');
if (message.includes("Cannot read property 'meta'") ||
message.includes('replaceState') ||
message.includes('showTabBar')) {
console.warn('H5兼容性警告:', message);
return;
}
originalConsoleError.apply(console, args);
};
// #endif
try {
const app = new Vue({
...App,
store,
})
app.$mount()
} catch(e) {
console.error('Vue 应用启动失败:', e);
// #ifdef H5
// H5 环境下的降级处理
setTimeout(() => {
try {
const app = new Vue({
...App,
store,
})
app.$mount()
} catch(retryError) {
console.error('Vue 应用重试启动失败:', retryError);
}
}, 100);
// #endif
}
// #endif
// #ifdef VUE3


+ 4
- 2
manifest.json View File

@ -101,7 +101,9 @@
}
},
"router" : {
"mode" : "hash"
}
"mode" : "hash",
"base" : "/"
},
"publicPath" : "./"
}
}

+ 37
- 0
mixins/configList.js View File

@ -15,6 +15,11 @@ export default {
computed: {
...mapState(['configList', 'userInfo', 'riceInfo']),
},
onLoad(query) {
if (query.shareId) {
uni.setStorageSync('shareId', query.shareId)
}
},
// 定义全局分享
// 1.发送给朋友
onShareAppMessage(res) {
@ -41,7 +46,39 @@ export default {
o.path = this.Gshare.path + '?shareId=' + this.userInfo.id
}
return o
},
onLoad(query) {
if (query.shareId) {
uni.setStorageSync('shareId', query.shareId)
}
},
onShow() {
this.setupWeixinShare()
},
methods: {
// 设置微信分享内容
setupWeixinShare() {
if (!this.$jWeixin) return
this.$jWeixin.ready(() => {
const shareData = {
title: this.Gshare.title || '愈然工坊',
desc: this.Gshare.desc || '愈然工坊,温柔呵护每一刻!',
link: this.Gshare.path || location.href.split('#')[0],
imgUrl: this.Gshare.imageUrl || '',
success: () => {
},
cancel: () => {
}
}
// 分享给朋友
this.$jWeixin.updateAppMessageShareData(shareData)
// 分享到朋友圈
this.$jWeixin.updateTimelineShareData(shareData)
// 分享到微博
this.$jWeixin.onMenuShareWeibo(shareData)
})
},
}
}

+ 105
- 25
pages.json View File

@ -1,35 +1,35 @@
{
"pages": [{
"pages": [ {
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "",
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/index/order",
"style": {
"navigationBarTitleText": "",
"navigationBarTitleText": "订单",
"enablePullDownRefresh": true
}
},
{
"path": "pages/index/category",
"style": {
"navigationBarTitleText": "",
"navigationBarTitleText": "商品",
"enablePullDownRefresh": true
}
},
{
"path": "pages/index/center",
"style": {
"navigationBarTitleText": ""
"navigationBarTitleText": "我的"
}
},
{
"path": "pages/index/cart",
"style": {
"navigationBarTitleText": "",
"navigationBarTitleText": "购物车",
"enablePullDownRefresh": true
}
}
@ -44,81 +44,141 @@
"root": "pages_order",
"pages": [
{
"path": "product/productDetail"
"path": "product/productDetail",
"style": {
"navigationBarTitleText": "商品详情"
}
},
{
"path": "order/createOrder"
"path": "order/createOrder",
"style": {
"navigationBarTitleText": "创建订单"
}
},
{
"path": "order/verifyOrder",
"style": {
"navigationBarTitleText": "核销订单",
"enablePullDownRefresh": true
}
},
{
"path": "order/orderDetail",
"style": {
"navigationBarTitleText": "订单详情",
"enablePullDownRefresh": true
}
},
{
"path": "mine/memberCenter"
"path": "mine/memberCenter",
"style": {
"navigationBarTitleText": "会员中心"
}
},
{
"path": "mine/partner"
"path": "mine/partner",
"style": {
"navigationBarTitleText": "合作伙伴"
}
},
{
"path": "mine/withdraw"
"path": "mine/withdraw",
"style": {
"navigationBarTitleText": "提现"
}
},
{
"path": "mine/runningWater"
"path": "mine/runningWater",
"style": {
"navigationBarTitleText": "流水记录"
}
},
{
"path": "mine/promotion"
"path": "mine/promotion",
"style": {
"navigationBarTitleText": "推广中心"
}
},
{
"path": "mine/promotionH5"
"path": "mine/promotionH5",
"style": {
"navigationBarTitleText": "推广中心"
}
},
{
"path": "mine/coupon"
"path": "mine/coupon",
"style": {
"navigationBarTitleText": "优惠券"
}
},
{
"path": "mine/voucher"
"path": "mine/voucher",
"style": {
"navigationBarTitleText": "代金券"
}
},
{
"path": "mine/verifyVoucher"
"path": "mine/verifyVoucher",
"style": {
"navigationBarTitleText": "核销代金券"
}
},
{
"path": "mine/signIn"
"path": "mine/signIn",
"style": {
"navigationBarTitleText": "签到"
}
},
{
"path": "mine/pointsRecord"
"path": "mine/pointsRecord",
"style": {
"navigationBarTitleText": "积分记录"
}
},
{
"path": "mine/cooperation",
"style": {
"navigationBarTitleText": "合作",
"enablePullDownRefresh": true
}
},
{
"path": "mine/verifyRecord"
"path": "mine/verifyRecord",
"style": {
"navigationBarTitleText": "核销记录"
}
},
{
"path": "auth/wxLogin"
"path": "auth/wxLogin",
"style": {
"navigationBarTitleText": "微信登录"
}
},
{
"path": "auth/wxUserInfo"
"path": "auth/wxUserInfo",
"style": {
"navigationBarTitleText": "用户信息"
}
},
{
"path": "auth/loginAndRegisterAndForgetPassword"
"path": "auth/loginAndRegisterAndForgetPassword",
"style": {
"navigationBarTitleText": "登录注册"
}
},
{
"path": "mine/help"
"path": "mine/help",
"style": {
"navigationBarTitleText": "帮助中心"
}
},
{
"path": "home/contact"
"path": "home/contact",
"style": {
"navigationBarTitleText": "联系我们"
}
}
]
}],
@ -129,5 +189,25 @@
"backgroundColor": "#F8F8F8",
"navigationStyle": "custom"
},
"tabBar": {
"custom": true,
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "pages/index/index",
"text": "首页"
}, {
"pagePath": "pages/index/category",
"text": "商品"
}, {
"pagePath": "pages/index/order",
"text": "订单"
}, {
"pagePath": "pages/index/center",
"text": "我的"
}]
},
"uniIdRouter": {}
}

+ 86
- 14
pages/index/center.vue View File

@ -196,27 +196,98 @@
}
},
methods: {
// #ifdef H5
//
isInWechat() {
const ua = navigator.userAgent.toLowerCase()
return ua.indexOf('micromessenger') !== -1
},
// #endif
clickNo() {
uni.showModal({
title: '暂未开放',
})
},
logout(){
uni.showModal({
title: '确认退出登录吗',
success : (r) => {
if (r.confirm) {
this.$store.commit('logout', true)
}
}
})
uni.showModal({
title: '确认退出登录吗',
success : (r) => {
if (r.confirm) {
this.$store.commit('logout', true)
}
}
})
},
openServicePopup(phone, title) {
// todo
this.$refs.customerServicePopup.open(phone, title)
},
onScan() {
// todo check
// #ifdef H5
// H5使JSSDK
if (!this.isInWechat()) {
uni.showToast({
title: '请在微信中打开',
icon: 'none'
})
return
}
if (this.$jWeixin) {
this.$jWeixin.ready(() => {
this.$jWeixin.scanQRCode({
needResult: 1, // 01
scanType: ["qrCode","barCode"], //
success: (res) => {
console.log('H5扫码结果:', res);
if (res.resultStr) {
//
const [id, type] = res.resultStr.split(',')
console.log('扫码结果:', res.resultStr);
this.$fetch('overOrder', {
orderId : id,
type, // type0- 1-
}).then(() => {
uni.showToast({
title: '核销成功',
icon: 'none'
})
}).catch(err => {
uni.showToast({
title: '核销失败',
icon: 'none'
})
})
}
},
fail: (err) => {
console.error('H5扫码失败:', err);
uni.showToast({
title: '扫码失败',
icon: 'none'
})
}
})
})
this.$jWeixin.error((res) => {
console.error('微信配置失败:', res)
uni.showToast({
title: '微信配置失败',
icon: 'none'
})
})
} else {
uni.showToast({
title: '微信环境异常',
icon: 'none'
})
}
// #endif
// #ifdef MP-WEIXIN
// 使uni.scanCode
uni.scanCode({
success: (res) => {
console.log(res);
@ -225,10 +296,6 @@
const [id, type] = res.result.split(',')
console.log('扫码结果:', res.result);
console.log('--overOrder', {
orderId : id,
type, // type0- 1-
})
this.$fetch('overOrder', {
orderId : id,
type, // type0- 1-
@ -237,18 +304,23 @@
title: '核销成功',
icon: 'none'
})
}).catch(err => {
uni.showToast({
title: '核销失败',
icon: 'none'
})
})
}
},
fail: (err) => {
console.error('扫码失败:', err);
uni.showToast({
title: '扫码失败',
icon: 'none'
})
}
});
// #endif
},
async fetchCouponData() {
try {


+ 1
- 2
pages/index/index.vue View File

@ -17,10 +17,9 @@
<view class="swipe">
<uv-swiper :list="bannerList" indicator indicatorMode="dot" height="340rpx" keyName="image"></uv-swiper>
</view>
<!-- 推荐 -> 会员 -->
<view v-if="!role" class="card flex recommend-member" @click="$utils.navigateTo('/pages/index/memberCenter')" >
<view v-if="!role" class="card flex recommend-member" @click="$utils.navigateTo('/pages_order/mine/memberCenter')" >
<view class="flex" style="flex: 1;">
<image class="icon-member" style="" src="@/static/image/home/icon-member.png"></image>
<!-- todo: check -->


+ 1
- 1
pages_order/mine/coupon.vue View File

@ -32,7 +32,7 @@
></uv-tabs>
</view>
<couponList ref="couponList" :list="list" :status="status"></couponList>
<couponList ref="couponList" :status="status"></couponList>
</view>
</template>


+ 24
- 32
pages_order/mine/memberCenter.vue View File

@ -77,6 +77,7 @@
<script>
import progress from '../components/progress.vue'
import mixinsConfigList from '@/mixins/configList.js'
import {
mapGetters,
@ -85,6 +86,7 @@
export default {
name: "MemberCenter",
mixins: [mixinsConfigList],
components: {
progress,
},
@ -95,7 +97,9 @@
}
},
onShow() {
// #ifdef H5
this.fetchCargeOptions()
// #if
},
computed: {
...mapGetters(['role']),
@ -120,17 +124,22 @@
this.selectedChargeId = id
},
async submit() {
// todo: check jump to create order ?
try {
let params = {
comoId: this.selectedChargeId
}
let result = await this.$fetch('addVip', params)
// #ifdef H5
// H5使JSSDK
await this.$wxPay({result})
// #endif
// #ifdef MP-WEIXIN
// 使
await uni.requestPaymentWxPay({result})
// #endif
uni.showToast({
title: '充值成功',
@ -139,36 +148,19 @@
setTimeout(uni.navigateBack, 800, -1)
} catch (err) {
console.error('支付失败:', err)
if (err.type === 'cancel') {
uni.showToast({
title: '用户取消支付',
icon: 'none'
})
} else {
uni.showToast({
title: '支付失败',
icon: 'none'
})
}
}
return
// this.$api('addVip', {
// comoId: this.selectedChargeId
// }, res => {
// if (res.code == 200) {
// uni.requestPayment({
// provider: 'wxpay', //
// timeStamp: res.result.timeStamp, //
// nonceStr: res.result.nonceStr, //
// package: res.result.packageValue,
// signType: res.result.signType, //
// paySign: res.result.paySign, //
// success: function(res) {
// console.log('', res);
// },
// fail: function(err) {
// console.log('', err);
// uni.showToast({
// icon: 'none',
// title: ""
// })
// }
// });
// }
// })
}
}
}


+ 75
- 13
pages_order/mine/runningWater.vue View File

@ -55,6 +55,8 @@
return this.$dayjs(this.selectedTime).format("YYYY年M月")
}
},
onLoad() {
},
methods: {
//
openCalendars() {
@ -91,35 +93,86 @@
console.log('--onTimeChange', e)
},
requestMerchantTransferH5(item, fn) {
wx.ready(function() {
wx.checkJsApi({
// #ifdef H5
// 使VuejWeixin
const jWeixin = this.$jWeixin
if (!jWeixin) {
console.error('jWeixin未初始化')
uni.showToast({
title: '微信环境异常',
icon: 'none'
})
return
}
jWeixin.ready(() => {
jWeixin.checkJsApi({
jsApiList: ['requestMerchantTransfer'],
success: function(res) {
success: (res) => {
if (res.checkResult['requestMerchantTransfer']) {
WeixinJSBridge.invoke('requestMerchantTransfer', {
// H5使WeixinJSBridge
if (typeof WeixinJSBridge !== 'undefined') {
WeixinJSBridge.invoke('requestMerchantTransfer', {
appId: this.$config.appId,
mchId: this.$config.mchId,
package: item.packageInfo,
},
function(res) {
}, (res) => {
if (res.err_msg === 'requestMerchantTransfer:ok') {
// res.err_msgsuccess
//
fn && fn()
} else {
console.log('提现失败:', res)
uni.showToast({
title: '提现失败,请稍后再试',
icon: 'none'
})
}
}
);
})
} else {
console.error('WeixinJSBridge未找到')
uni.showToast({
title: '请在微信中打开',
icon: 'none'
})
}
} else {
alert('你的微信版本过低,请更新至最新版本。');
uni.showToast({
title: '你的微信版本过低,请更新至最新版本',
icon: 'none'
})
}
},
fail: (error) => {
console.error('checkJsApi失败:', error)
uni.showToast({
title: '微信接口检查失败',
icon: 'none'
})
}
});
});
})
})
jWeixin.error((res) => {
console.error('微信配置失败:', res)
uni.showToast({
title: '微信配置失败',
icon: 'none'
})
})
// #endif
},
withdraw(item) {
// #ifdef H5
//
if (!this.isInWechat()) {
uni.showToast({
title: '请在微信中打开',
icon: 'none'
})
return
}
// H5使H5
this.requestMerchantTransferH5(item, () => {
this.$store.commit('getUserInfo')
this.$store.commit('getRiceInfo')
@ -132,6 +185,8 @@
return
// #endif
// #ifdef MP-WEIXIN
//
//
if (!wx.canIUse('requestMerchantTransfer')) {
wx.showModal({
@ -167,6 +222,13 @@
console.log('requestMerchantTransfer完成:', res);
}
});
// #endif
},
// #ifdef H5
//
isInWechat() {
const ua = navigator.userAgent.toLowerCase()
return ua.indexOf('micromessenger') !== -1
},
}
}


+ 115
- 5
pages_order/mine/withdraw.vue View File

@ -7,7 +7,7 @@
<!-- 佣金信息 -->
<view class="info">
<image class="info-bg" src="../static/withdraw/bg.png"></image>
<image class="info-bg" src="/pages_order/static/withdraw/bg.png"></image>
<view class="info-content">
<view class="label">佣金</view>
@ -68,11 +68,12 @@
<script>
import mixinsList from '@/mixins/list.js'
import mixinsConfigList from '@/mixins/configList.js'
import {
mapState
} from 'vuex'
export default {
mixins: [mixinsList],
mixins: [mixinsList, mixinsConfigList],
computed: {
...mapState(['userInfo', 'riceInfo']),
},
@ -94,11 +95,20 @@
}
}
},
onLoad() {
},
onShow() {
this.$store.commit('getUserInfo')
this.$store.commit('getRiceInfo')
},
methods: {
// #ifdef H5
//
isInWechat() {
const ua = navigator.userAgent.toLowerCase()
return ua.indexOf('micromessenger') !== -1
},
// #endif
withdraw() { //
if (!this.form.name) {
uni.showToast({
@ -152,6 +162,24 @@
if (res.code === 200) {
//
if (res.result && res.result.state === 'WAIT_USER_CONFIRM' && res.result.packageInfo && res.result.outBillNo) {
let data = res.result
// #ifdef H5
// H5
if (!this.isInWechat()) {
uni.showToast({
title: '请在微信中打开',
icon: 'none'
})
return
}
// H5使H5
this.requestMerchantTransferH5(res.result.packageInfo, data.outBillNo)
return
// #endif
// #ifdef MP-WEIXIN
//
if (!wx.canIUse('requestMerchantTransfer')) {
wx.showModal({
@ -160,8 +188,6 @@
});
return
}
let data = res.result
// API
wx.requestMerchantTransfer({
@ -194,6 +220,7 @@
console.log('requestMerchantTransfer完成:', res);
}
});
// #endif
} else {
uni.showToast({
title: '提现成功',
@ -213,7 +240,90 @@
},
// #ifdef H5
// H5
requestMerchantTransferH5(packageInfo, outBillNo, callback) {
// 使VuejWeixin
const jWeixin = this.$jWeixin
if (!jWeixin) {
console.error('jWeixin未初始化')
uni.showToast({
title: '微信环境异常',
icon: 'none'
})
return
}
jWeixin.ready(() => {
jWeixin.checkJsApi({
jsApiList: ['requestMerchantTransfer'],
success: (res) => {
if (res.checkResult['requestMerchantTransfer']) {
// H5使WeixinJSBridge
if (typeof WeixinJSBridge !== 'undefined') {
WeixinJSBridge.invoke('requestMerchantTransfer', {
appId: this.$config.appId,
mchId: this.$config.mchId,
package: packageInfo,
}, (res) => {
if (res.err_msg === 'requestMerchantTransfer:ok') {
//
uni.showToast({
title: '提现申请已提交',
icon: 'success'
})
this.form.money = ''
this.form.name = ''
this.$api('getMoney', {
id: outBillNo,
}).then(res => {
this.$store.commit('getUserInfo')
this.$store.commit('getRiceInfo')
})
callback && callback()
} else {
console.log('提现失败:', res)
uni.showToast({
title: '提现失败,请稍后再试',
icon: 'none'
})
}
})
} else {
console.error('WeixinJSBridge未找到')
uni.showToast({
title: '请在微信中打开',
icon: 'none'
})
}
} else {
uni.showToast({
title: '你的微信版本过低,请更新至最新版本',
icon: 'none'
})
}
},
fail: (error) => {
console.error('checkJsApi失败:', error)
uni.showToast({
title: '微信接口检查失败',
icon: 'none'
})
}
})
})
jWeixin.error((res) => {
console.error('微信配置失败:', res)
uni.showToast({
title: '微信配置失败',
icon: 'none'
})
})
},
// #endif
parameterVerification() { //
let {


+ 333
- 292
pages_order/product/productDetail.vue View File

@ -1,302 +1,343 @@
<template>
<view class="page">
<!-- 导航栏 -->
<navbar title="商品详情" leftClick @leftClick="$utils.navigateBack" color="#fff" />
<uv-swiper :list="bannerList" indicator indicatorMode="dot" height="475rpx"></uv-swiper>
<view class="overview">
<view class="flex sale">
<view class="flex price" :class="[role]">
<view>
<text class="price-unit"></text>
<text>{{ productDetail.price }}</text>
</view>
<view class="flex tag" v-if="role">
<image class="icon" :src="vipInfo.massageVipCombo.imagePrice"></image>
<text>{{ vipInfo.massageVipCombo.title }}</text>
</view>
</view>
<view>
<text>{{ `已售出:${productDetail.sales}` }}</text>
</view>
</view>
<view class="title">
<text>{{ productDetail.title }}</text>
</view>
<view class="flex desc">
<view v-for="tag in tags" :key="tag" class="flex tag">
<view class="dot"></view>
<text>{{ tag }}</text>
</view>
</view>
</view>
<!-- 商品详情 -->
<view class="detail">
<view class="header">
<cardTitle>商品详情</cardTitle>
</view>
<uv-parse :content="productDetail.details"></uv-parse>
</view>
<!-- 分享和购买按钮 -->
<view class="flex bar">
<button plain class="flex flex-column btn btn-share" open-type="share">
<image class="btn-share-icon" src="../static/productDetail/icon-share.png"></image>
<text>分享</text>
</button>
<view class="flex count">
<text>合计</text>
<view class="price">
<text class="price-unit">¥</text>
<!-- todo: check -->
<text>{{ productDetail.price }}</text>
</view>
</view>
<button plain class="btn btn-pay" @click="submit">立即支付</button>
</view>
</view>
<view class="page">
<!-- 导航栏 -->
<navbar title="商品详情" leftClick @leftClick="$utils.navigateBack" color="#fff" />
<uv-swiper :list="bannerList" indicator indicatorMode="dot" height="475rpx"></uv-swiper>
<view class="overview">
<view class="flex sale">
<view class="flex price" :class="[role]">
<view>
<text class="price-unit"></text>
<text>{{ productDetail.price }}</text>
</view>
<view class="flex tag" v-if="role">
<image class="icon" :src="vipInfo.massageVipCombo.imagePrice"></image>
<text>{{ vipInfo.massageVipCombo.title }}</text>
</view>
</view>
<view>
<text>{{ `已售出:${productDetail.sales}` }}</text>
</view>
</view>
<view class="title">
<text>{{ productDetail.title }}</text>
</view>
<view class="flex desc">
<view v-for="tag in tags" :key="tag" class="flex tag">
<view class="dot"></view>
<text>{{ tag }}</text>
</view>
</view>
</view>
<!-- 商品详情 -->
<view class="detail">
<view class="header">
<cardTitle>商品详情</cardTitle>
</view>
<uv-parse :content="productDetail.details"></uv-parse>
</view>
<!-- 分享和购买按钮 -->
<view class="flex bar">
<!-- #ifdef H5 -->
<button plain class="flex flex-column btn btn-share" @click="handleShare">
<image class="btn-share-icon" src="../static/productDetail/icon-share.png"></image>
<text>分享</text>
</button>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<button plain class="flex flex-column btn btn-share" open-type="share">
<image class="btn-share-icon" src="../static/productDetail/icon-share.png"></image>
<text>分享</text>
</button>
<!-- #endif -->
<view class="flex count">
<text>合计</text>
<view class="price">
<text class="price-unit">¥</text>
<!-- todo: check -->
<text>{{ productDetail.price }}</text>
</view>
</view>
<button plain class="btn btn-pay" @click="submit">立即支付</button>
</view>
</view>
</template>
<script>
import { mapGetters, mapState } from 'vuex'
import cardTitle from '@/components/base/cardTitle.vue'
export default {
components: {
cardTitle,
},
data() {
return {
productDetail: {
image: '',
details: '',
},
id: 0,
}
},
computed: {
...mapGetters(['role']),
...mapState(['vipInfo']),
// todo: check
bannerList() {
const { image } = this.productDetail
if (!image) {
return []
}
return Array.isArray(image) ? image : image.split(',')
},
tags() {
const { tag } = this.productDetail
return tag?.split('、') || []
}
},
onLoad(args) {
this.id = args.id
this.fetchProductDetail(this.id)
},
onShow() {
},
import { mapGetters, mapState } from 'vuex'
import cardTitle from '@/components/base/cardTitle.vue'
export default {
components: {
cardTitle,
},
data() {
return {
productDetail: {
image: '',
details: '',
},
id: 0,
}
},
computed: {
...mapGetters(['role']),
...mapState(['vipInfo']),
// todo: check
bannerList() {
const { image } = this.productDetail
if (!image) {
return []
}
return Array.isArray(image) ? image : image.split(',')
},
tags() {
const { tag } = this.productDetail
return tag?.split('、') || []
}
},
onLoad(args) {
this.id = args.id
this.fetchProductDetail(this.id)
// ID
if (args.shareId) {
uni.setStorageSync('shareId', args.shareId)
}
},
onShow() {
//
this.updateShareContent()
},
onShareAppMessage(res) {
const {
title,
} = this.productDetail
let o = {
title: title,
// todo: check
imageUrl: bannerList[0],
query: `id=${this.productDetail.id}`,
}
return o
//
return {
title: this.productDetail.title || '愈然工坊',
imageUrl: this.bannerList[0] || '',
path: `/pages_order/product/productDetail?id=${this.productDetail.id}${this.userInfo.id ? '&shareId=' + this.userInfo.id : ''}`,
}
},
methods: {
//
async fetchProductDetail(id) {
try {
this.productDetail = await this.$fetch('queryProductDetail', { id })
} catch (err) {
}
},
//
submit() {
this.$store.commit('setPayOrderProduct', [
this.productDetail
])
this.$utils.navigateTo('/pages_order/order/createOrder')
},
}
}
methods: {
// #ifdef H5
// H5
handleShare() {
uni.showToast({
title: '请点击右上角分享',
icon: 'none'
})
},
// #endif
//
updateShareContent() {
if (this.productDetail.id) {
//
this.Gshare.title = this.productDetail.title || '愈然工坊'
this.Gshare.desc = this.productDetail.title || '愈然工坊,温柔呵护每一刻!'
this.Gshare.path = `/pages_order/product/productDetail?id=${this.productDetail.id}`
this.Gshare.imageUrl = this.bannerList[0] || ''
// #ifdef H5
// H5
this.setupWeixinShare()
// #endif
}
},
//
async fetchProductDetail(id) {
try {
this.productDetail = await this.$fetch('queryProductDetail', { id })
//
this.updateShareContent()
} catch (err) {
}
},
//
submit() {
this.$store.commit('setPayOrderProduct', [
this.productDetail
])
this.$utils.navigateTo('/pages_order/order/createOrder')
},
}
}
</script>
<style scoped lang="scss">
$bar-height: 132rpx;
.page {
padding-bottom: calc(#{$bar-height} + env(safe-area-inset-bottom));
background-color: #F3F3F3;
/deep/ .nav-bar__view {
background-image: linear-gradient(#84A73F, #D8FF8F);
}
.overview {
padding: 16rpx 31rpx 18rpx 24rpx;
background-color: $uni-fg-color;
.sale {
justify-content: space-between;
color: #949494;
font-size: 20rpx;
.price {
color: #FF2A2A;
font-size: 45rpx;
&-unit {
font-size: 30rpx;
margin-right: 3rpx;
}
.tag {
font-size: 18rpx;
font-weight: 700;
padding: 9rpx 12rpx 9rpx 19rpx;
margin-left: 15rpx;
.icon {
width: 27rpx;
height: 19rpx;
margin-right: 3rpx;
}
}
&.member-personal {
color: $uni-color-light;
.tag {
background-color: rgba($color: #D8FF8F, $alpha: 0.72);
}
}
&.member-business {
color: #FFB465;
.tag {
background-color: rgba($color: #FFFBC4, $alpha: 0.72);
}
}
}
}
.title {
color: #3A3A3A;
font-size: 28rpx;
font-weight: 700;
margin-top: 15rpx;
}
.desc {
justify-content: space-between;
margin-top: 20rpx;
.tag {
color: #949494;
font-size: 22rpx;
.dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background-color: #949494;
margin-right: 16rpx;
}
}
}
}
.detail {
margin-top: 12rpx;
padding: 18rpx 28rpx;
background-color: $uni-fg-color;
.header {
margin-bottom: 30rpx;
}
}
.bar {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
height: $bar-height;
padding-bottom: env(safe-area-inset-bottom);
background-color: $uni-fg-color;
.count {
flex: 1;
color: #000000;
font-size: 28rpx;
margin-left: 29rpx;
padding-left: 40rpx;
border-left: 1rpx solid #B3997E;
justify-content: flex-start;
.price {
color: #FF2A2A;
font-size: 30rpx;
&-unit {
font-size: 18rpx;
}
}
}
.btn {
border: none;
line-height: 1;
background-color: transparent;
padding: 0;
width: auto;
height: auto;
margin: 0;
&-share {
margin-left: 58rpx;
color: #000000;
font-size: 22rpx;
&-icon {
width: 48rpx;
height: 50rpx;
margin-bottom: 11rpx;
}
}
&-pay {
margin-right: 27rpx;
padding: 24rpx 137rpx;
color: $uni-text-color-inverse;
font-size: 28rpx;
border-radius: 44rpx;
background-image: linear-gradient(to right, #84A73F, #D8FF8F);
}
}
}
}
$bar-height: 132rpx;
.page {
padding-bottom: calc(#{$bar-height} + env(safe-area-inset-bottom));
background-color: #F3F3F3;
/deep/ .nav-bar__view {
background-image: linear-gradient(#84A73F, #D8FF8F);
}
.overview {
padding: 16rpx 31rpx 18rpx 24rpx;
background-color: $uni-fg-color;
.sale {
justify-content: space-between;
color: #949494;
font-size: 20rpx;
.price {
color: #FF2A2A;
font-size: 45rpx;
&-unit {
font-size: 30rpx;
margin-right: 3rpx;
}
.tag {
font-size: 18rpx;
font-weight: 700;
padding: 9rpx 12rpx 9rpx 19rpx;
margin-left: 15rpx;
.icon {
width: 27rpx;
height: 19rpx;
margin-right: 3rpx;
}
}
&.member-personal {
color: $uni-color-light;
.tag {
background-color: rgba($color: #D8FF8F, $alpha: 0.72);
}
}
&.member-business {
color: #FFB465;
.tag {
background-color: rgba($color: #FFFBC4, $alpha: 0.72);
}
}
}
}
.title {
color: #3A3A3A;
font-size: 28rpx;
font-weight: 700;
margin-top: 15rpx;
}
.desc {
justify-content: space-between;
margin-top: 20rpx;
.tag {
color: #949494;
font-size: 22rpx;
.dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background-color: #949494;
margin-right: 16rpx;
}
}
}
}
.detail {
margin-top: 12rpx;
padding: 18rpx 28rpx;
background-color: $uni-fg-color;
.header {
margin-bottom: 30rpx;
}
}
.bar {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
height: $bar-height;
padding-bottom: env(safe-area-inset-bottom);
background-color: $uni-fg-color;
.count {
flex: 1;
color: #000000;
font-size: 28rpx;
margin-left: 29rpx;
padding-left: 40rpx;
border-left: 1rpx solid #B3997E;
justify-content: flex-start;
.price {
color: #FF2A2A;
font-size: 30rpx;
&-unit {
font-size: 18rpx;
}
}
}
.btn {
border: none;
line-height: 1;
background-color: transparent;
padding: 0;
width: auto;
height: auto;
margin: 0;
&-share {
margin-left: 58rpx;
color: #000000;
font-size: 22rpx;
&-icon {
width: 48rpx;
height: 50rpx;
margin-bottom: 11rpx;
}
}
&-pay {
margin-right: 27rpx;
padding: 24rpx 137rpx;
color: $uni-text-color-inverse;
font-size: 28rpx;
border-radius: 44rpx;
background-image: linear-gradient(to right, #84A73F, #D8FF8F);
}
}
}
}
</style>

+ 107
- 0
utils/h5-fix.js View File

@ -0,0 +1,107 @@
// H5 环境修复
export function fixH5Environment() {
// #ifdef H5
try {
// 修复路由 meta 属性访问问题
const originalGetCurrentPages = getCurrentPages;
window.getCurrentPages = function() {
try {
const pages = originalGetCurrentPages();
// 确保页面对象有基本的路由信息
if (pages && pages.length > 0) {
pages.forEach(page => {
if (page && !page.route) {
page.route = page.$page?.fullPath || '/';
}
if (page && page.route && !page.$page?.meta) {
page.$page = page.$page || {};
page.$page.meta = page.$page.meta || {};
}
});
}
return pages;
} catch(e) {
console.warn('getCurrentPages 修复失败:', e);
return [];
}
};
// 修复 History API
const originalReplaceState = window.history.replaceState;
window.history.replaceState = function(state, title, url) {
try {
// 确保 URL 格式正确
if (url && typeof url === 'string') {
// 修复格式不正确的 URL
if (url.startsWith('https:/#/')) {
url = url.replace('https:/#/', window.location.origin + '/#/');
}
// 如果 URL 不包含完整的域名,添加基础路径
if (url.startsWith('#/')) {
url = window.location.origin + window.location.pathname + url;
}
}
return originalReplaceState.call(this, state, title, url);
} catch(e) {
console.warn('History.replaceState 修复失败:', e);
return null;
}
};
// 修复路由跳转相关问题
const originalNavigateTo = uni.navigateTo;
uni.navigateTo = function(options) {
try {
return originalNavigateTo(options);
} catch(e) {
console.warn('uni.navigateTo 失败:', e);
// 降级处理
window.location.href = window.location.origin + window.location.pathname + '#' + options.url;
}
};
const originalReLaunch = uni.reLaunch;
uni.reLaunch = function(options) {
try {
return originalReLaunch(options);
} catch(e) {
console.warn('uni.reLaunch 失败:', e);
// 降级处理
window.location.href = window.location.origin + window.location.pathname + '#' + options.url;
}
};
console.log('H5 环境修复完成');
} catch(e) {
console.warn('H5 环境修复失败:', e);
}
// #endif
}
// 修复 URL 参数解析
export function fixUrlParams() {
// #ifdef H5
try {
function GetQueryString(name) {
const url = window.location.href;
try {
const cs = url.split('?')[1];
if (cs) {
const cs_arr = cs.split('&');
for (let i = 0; i < cs_arr.length; i++) {
if (cs_arr[i].split('=')[0] === name) {
sessionStorage.setItem('vid', cs_arr[i].split('=')[1]);
}
}
}
} catch(e) {
console.warn('URL参数解析失败:', e);
}
}
GetQueryString('vid');
} catch(e) {
console.warn('URL参数修复失败:', e);
}
// #endif
}

+ 146
- 0
utils/h5-router-fix.js View File

@ -0,0 +1,146 @@
// H5 路由和环境修复
// #ifdef H5
// 存储原始方法
let originalGetCurrentPages;
let originalHistoryReplaceState;
let isFixed = false;
// 修复H5环境路由问题的主函数
export function fixH5Router() {
if (isFixed) return;
try {
// 1. 修复 getCurrentPages 方法
if (typeof getCurrentPages === 'function') {
originalGetCurrentPages = getCurrentPages;
window.getCurrentPages = function() {
try {
const pages = originalGetCurrentPages();
if (pages && Array.isArray(pages)) {
// 确保每个页面对象有必要的属性
pages.forEach((page, index) => {
if (!page.route) {
page.route = page.$page?.fullPath || `page_${index}`;
}
if (!page.options) {
page.options = {};
}
// 确保页面有 meta 属性
if (page.$page && !page.$page.meta) {
page.$page.meta = {};
}
});
}
return pages || [];
} catch(e) {
console.warn('getCurrentPages 执行失败:', e);
return [{
route: '/',
options: {},
$page: { meta: {} }
}];
}
};
}
// 2. 修复 History API
if (window.history && window.history.replaceState) {
originalHistoryReplaceState = window.history.replaceState;
window.history.replaceState = function(state, title, url) {
try {
// 修复 URL 格式问题
if (url && typeof url === 'string') {
// 处理错误的 URL 格式
if (url.includes('https:/#/')) {
url = url.replace(/https:\/+#\//, window.location.origin + '/#/');
}
// 确保相对路径的正确性
if (url.startsWith('#/')) {
url = window.location.origin + window.location.pathname + url;
}
// 处理双斜杠问题
url = url.replace(/([^:]\/)\/+/g, '$1');
}
return originalHistoryReplaceState.call(this, state, title, url);
} catch(e) {
console.warn('History.replaceState 修复执行失败:', e);
// 忽略错误,不执行原方法
return null;
}
};
}
// 3. 添加全局错误处理
const originalOnError = window.onerror;
window.onerror = function(message, source, lineno, colno, error) {
// 过滤已知的H5兼容性错误
if (typeof message === 'string') {
if (message.includes("Cannot read property 'meta'") ||
message.includes('replaceState') ||
message.includes('showTabBar') ||
message.includes('History')) {
console.warn('H5兼容性错误已忽略:', message);
return true; // 阻止默认错误处理
}
}
// 其他错误交给原处理器
if (originalOnError) {
return originalOnError(message, source, lineno, colno, error);
}
return false;
};
// 4. 修复 URL 哈希处理
if (window.location.hash === '' || window.location.hash === '#') {
window.location.hash = '#/';
}
// 5. 监听 hashchange 事件,确保路由正常
window.addEventListener('hashchange', function(e) {
try {
const hash = window.location.hash;
if (!hash || hash === '#') {
window.location.hash = '#/';
}
} catch(e) {
console.warn('hashchange 处理失败:', e);
}
});
isFixed = true;
console.log('H5 路由修复完成');
} catch(e) {
console.warn('H5 路由修复失败:', e);
}
}
// 恢复原始方法(用于调试)
export function restoreH5Router() {
if (!isFixed) return;
try {
if (originalGetCurrentPages) {
window.getCurrentPages = originalGetCurrentPages;
}
if (originalHistoryReplaceState) {
window.history.replaceState = originalHistoryReplaceState;
}
isFixed = false;
console.log('H5 路由修复已恢复');
} catch(e) {
console.warn('H5 路由恢复失败:', e);
}
}
// 页面加载完成后自动执行修复
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', fixH5Router);
} else {
fixH5Router();
}
// #endif

+ 3
- 1
utils/share.js View File

@ -6,7 +6,9 @@ import Vue from 'vue'
function share() { //微信分享
//获取签名
let data = {
url: Vue.prototype.$config.redirect
// location.href.split('#')[0])
// url: Vue.prototype.$config.redirect + '/#/'
url: location.href.split('#')[0]
}
api('getVipShareSign', data, res => {
if (res.code == 200) {


Loading…
Cancel
Save