12 Commits

Author SHA1 Message Date
  fox fefdfebf47 feat: bug修复; 2 weeks ago
  前端-胡立永 7bf9c02b10 feat: 添加H5环境兼容性修复和功能优化 3 weeks ago
  前端-胡立永 922517dc3b feat(分享): 添加微信分享功能及相关配置 1 month ago
  前端-胡立永 3acefe0832 feat: 添加H5环境支持及功能优化 1 month ago
  前端-胡立永 964031adb9 feat(登录): 实现H5和微信小程序的双平台登录逻辑 1 month ago
  前端-胡立永 c118865ba5 fix: 将环境配置更改为生产环境并修复提现错误提示 1 month ago
  前端-胡立永 80040f273b feat(提现): 实现微信商户转账提现功能 1 month ago
  Fox b0fab06d88 Merge pull request 'feat: 接口对接;' (#8) from fox into master 2 months ago
  Fox 028acfe3ff Merge pull request 'feat: 接口对接;' (#7) from fox into master 2 months ago
  Fox 258f91ced7 Merge pull request 'feat: 签到接口对接;' (#6) from fox into master 2 months ago
  Fox 69e6085eb3 Merge pull request 'feat: 申请退款接口对接;' (#5) from fox into master 2 months ago
  Fox 637be6ca65 Merge pull request 'feat: 接口对接;' (#4) from fox into master 2 months ago
39 changed files with 2774 additions and 1155 deletions
Split View
  1. +1
    -0
      MP_verify_wfZF8R25IxLLUUXi.txt
  2. +22
    -0
      api/model/info.js
  3. +16
    -0
      api/model/login.js
  4. +20
    -1
      components/base/navbar.vue
  5. +5
    -0
      components/couponList/couponList.vue
  6. +1
    -1
      components/order/orderCard.vue
  7. +9
    -2
      config.js
  8. +72
    -5
      main.js
  9. +9
    -2
      manifest.json
  10. +37
    -0
      mixins/configList.js
  11. +107
    -24
      pages.json
  12. +6
    -16
      pages/index/category.vue
  13. +115
    -85
      pages/index/center.vue
  14. +4
    -4
      pages/index/index.vue
  15. +179
    -124
      pages_order/auth/wxLogin.vue
  16. +39
    -0
      pages_order/auth/wxUserInfo.vue
  17. +16
    -24
      pages_order/components/formTextarea.vue
  18. +0
    -0
      pages_order/components/memberProgress.vue
  19. +376
    -385
      pages_order/mine/cooperation.vue
  20. +4
    -2
      pages_order/mine/coupon.vue
  21. +2
    -2
      pages_order/mine/help.vue
  22. +27
    -35
      pages_order/mine/memberCenter.vue
  23. +2
    -2
      pages_order/mine/partner.vue
  24. +56
    -52
      pages_order/mine/promotion.vue
  25. +602
    -0
      pages_order/mine/promotionH5.vue
  26. +164
    -23
      pages_order/mine/runningWater.vue
  27. +7
    -11
      pages_order/mine/voucher.vue
  28. +205
    -15
      pages_order/mine/withdraw.vue
  29. +13
    -2
      pages_order/order/createOrder.vue
  30. +4
    -4
      pages_order/order/verifyOrder.vue
  31. +333
    -292
      pages_order/product/productDetail.vue
  32. BIN
      pages_order/static/center/member-bg.png
  33. +20
    -1
      store/store.js
  34. +0
    -2
      uni.scss
  35. +107
    -0
      utils/h5-fix.js
  36. +146
    -0
      utils/h5-router-fix.js
  37. +4
    -0
      utils/index.js
  38. +34
    -34
      utils/pay.js
  39. +10
    -5
      utils/share.js

+ 1
- 0
MP_verify_wfZF8R25IxLLUUXi.txt View File

@ -0,0 +1 @@
wfZF8R25IxLLUUXi

+ 22
- 0
api/model/info.js View File

@ -54,6 +54,28 @@ const api = {
limit: 1000,
showLoading: true,
},
// 钱包-提现
cashout: {
url: '/amount/cashout',
method: 'POST',
auth: true,
limit: 1000,
showLoading: true,
},
// 钱包-领取提现金额
getMoney: {
url: '/amount/getMoney',
method: 'POST',
auth: true,
limit: 1000,
showLoading: true,
},
// 钱包-提现记录列表
queryCashoutLog: {
url: '/amount/queryCashoutLog',
method: 'GET',
auth: true,
},
// 获取地址列表带分页
getAddressPageList: {
url: '/info_common/getAddressPageList',


+ 16
- 0
api/model/login.js View File

@ -5,8 +5,17 @@
const api = {
// 微信登录接口
wxLogin: {
// #ifdef H5
url: '/login/wxLogin',
method: 'POST',
// #endif
// #ifdef MP-WEIXIN
url: '/login/login',
method: 'GET',
// #endif
limit : 500,
showLoading : true,
},
@ -36,6 +45,13 @@ const api = {
method: 'GET',
auth: true,
},
//公众号签名
getVipShareSign : {
url: '/share/getVipShareSign',
method: 'GET',
},
}
export default api

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

@ -1,6 +1,11 @@
<template>
<!-- <view class="navbar"
:style="{backgroundColor : bgColor}"> -->
<!-- #ifdef MP-WEIXIN -->
<view class="title nav-bar__view"
:style="{backgroundColor : bgColor,color}">
<view class="left">
@ -40,6 +45,7 @@
</view>
</view>
<!-- #endif -->
<!-- </view> -->
</template>
@ -76,15 +82,28 @@
}
},
created() {
if(typeof document != 'undefined'){
document.title = this.title
}
},
beforeDestroy() {
},
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


+ 5
- 0
components/couponList/couponList.vue View File

@ -53,6 +53,11 @@
this.getCouponList()
}
},
// #ifndef H5
mounted() {
this.getCouponList()
},
// #endif
methods: {
select(item) {
this.$emit('select', item)


+ 1
- 1
components/order/orderCard.vue View File

@ -20,7 +20,7 @@
</view>
<view class="btns">
<template v-if="!readonly">
<template v-if="data.status != 1">
<template v-if="data.status != 1 && data.status != 3">
<button plain class="btn btn-plain" @click="onDelete">删除订单</button>
</template>
<!-- 待付款 -->


+ 9
- 2
config.js View File

@ -7,17 +7,21 @@ import uvUI from '@/uni_modules/uv-ui-tools'
Vue.use(uvUI);
// 当前环境
const type = 'dev'
const type = 'prod'
// 环境配置
const config = {
dev: {
baseUrl: 'http://augcl.natapp1.cc/massage-admin/massage',
// redirect : 'http://h5.xzaiyp.top',
redirect : 'https://wwwh5.yurangongfang.com/',
},
prod: {
baseUrl: 'https://www.yurangongfang.com/massage-admin/massage',
}
// baseUrl: 'https://www.yurangongfang.com/massage-admin/massage',
redirect : 'https://wwwh5.yurangongfang.com/',
},
}
@ -38,6 +42,9 @@ const defaultConfig = {
endpoint: 'oss-cn-shenzhen.aliyuncs.com',
}
},
//商户号
mchId : '1712378227',
appId : 'wxb1c123a63736f789',
}


+ 72
- 5
main.js View File

@ -25,11 +25,78 @@ import navbar from '@/components/base/navbar.vue'
Vue.component('configPopup',configPopup)
Vue.component('navbar',navbar)
const app = new Vue({
...App,
store,
})
app.$mount()
// #ifdef H5
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
// 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


+ 9
- 2
manifest.json View File

@ -1,6 +1,6 @@
{
"name" : "unapp模板",
"appid" : "__UNI__C572032",
"appid" : "__UNI__197A38F",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
@ -87,6 +87,8 @@
},
"devServer" : {
"https" : false,
"port" : 8002,
"disableHostCheck" : true,
"proxy" : {
"/ws/geocoder/v1/" : {
"target" : "https://apis.map.qq.com",
@ -97,6 +99,11 @@
"changeOrigin" : true
}
}
}
},
"router" : {
"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)
})
},
}
}

+ 107
- 24
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,78 +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/coupon"
"path": "mine/promotionH5",
"style": {
"navigationBarTitleText": "推广中心"
}
},
{
"path": "mine/voucher"
"path": "mine/coupon",
"style": {
"navigationBarTitleText": "优惠券"
}
},
{
"path": "mine/verifyVoucher"
"path": "mine/voucher",
"style": {
"navigationBarTitleText": "代金券"
}
},
{
"path": "mine/signIn"
"path": "mine/verifyVoucher",
"style": {
"navigationBarTitleText": "核销代金券"
}
},
{
"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": "联系我们"
}
}
]
}],
@ -126,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": {}
}

+ 6
- 16
pages/index/category.vue View File

@ -14,7 +14,6 @@
v-model="queryParams.title"
placeholder="搜索项目名称"
bgColor="#F5F5F5"
inputAlign="center"
:showAction="false"
@search="search"
@change="search"
@ -111,28 +110,18 @@
onShow() {
},
onLoad({
search,
cid
}) {
onLoad() {
let search = uni.getStorageSync('search')
console.log('onLoad', search)
if (search) {
this.queryParams.title = search
uni.setStorageSync('search', '')
}
this.initList()
return
if(this.category.length > 0 && cid){
this.category.forEach((n, i) => {
if(n.id == cid){
this.current = i
}
})
// this.queryParams.classId = cid
}else if (this.category.length > 0) {
// this.queryParams.classId = this.category[0].id
}
},
methods: {
async fetchCategoryList() {
@ -190,6 +179,7 @@
align-items: center;
}
$body-height: calc(100vh - #{$tabbar-height} - env(safe-area-inset-bottom));
$search-height: 108rpx;
$list-height: calc(#{$body-height} - #{$search-height});


+ 115
- 85
pages/index/center.vue View File

@ -9,11 +9,12 @@
</view>
<!-- 登录后显示用户信息 -->
<view v-else class="flex flex-column user">
<view v-else class="flex flex-column user" @click="$utils.navigateTo('/pages_order/auth/wxUserInfo')">
<image class="user-avatar" :src="userInfo.headImage" mode="aspectFill"></image>
<view class="user-name">{{ userInfo.nickName }}</view>
</view>
<view class="member">
<image class="member-bg" src="@/pages_order/static/center/member-bg.png" mode="widthFix" ></image>
<view class="flex member-overview" :class="[role ? 'is-member' : '']">
<template v-if="role">
<!-- todo: 缺切图 "皇冠" -->
@ -32,35 +33,6 @@
<button plain class="btn member-btn" @click="$utils.navigateTo('/pages_order/mine/memberCenter')">立即开通</button>
</template>
</view>
<view class="member-rights">
<image class="member-rights-bg" src="@/pages_order/static/center/member-bg.png" mode="top" ></image>
<view class="flex member-rights-content">
<view class="flex">
<image class="icon icon-rights" src="@/static/image/center/icon-member-weal.png" mode="widthFix"></image>
<view>
<view class="member-rights-label">会员福利</view>
<view class="member-rights-value">会员折扣</view>
</view>
</view>
<view class="flex">
<!-- todo: 缺切图 -->
<image class="icon icon-rights" src="@/static/image/center/icon-member-weal.png" mode="widthFix"></image>
<view>
<view class="member-rights-label">会员积分</view>
<view class="member-rights-value">兑换礼品</view>
</view>
</view>
<!-- todo: 缺切图 -->
<view class="flex">
<image class="icon icon-rights" src="@/static/image/center/icon-member-weal.png" mode="widthFix"></image>
<view>
<view class="member-rights-label">会员优惠券</view>
<view class="member-rights-value">满减折扣</view>
</view>
</view>
</view>
</view>
</view>
</view>
@ -97,7 +69,15 @@
<image class="fun-common-icon" src="@/static/image/center/icon-team.png" mode="widthFix"></image>
<text class="fun-common-label">我的团队</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="flex flex-column" @click="$utils.navigateTo('/pages_order/mine/promotion')">
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="flex flex-column" @click="$utils.navigateTo('/pages_order/mine/promotionH5')">
<!-- #endif -->
<image class="fun-common-icon" src="@/static/image/center/icon-invite.png" mode="widthFix"></image>
<text class="fun-common-label">邀请好友</text>
</view>
@ -139,7 +119,6 @@
</view>
</view>
<!-- todo -->
<view class="right" @click="onScan">
<image class="right-bg" src="@/static/image/center/bg-scan.png"></image>
</view>
@ -180,7 +159,7 @@
}
},
onShow() {
if(this.isLogin){
if(uni.getStorageSync('token')){
this.$store.commit('getUserInfo')
this.$store.commit('getUserCenterInfo')
this.$store.commit('getRiceInfo')
@ -188,27 +167,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);
@ -217,10 +267,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-
@ -229,18 +275,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 {
@ -262,7 +313,7 @@
.header {
height: 550rpx;
background-image: linear-gradient(#84A73F, #D8FF8F);
justify-content: flex-start;
justify-content: flex-end;
position: relative;
}
}
@ -311,19 +362,31 @@
}
.member {
width: calc(100vw - 13rpx*2);
position: absolute;
bottom: 0;
left: 13rpx;
width: calc(100vw - 8rpx);
height: 241rpx;
// position: absolute;
// bottom: 0;
// left: 13rpx;
position: relative;
margin-top: 37rpx;
transform: translateY(10rpx);
color: #F5F5F5;
font-size: 22rpx;
background-color: #2B2C30;
// background-color: #2B2C30;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
&-bg {
width: 100%;
height: 100%;
}
&-overview {
position: absolute;
top: 10rpx;
left: 30rpx;
min-height: 81rpx;
justify-content: space-between;
@ -368,39 +431,6 @@
&-tips {
margin-left: 6rpx;
}
&-rights {
background-color: #37393D;
position: relative;
width: 100%;
height: 142rpx;
box-sizing: border-box;
&-bg {
width: 100%;
height: 100%;
}
&-content {
position: absolute;
bottom: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 0 35rpx 6rpx 35rpx;
justify-content: space-between;
}
&-label {
color: #CBAD8F;
font-size: 22rpx;
font-weight: 700;
}
&-value {
color: #F5F5F5;
font-size: 18rpx;
}
}
}
.card {


+ 4
- 4
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 -->
@ -98,8 +97,9 @@
methods: {
//
search() {
uni.navigateTo({
url: '/pages/index/category?search=' + this.keyword
uni.setStorageSync('search', this.keyword)
uni.switchTab({
url: '/pages/index/category'
})
this.keyword = ''
},


+ 179
- 124
pages_order/auth/wxLogin.vue View File

@ -6,9 +6,8 @@
{{ configList.config_app_name }}
</view>
</view>
<button class="btn btn-login mt"
@click="wxLogin">
<button class="btn btn-login mt" @click="wxLogin">
<view class="icon">
<uv-icon name="weixin-fill" color="#ffffff" size="36rpx"></uv-icon>
</view>
@ -20,23 +19,15 @@
<button class="btn btn-cancel" :plain="true" :hairline="false" @click="onCancel">
暂不登录
</button>
<view class="config mt">
<view style="flex: 1;">
<uv-checkbox-group
v-model="checkboxValue"
shape="circle"
>
<uv-checkbox-group v-model="checkboxValue" shape="circle">
<view class="content">
<uv-checkbox
size="28rpx"
icon-size="28rpx"
activeColor="#07C261"
:name="1"
></uv-checkbox>
<uv-checkbox size="28rpx" icon-size="28rpx" activeColor="#07C261" :name="1"></uv-checkbox>
已同意
<text @click="openModal">注册协议隐私协议</text>
</view>
</view>
</uv-checkbox-group>
</view>
<view class="flex">
@ -45,138 +36,202 @@
</view>
<agreementModal ref="modal" @confirm="onConfirmAggrement"></agreementModal>
</view>
</template>
<script>
import agreementModal from './agreementModal.vue'
export default {
name : 'Login',
data() {
return {
checkboxValue : []
}
},
components: {
agreementModal
},
methods: {
wxLogin(){
if(!this.checkboxValue.length){
return uni.showToast({
title: '请先同意隐私协议',
icon:'none'
})
import agreementModal from './agreementModal.vue'
import Vue from 'vue'
export default {
name: 'Login',
data() {
return {
checkboxValue: []
}
this.$store.commit('login')
},
openModal() {
this.$refs.modal.open()
components: {
agreementModal
},
onConfirmAggrement(confirm) {
if (confirm) {
this.checkboxValue = [1]
} else {
this.checkboxValue = []
onShow() {
if (this.GetQueryString('code')) { //code
this.agreement = true; //
//
this.toWxLogin(this.GetQueryString('code'))
}
},
onCancel() {
console.log('--onCancel')
uni.reLaunch({
url: '/pages/index/index'
})
},
methods: {
wxLogin() {
if (!this.checkboxValue.length) {
return uni.showToast({
title: '请先同意隐私协议',
icon: 'none'
})
}
// #ifdef MP-WEIXIN
this.$store.commit('login')
// #endif
// #ifdef H5
this.getwx_authorize();
// #endif
},
openModal() {
this.$refs.modal.open()
},
onConfirmAggrement(confirm) {
if (confirm) {
this.checkboxValue = [1]
} else {
this.checkboxValue = []
}
},
onCancel() {
console.log('--onCancel')
uni.reLaunch({
url: '/pages/index/index'
})
},
getwx_authorize() {
console.log(Vue.prototype.$config.redirect);
let redirect_uri = encodeURIComponent(Vue.prototype.$config.redirect + '/#/pages_order/auth/wxLogin');
let appid = Vue.prototype.$config.appId;
window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid +
'&redirect_uri=' + redirect_uri +
'&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect' + '&t=' + new Date().getTime();
},
//url
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) {
return cs_arr[i].split('=')[1];
}
}
return "";
} catch {
return "";
}
},
//
toWxLogin(code) {
let vid = sessionStorage.getItem('vid');
this.$api('wxLogin', {
code,
vid
}, res => {
if (res.code == 200) {
this.$store.commit('h5Login', res.result)
} else {
location.href = Vue.prototype.$config.redirect + '/#/pages_order/auth/wxLogin'
}
sessionStorage.removeItem('vid')
})
},
}
}
}
</script>
<style scoped lang="scss">
.login{
display: flex;
justify-content: flex-start;
align-items: center;
height: 100vh;
flex-direction: column;
position: relative;
background: $uni-fg-color;
.logo{
margin-top: 334rpx;
width: 320rpx;
image{
height: 150rpx;
.login {
display: flex;
justify-content: flex-start;
align-items: center;
height: 100vh;
flex-direction: column;
position: relative;
background: $uni-fg-color;
.logo {
margin-top: 334rpx;
width: 320rpx;
image {
height: 150rpx;
width: 320rpx;
}
.text {
margin-top: 90rpx;
font-size: 38rpx;
font-family: PingFang SC, PingFang SC-Bold;
font-weight: 700;
text-align: center;
color: #000000;
}
}
.text{
margin-top: 90rpx;
font-size: 38rpx;
font-family: PingFang SC, PingFang SC-Bold;
font-weight: 700;
text-align: center;
color: #000000;
}
}
.btn{
width: 80%;
height: 100rpx;
font-size: 15px;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
.btn {
width: 80%;
height: 100rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50rpx;
&-login {
background-image: linear-gradient(to right, #84A73F, #D8FF8F);
color: #fff;
border: none;
.icon{
margin-right: 10rpx;
image{
width: 40rpx;
height: 35rpx;
font-size: 15px;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50rpx;
&-login {
background-image: linear-gradient(to right, #84A73F, #D8FF8F);
color: #fff;
border: none;
.icon {
margin-right: 10rpx;
image {
width: 40rpx;
height: 35rpx;
}
}
}
&-cancel {
height: calc(100rpx - 1rpx * 2);
color: #c7c7c7;
background-color: transparent;
border: 1rpx solid #c7c7c7;
margin-top: 60rpx;
}
}
&-cancel {
height: calc(100rpx - 1rpx * 2);
color: #c7c7c7;
background-color: transparent;
border: 1rpx solid #c7c7c7;
margin-top: 60rpx;
.mt {
margin-top: 200rpx;
}
}
.mt{
margin-top: 200rpx;
}
.config{
width: 80%;
line-height: 40rpx;
// margin-top: 27rpx;
color: #C7C7C7;
text-align: left;
display: flex;
.content {
font-size: 22rpx;
.config {
width: 80%;
line-height: 40rpx;
// margin-top: 27rpx;
color: #C7C7C7;
text-align: left;
display: flex;
align-items: center;
}
text{
color: #07C261;
.content {
font-size: 22rpx;
display: flex;
align-items: center;
}
text {
color: #07C261;
}
}
}
}
.flex {
display: flex;
align-items: center;
}
</style>
.flex {
display: flex;
align-items: center;
}
</style>

+ 39
- 0
pages_order/auth/wxUserInfo.vue View File

@ -45,6 +45,8 @@
<view class="label">
手机号
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="content">
<input v-if="userInfoForm.phone"
placeholder-class="uni-placeholder"
@ -64,6 +66,19 @@
</button>
</view>
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="content">
<input
placeholder-class="uni-placeholder"
placeholder="请输入手机号"
style="text-align: right;"
v-model="userInfoForm.phone"
/>
</view>
<!-- #endif -->
</view>
</view>
@ -114,6 +129,26 @@
}
})
},
validatePhone(phone) {
// let fixedLindReg = /(\d{3,4}-)?\d{7,8}$/g //
let mobileReg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/g //
// if (fixedLindReg.test(phone)) {
// return true
// }
if (mobileReg.test(phone)) {
return true
}
uni.showToast({
title: '请填写正确手机号',
icon: "none"
})
return false
},
submit() {
let self = this
@ -134,6 +169,10 @@
return
}
if (!this.validatePhone(self.userInfoForm.phone)) {
return
}
self.$api('updateInfo', {
headImage : self.userInfoForm.headImage,
nickName : self.userInfoForm.nickName,


+ 16
- 24
pages_order/components/formTextarea.vue View File

@ -1,40 +1,32 @@
<template>
<uv-textarea
:value="value"
@input="$emit('input', $event)"
:placeholder="placeholder"
height="175rpx"
border="none"
:customStyle="{
<uv-textarea :value="value" @input="$emit('input', $event)" :placeholder="placeholder" height="175rpx" border="none"
:customStyle="{
backgroundColor: '#F5F5F5',
borderRadius: '6rpx',
}"
:placeholderStyle="{
color: '#999999',
fontSize: '28rpx',
}"
></uv-textarea>
borderRadius: '6rpx',}"
></uv-textarea>
<!--
:placeholderStyle="{color: '#999999',
fontSize: '28rpx'}" -->
</template>
<script>
export default {
props: {
props: {
value: {
default: null
},
placeholder: {
type: String,
placeholder: {
type: String,
default: '请输入'
},
},
},
data() {
return {
}
},
methods: {
},
}
return {}
},
methods: {},
}
</script>
<style scoped lang="scss">

pages_order/components/progress.vue → pages_order/components/memberProgress.vue View File


+ 376
- 385
pages_order/mine/cooperation.vue View File

@ -1,405 +1,396 @@
<template>
<view class="page">
<view class="page">
<!-- 导航栏 -->
<navbar title="商家合作" leftClick @leftClick="$utils.navigateBack" color="#fff" />
<view v-if="['0', '2'].includes(status) && statusDesc" class="flex tips">
<uv-icon name="info-circle" color="#86A941" size="28rpx"></uv-icon>
<text style="margin-left: 3rpx;">{{ statusDesc }}</text>
</view>
<view class="content">
<view class="form">
<view class="form-title">门头照片</view>
<view class="card upload">
<formUpload v-model="form.image">
<template v-slot="{ value }">
<view class="flex">
<image v-if="value"
class="upload-img"
:src="value"
mode="aspectFill"
/>
<image v-else
class="upload-img"
src="../static/cooperation/icon-upload.png"
mode="aspectFill"
/>
</view>
</template>
</formUpload>
</view>
</view>
<view class="form">
<view class="form-title">店铺信息</view>
<view class="card info">
<uv-form
ref="form"
:model="form"
:rules="rules"
labelPosition="left"
labelWidth="150rpx"
:labelStyle="{
<view v-if="['0', '2'].includes(status) && statusDesc" class="flex tips">
<uv-icon name="info-circle" color="#86A941" size="28rpx"></uv-icon>
<text style="margin-left: 3rpx;">{{ statusDesc }}</text>
</view>
<view class="content">
<view class="form">
<view class="form-title">门头照片</view>
<view class="card upload">
<formUpload v-model="form.image">
<view class="flex">
<image v-if="form.image" class="upload-img" :src="form.image" mode="aspectFill" />
<image v-else class="upload-img" src="../static/cooperation/icon-upload.png" mode="aspectFill" />
</view>
</formUpload>
</view>
</view>
<view class="form">
<view class="form-title">店铺信息</view>
<view class="card info">
<uv-form ref="form" :model="form" :rules="rules" labelPosition="left" labelWidth="150rpx"
:labelStyle="{
color: '#000000',
fontSize: '28rpx',
}"
>
<view class="form-item">
<uv-form-item label="店铺名称" prop="shop">
<view class="form-item-content">
<formInput v-model="form.shop" placeholder="请输入店铺名称"></formInput>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item label="您的姓名" prop="name">
<view class="form-item-content">
<formInput v-model="form.name" placeholder="请输入您的姓名"></formInput>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item label="联系手机号" prop="phone">
<view class="form-item-content">
<formInput v-model="form.phone" placeholder="请输入您的手机号"></formInput>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item label="所在地区" prop="area">
<view class="form-item-content flex area">
<text>{{ form.area ? form.area : '请选择' }}</text>
<button plain class="btn area-btn" @click="selectAddr">
<image class="area-btn-icon" src="../static/cooperation/icon-arrow.png" mode="widthFix"></image>
</button>
</view>
</uv-form-item>
</view>
<view class="form-item address">
<uv-form-item label="详细地址" prop="address" labelPosition="top" >
<view style="margin-top: 22rpx;">
<formTextarea
v-model="form.address"
placeholder="请输入详细地址"
></formTextarea>
</view>
</uv-form-item>
</view>
</uv-form>
</view>
</view>
<view class="tools" v-if="status != '1'">
<button plain class="btn btn-submit" @click="onSubmit">提交</button>
</view>
</view>
</view>
}">
<view class="form-item">
<uv-form-item label="店铺名称" prop="shop">
<view class="form-item-content">
<formInput v-model="form.shop" placeholder="请输入店铺名称"></formInput>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item label="您的姓名" prop="name">
<view class="form-item-content">
<formInput v-model="form.name" placeholder="请输入您的姓名"></formInput>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item label="联系手机号" prop="phone">
<view class="form-item-content">
<formInput v-model="form.phone" placeholder="请输入您的手机号"></formInput>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item label="所在地区" prop="area">
<view class="form-item-content flex area">
<view style="width: 450rpx;">{{ form.area ? form.area : '请选择' }}</view>
<button plain class="btn area-btn" @click="selectAddr">
<image class="area-btn-icon" src="../static/cooperation/icon-arrow.png"
mode="widthFix"></image>
</button>
</view>
</uv-form-item>
</view>
<view class="form-item address">
<uv-form-item label="详细地址" prop="address" labelPosition="top">
<view style="margin-top: 22rpx;width: 100%;">
<formTextarea v-model="form.address" placeholder="请输入详细地址"></formTextarea>
</view>
</uv-form-item>
</view>
</uv-form>
</view>
</view>
<view class="tools" v-if="status != '1'">
<button plain class="btn btn-submit" @click="onSubmit">提交</button>
</view>
</view>
</view>
</template>
<script>
import { mapState } from 'vuex'
import Position from '@/utils/position.js'
import formInput from '../components/formInput.vue'
import formUpload from '../components/formUpload.vue'
import formTextarea from '../components/formTextarea.vue'
export default {
components: {
formInput,
formUpload,
formTextarea,
},
data() {
return {
id: null,
status: null,
statusDesc: null,
form: {
image: null,
shop: null,
name: null,
phone: null,
area: null,
latitude: null,
longitude: null,
address: null,
},
rules: {
'image': {
type: 'string',
required: true,
message: '请选择门头照片',
},
'shop': {
type: 'string',
required: true,
message: '请输入店铺名称',
},
'name': {
type: 'string',
required: true,
message: '请输入您的姓名',
},
'phone': {
type: 'string',
required: true,
message: '请输入您的手机号',
},
'area': {
type: 'string',
required: true,
message: '请选择所在地区',
},
'address': {
type: 'string',
required: true,
message: '请输入详细地址',
},
},
}
},
computed: {
...mapState(['userInfo']),
},
onLoad() {
this.initData()
},
onPullDownRefresh() {
this.updateStatus()
},
methods: {
//
selectAddr() {
// Position.getLocation(res => {
Position.selectAddress(0, 0, success => {
this.setAddress(success)
})
// })
},
//
setAddress(res) {
//
this.form.latitude = res.latitude
this.form.longitude = res.longitude
if (!res.address && res.name) { //
return this.form.area = res.name
}
if (res.address || res.name) {
return this.form.area = res.address + res.name
}
this.form.area = '' //
},
async initData() {
try {
const shopDetails = (await this.$fetch('queryShopById'))
console.log("shopDetails======")
console.log(shopDetails)
if (!shopDetails) {
return
}
const {
id,
status,
status_dictText,
remark,
image,
shop,
name,
phone,
area,
latitude,
longitude,
address,
} = shopDetails
this.form = {
image,
shop,
name,
phone,
area,
latitude,
longitude,
address,
}
this.id = id
this.status = status
this.statusDesc = status == '2' ? `${status_dictText}${remark}` : status_dictText
} catch (err) {
}
},
async updateStatus() {
if (!this.id) {
return
}
try {
const { status, status_dictText, remark } = await this.$fetch('queryShopById', { id: this.id })
this.status = status
this.statusDesc = status == '2' ? `${status_dictText}${remark}` : status_dictText
} catch (err) {
console.log('--err', err)
}
uni.stopPullDownRefresh();
},
async onSubmit() {
try {
await this.$refs.form.validate()
const {
image,
shop,
name,
phone,
area,
latitude,
longitude,
address,
} = this.form
const params = {
image,
shop,
name,
phone,
area,
latitude,
longitude,
address,
}
let api = this.id ? 'updateShop' : 'addShop'
await this.$fetch(api, params)
uni.showToast({
title: '提交成功',
icon: 'none'
})
setTimeout(uni.navigateBack, 1000, -1)
} catch (err) {
}
},
},
}
import {
mapState
} from 'vuex'
import Position from '@/utils/position.js'
import formInput from '../components/formInput.vue'
import formUpload from '../components/formUpload.vue'
import formTextarea from '../components/formTextarea.vue'
export default {
components: {
formInput,
formUpload,
formTextarea,
},
data() {
return {
id: null,
status: null,
statusDesc: null,
form: {
image: null,
shop: null,
name: null,
phone: null,
area: null,
latitude: null,
longitude: null,
address: null,
},
rules: {
'image': {
type: 'string',
required: true,
message: '请选择门头照片',
},
'shop': {
type: 'string',
required: true,
message: '请输入店铺名称',
},
'name': {
type: 'string',
required: true,
message: '请输入您的姓名',
},
'phone': {
type: 'string',
required: true,
message: '请输入您的手机号',
},
'area': {
type: 'string',
required: true,
message: '请选择所在地区',
},
'address': {
type: 'string',
required: true,
message: '请输入详细地址',
},
},
}
},
computed: {
...mapState(['userInfo']),
},
onLoad() {
this.initData()
},
onPullDownRefresh() {
this.updateStatus()
},
methods: {
//
selectAddr() {
// Position.getLocation(res => {
Position.selectAddress(0, 0, success => {
this.setAddress(success)
})
// })
},
//
setAddress(res) {
//
this.form.latitude = res.latitude
this.form.longitude = res.longitude
if (!res.address && res.name) { //
return this.form.area = res.name
}
if (res.address || res.name) {
return this.form.area = res.address + res.name
}
this.form.area = '' //
},
async initData() {
try {
const shopDetails = (await this.$fetch('queryShopById'))
console.log("shopDetails======")
console.log(shopDetails)
if (!shopDetails) {
return
}
const {
id,
status,
status_dictText,
remark,
image,
shop,
name,
phone,
area,
latitude,
longitude,
address,
} = shopDetails
this.form = {
image,
shop,
name,
phone,
area,
latitude,
longitude,
address,
}
this.id = id
this.status = status
this.statusDesc = status == '2' ? `${status_dictText}${remark}` : status_dictText
} catch (err) {
}
},
async updateStatus() {
if (!this.id) {
return
}
try {
const {
status,
status_dictText,
remark
} = await this.$fetch('queryShopById', {
id: this.id
})
this.status = status
this.statusDesc = status == '2' ? `${status_dictText}${remark}` : status_dictText
} catch (err) {
console.log('--err', err)
}
uni.stopPullDownRefresh();
},
async onSubmit() {
try {
await this.$refs.form.validate()
const {
image,
shop,
name,
phone,
area,
latitude,
longitude,
address,
} = this.form
const params = {
image,
shop,
name,
phone,
area,
latitude,
longitude,
address,
}
let api = this.id ? 'updateShop' : 'addShop'
await this.$fetch(api, params)
uni.showToast({
title: '提交成功',
icon: 'none'
})
setTimeout(uni.navigateBack, 1000, -1)
} catch (err) {
}
},
},
}
</script>
<style lang="scss" scoped>
.page {
background-color: $uni-bg-color;
min-height: 100vh;
/deep/ .nav-bar__view {
background-image: linear-gradient(#84A73F, #D8FF8F);
}
}
.tips {
padding: 5rpx 0;
font-weight: bold;
font-size: 28rpx;
color: $uni-color;
background-color: rgba($color: #D8FF8F, $alpha: 0.3);
}
.content {
padding: 28rpx 30rpx;
}
.form {
& + & {
margin-top: 44rpx;
}
&-title {
color: #000000;
font-size: 28rpx;
margin-bottom: 15rpx;
}
&-item {
padding-left: 8rpx;
& + & {
// margin-top: 20rpx;
border-top: 1rpx solid rgba($color: #C7C7C7, $alpha: 0.69);
}
&-content {
min-height: 60rpx;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 28rpx;;
color: #999999;
}
}
}
.upload {
padding: 37rpx 22rpx;
&-img {
width: 131rpx; height: 131rpx;
}
}
.area {
color: #000000;
font-size: 28rpx;
line-height: 40rpx;
justify-content: flex-end;
&-btn {
border: none;
padding: 7rpx 20rpx 7rpx 7rpx;
&-icon {
width: 30rpx;
height: auto;
}
}
}
.address {
padding: 0;
/deep/ .uv-form-item__body__left__content {
margin-top: 10rpx;
padding-left: 8rpx;
}
}
.tools {
padding: 0 56rpx;
margin-top: 126rpx;
}
.btn-submit {
padding: 29rpx 0;
border: none;
font-size: 36rpx;
border-radius: 45rpx;
color: $uni-text-color-inverse;
background-image: linear-gradient(to right, #84A73F, #D8FF8F);
}
.tips {
padding: 5rpx 0;
font-weight: bold;
font-size: 28rpx;
color: $uni-color;
background-color: rgba($color: #D8FF8F, $alpha: 0.3);
}
.content {
padding: 28rpx 30rpx;
}
.form {
&+& {
margin-top: 44rpx;
}
&-title {
color: #000000;
font-size: 28rpx;
margin-bottom: 15rpx;
}
&-item {
padding-left: 8rpx;
&+& {
// margin-top: 20rpx;
border-top: 1rpx solid rgba($color: #C7C7C7, $alpha: 0.69);
}
&-content {
min-height: 60rpx;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 28rpx;
;
color: #999999;
}
}
}
.upload {
padding: 37rpx 22rpx;
&-img {
width: 131rpx;
height: 131rpx;
}
}
.area {
color: #000000;
font-size: 28rpx;
line-height: 40rpx;
justify-content: flex-end;
&-btn {
border: none;
padding: 7rpx 20rpx 7rpx 7rpx;
&-icon {
width: 30rpx;
height: auto;
}
}
}
.address {
padding: 0;
/deep/ .uv-form-item__body__left__content {
margin-top: 10rpx;
padding-left: 8rpx;
}
}
.tools {
padding: 0 56rpx;
margin-top: 126rpx;
}
.btn-submit {
padding: 29rpx 0;
border: none;
font-size: 36rpx;
border-radius: 45rpx;
color: $uni-text-color-inverse;
background-image: linear-gradient(to right, #84A73F, #D8FF8F);
}
</style>

+ 4
- 2
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>
@ -50,7 +50,9 @@
},
},
onShow() {
this.getCouponList()
this.$nextTick(() => {
this.getCouponList()
})
},
data() {
return {


+ 2
- 2
pages_order/mine/help.vue View File

@ -21,8 +21,8 @@
<view class="help-screenshot">
<uv-upload :fileList="fileList" multiple :maxCount="3" width="180rpx" height="180rpx" multiple
@afterRead="afterRead" @delete="deleteImage">
<image src="../static/help/uploading.png" mode="aspectFill"
style="width: 180rpx;height: 180rpx;" />
<!-- <image src="../static/help/uploading.png" mode="aspectFill"
style="width: 180rpx;height: 180rpx;" /> -->
</uv-upload>
</view>
</view>


+ 27
- 35
pages_order/mine/memberCenter.vue View File

@ -28,7 +28,7 @@
</view>
<view class="consumption" v-if="!role">
<!-- todo: 对接接口 -->
<progress :current="1000" :total="3800"></progress>
<memberProgress :current="1000" :total="3800"></memberProgress>
<text class="consumption-desc">{{ `累积消费达到${3800}元或直接充值即可成为会员` }}</text>
</view>
<view class="charge">
@ -76,7 +76,8 @@
</template>
<script>
import progress from '../components/progress.vue'
import memberProgress from '../components/memberProgress.vue'
import mixinsConfigList from '@/mixins/configList.js'
import {
mapGetters,
@ -85,8 +86,9 @@
export default {
name: "MemberCenter",
mixins: [mixinsConfigList],
components: {
progress,
memberProgress,
},
data() {
return {
@ -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: ""
// })
// }
// });
// }
// })
}
}
}


+ 2
- 2
pages_order/mine/partner.vue View File

@ -13,7 +13,7 @@
<view class="phone">{{ userInfo.phone }}</view>
<view class="flex account">
<view class="count">
<view><text class="count-unit">¥</text>{{ riceInfo.canWithdraw || 0 }}</view>
<view><text class="count-unit">¥</text>{{ userInfo.recommendAmount || 0 }}</view>
<view class="count-desc">推广佣金</view>
</view>
<button plain class="btn" @click="$utils.navigateTo('/pages_order/mine/withdraw')">去提现</button>
@ -25,7 +25,7 @@
<view class="list">
<view class="list-header">
<view class="list-header-title">
<text>直推用户<text class="sub">{{ `${total}人)` }}</text></text>
<text>直推用户<text class="sub">{{ `${total || 0}人)` }}</text></text>
<view class="list-header-title-line"></view>
</view>
</view>


+ 56
- 52
pages_order/mine/promotion.vue View File

@ -1,14 +1,14 @@
<template>
<view class="page">
<navbar title="邀请好友" leftClick @leftClick="$utils.navigateBack" color="#fff" />
<view class="flex flex-column content">
<view style="width: 698rpx; height: 788rpx; border-radius: 16rpx; overflow: hidden;">
<canvas id="myCanvas" canvas-id="firstCanvas1" type="2d" style="width: 100%; height: 100%;"></canvas>
</view>
<view style="width: 698rpx; height: 788rpx; border-radius: 16rpx; overflow: hidden;">
<canvas id="myCanvas" canvas-id="firstCanvas1" type="2d" style="width: 100%; height: 100%;"></canvas>
</view>
<view class="tools">
<button plain class="flex btn" @click="saveImg">
@ -17,13 +17,15 @@
</view>
</view>
</view>
</template>
<script>
import { mapState } from 'vuex'
import {
mapState
} from 'vuex'
export default {
name: 'Promotion',
computed: {
@ -36,19 +38,22 @@
canvas: {},
}
},
onReady() {
this.fetchQrCode()
},
onReady() {
this.fetchQrCode()
},
methods: {
async fetchQrCode() {
try {
this.wxCodeImage = (await this.$fetch('getInviteCode'))?.url
this.draw()
} catch (err) {
}
},
async fetchQrCode() {
try {
// #ifdef MP-WEIXIN
this.wxCodeImage = (await this.$fetch('getInviteCode'))?.url
// #endif
this.draw()
} catch (err) {
}
},
draw() {
uni.showLoading({
@ -99,45 +104,45 @@
const codeY = 645 * Ratio / dpr
// todo: fetch code
ctx.fillText(`邀请码:${'YFY1688'}`, codeX, codeY);
//
const coderImage = canvas.createImage()
coderImage.src = this.wxCodeImage
coderImage.onload = () => {
const x = 158 * Ratio / dpr
const y = 188 * Ratio / dpr
const size = 382 * Ratio / dpr
ctx.drawImage(coderImage, x, y, size, size)
// coderImage.onload = () => {
// const x = 158 * Ratio / dpr
// const y = 188 * Ratio / dpr
// const size = 382 * Ratio / dpr
// ctx.drawImage(coderImage, x, y, size, size)
uni.hideLoading()
}
// }
})
},
saveImg(){
this.$authorize('scope.writePhotosAlbum').then((res) => {
this.imgApi()
})
},
saveImg() {
this.$authorize('scope.writePhotosAlbum').then((res) => {
this.imgApi()
})
},
imgApi() {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: this.canvas.width,
height: this.canvas.height,
canvas: this.canvas,
success: (res) => {
let tempFilePath = res.tempFilePath;
this.saveImgToPhone(tempFilePath)
},
fail: (err) => {
console.log('--canvasToTempFilePath--fail', err)
}
}, this);
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: this.canvas.width,
height: this.canvas.height,
canvas: this.canvas,
success: (res) => {
let tempFilePath = res.tempFilePath;
this.saveImgToPhone(tempFilePath)
},
fail: (err) => {
console.log('--canvasToTempFilePath--fail', err)
}
}, this);
},
saveImgToPhone(image) {
saveImgToPhone(image) {
/* 获取图片的信息 */
uni.getImageInfo({
src: image,
@ -158,17 +163,16 @@
});
}
});
}
}
}
}
</script>
<style lang="scss" scoped>
.page {
background-color: $uni-bg-color;
min-height: 100vh;
/deep/ .nav-bar__view {
background-image: linear-gradient(#84A73F, #D8FF8F);
}
@ -179,7 +183,7 @@
padding: 48rpx 26rpx;
}
.image{
.image {
width: 100%;
height: 778rpx;
}
@ -189,7 +193,7 @@
width: 100%;
padding: 0 56rpx;
box-sizing: border-box;
.btn {
width: 100%;
padding: 29rpx 0;


+ 602
- 0
pages_order/mine/promotionH5.vue View File

@ -0,0 +1,602 @@
<template>
<view class="placard">
<view class="placard-content">
<!-- H5环境显示最终图片 -->
<view v-if="tempFilePath" class="img-box" :style="{ width: canvasW + 'px', height: canvasH + 'px' }">
<img
:src="tempFilePath"
:style="{ width: canvasW + 'px', height: canvasH + 'px' }"
@contextmenu.prevent
style="display: block; border-radius: 10px;"
/>
</view>
<!-- 生成中的提示 -->
<view v-else class="loading-box" :style="{ width: canvasW + 'px', height: canvasH + 'px' }">
<view class="loading-content">
<view class="loading-spinner"></view>
<text class="loading-text">
{{ !userId ? '获取用户信息中...' : '海报生成中...' }}
</text>
</view>
</view>
<!-- 二维码组件 - 隐藏 -->
<view v-if="userId" class="qrcode" ref="qrcode">
<uv-qrcode
:value="qrCodeValue"
:size="qrCodeSize"
type="image/png"
ref="qrCodeComponent"
@complete="onQRCodeComplete">
</uv-qrcode>
</view>
<!-- 画布 - 隐藏 -->
<canvas
:style="{ width: canvasW + 'px', height: canvasH + 'px' }"
canvas-id="myCanvas"
id="myCanvas"
:width="canvasW"
:height="canvasH"
class="hidden-canvas">
</canvas>
<view class="add-btn">
<view class="btn" @click="handleSaveImage">
长按图片保存到手机
</view>
</view>
</view>
</view>
</template>
<script>
import Vue from 'vue'
import { mapState } from 'vuex'
export default {
name: 'Placard',
computed: {
...mapState(['userInfo']),
// ID
userId() {
return this.userInfo?.id || ''
},
//
qrCodeContent() {
if (this.userId) {
return Vue.prototype.$config.redirect + `?vid=${this.userId}`
}
return Vue.prototype.$config.redirect
}
},
data() {
return {
qrCodeValue: '', //
qrCodeSize: 180,
qrCodeDarkColor: '#000',
qrCodeLightColor: '#fff',
margin: 0,
//
canvasW: 299,
canvasH: 403,
//
systemInfo: {},
//
tempFilePath: '',
_rpx: 1, // H5使
_center: 0,
//
qrCodeReady: false,
//
retryCount: 0,
maxRetry: 1,
//
userInfoLoaded: false
}
},
watch: {
//
userInfo: {
handler(newVal) {
if (newVal && newVal.id) {
this.userInfoLoaded = true
this.qrCodeValue = this.qrCodeContent
//
if (!this.qrCodeReady) {
this.$nextTick(() => {
this.generateQRCode()
})
}
}
},
deep: true,
immediate: true
}
},
mounted() {
this.initCanvas()
},
onShow() {
//
this.getUserInfo()
},
methods: {
//
initCanvas() {
// H5使便
this.canvasW = 349
this.canvasH = 389
this.qrCodeSize = 180
this._center = this.canvasW / 2
//
if (this.userInfoLoaded && this.qrCodeValue) {
this.generateQRCode()
} else {
//
this.getUserInfo()
}
},
//
getUserInfo() {
// store
if (!this.userInfo?.id) {
this.$store.commit('getUserInfo')
}
},
//
generateQRCode() {
// ID
if (!this.userId) {
console.log('等待用户信息加载...')
return
}
//
if (!this.qrCodeValue) {
this.qrCodeValue = this.qrCodeContent
}
console.log('生成二维码,内容:', this.qrCodeValue)
this.$nextTick(() => {
if (this.$refs.qrCodeComponent) {
this.$refs.qrCodeComponent.make()
}
})
},
//
onQRCodeComplete(res) {
if (res.success) {
this.qrCodeReady = true
this.draw()
}
},
//
async draw() {
if (!this.qrCodeReady) {
console.log('二维码未准备好')
return
}
const ctx = uni.createCanvasContext('myCanvas', this)
//
ctx.setFillStyle('#ffffff')
ctx.fillRect(0, 0, this.canvasW, this.canvasH)
ctx.setFillStyle('#000000')
ctx.setFontSize(16)
let text = '邀请好友'
//
let metrics = ctx.measureText(text);
ctx.fillText(text, this._center - metrics.width / 2, 53);
text = `邀请码:${this.userId}`
//
metrics = ctx.measureText(text);
// todo: check code
ctx.fillText(text, this._center - metrics.width / 2, 342);
//
try {
const qrCodeImagePath = await this.getQRCodeImage()
if (qrCodeImagePath) {
const qrSize = 192
const qrX = (this.canvasW - qrSize) / 2
const qrY = 94
ctx.drawImage(qrCodeImagePath, qrX, qrY, qrSize, qrSize)
}
} catch (e) {
console.log('二维码绘制失败', e)
}
//
ctx.draw(false, () => {
//
setTimeout(() => {
this.canvasToTempFilePath()
}, 1000)
})
return
//
ctx.setFillStyle('#333333')
ctx.setFontSize(18)
ctx.setTextAlign('center')
ctx.fillText('邀请好友一起享受优质服务', this.canvasW / 2, 30)
// ()
if (this.userInfo.headImage) {
try {
//
const avatarPath = await this.downloadImage(this.userInfo.headImage)
const avatarSize = 60
const avatarX = (this.canvasW - avatarSize) / 2
const avatarY = 50
//
ctx.setFillStyle('#f0f0f0')
ctx.beginPath()
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI)
ctx.fill()
//
ctx.save()
ctx.beginPath()
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI)
ctx.clip()
ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize)
ctx.restore()
} catch (e) {
console.log('头像绘制失败', e)
//
const avatarSize = 60
const avatarX = (this.canvasW - avatarSize) / 2
const avatarY = 50
ctx.setFillStyle('#e0e0e0')
ctx.beginPath()
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI)
ctx.fill()
}
} else {
//
const avatarSize = 60
const avatarX = (this.canvasW - avatarSize) / 2
const avatarY = 50
ctx.setFillStyle('#e0e0e0')
ctx.beginPath()
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI)
ctx.fill()
}
//
if (this.userInfo.nickName) {
ctx.setFillStyle('#333333')
ctx.setFontSize(16)
ctx.setTextAlign('center')
ctx.fillText(this.userInfo.nickName || '用户', this.canvasW / 2, 140)
}
//
try {
const qrCodeImagePath = await this.getQRCodeImage()
if (qrCodeImagePath) {
const qrSize = 120
const qrX = (this.canvasW - qrSize) / 2
const qrY = 160
ctx.drawImage(qrCodeImagePath, qrX, qrY, qrSize, qrSize)
}
} catch (e) {
console.log('二维码绘制失败', e)
}
//
ctx.setFillStyle('#666666')
ctx.setFontSize(14)
ctx.setTextAlign('center')
ctx.fillText('扫码关注,享受专业服务', this.canvasW / 2, 310)
//
ctx.draw(false, () => {
//
setTimeout(() => {
this.canvasToTempFilePath()
}, 1000)
})
},
//
downloadImage(url) {
return new Promise((resolve, reject) => {
// H5使URL
// #ifdef H5
resolve(url)
// #endif
// #ifndef H5
uni.downloadFile({
url: url,
success: (res) => {
if (res.statusCode === 200) {
resolve(res.tempFilePath)
} else {
reject('下载失败')
}
},
fail: reject
})
// #endif
})
},
//
getQRCodeImage() {
return new Promise((resolve, reject) => {
if (this.$refs.qrCodeComponent) {
this.$refs.qrCodeComponent.toTempFilePath({
success: (res) => {
resolve(res.tempFilePath)
},
fail: (err) => {
reject(err)
}
})
} else {
reject('二维码组件不存在')
}
})
},
//
canvasToTempFilePath() {
// #ifdef H5
// H5canvas
setTimeout(() => {
this.convertCanvasToBase64()
}, 500)
// #endif
// #ifndef H5
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
success: (res) => {
this.tempFilePath = res.tempFilePath
console.log('海报生成成功', res.tempFilePath)
},
fail: (err) => {
console.log('海报生成失败', err)
uni.showToast({
title: '海报生成失败',
icon: 'none'
})
}
}, this)
// #endif
},
// canvasbase64
convertCanvasToBase64() {
// #ifdef H5
try {
// canvas
const canvasElement = document.querySelector('#myCanvas')
if (!canvasElement) {
console.error('找不到canvas元素')
this.retryConvert()
return
}
// canvas
if (canvasElement.tagName.toLowerCase() !== 'canvas') {
console.error('元素不是canvas')
this.retryConvert()
return
}
// canvas
setTimeout(() => {
try {
// base64
const dataURL = canvasElement.toDataURL('image/png', 1.0)
if (dataURL && dataURL.startsWith('data:image')) {
this.tempFilePath = dataURL
console.log('海报生成成功')
this.retryCount = 0 //
} else {
throw new Error('生成的图片数据无效')
}
} catch (e) {
console.error('转换图片失败', e)
this.retryConvert()
}
}, 200)
} catch (e) {
console.error('转换图片失败', e)
this.retryConvert()
}
// #endif
},
//
retryConvert() {
if (this.retryCount < this.maxRetry) {
this.retryCount++
console.log(`重试转换图片,第${this.retryCount}`)
setTimeout(() => {
this.convertCanvasToBase64()
}, 500)
} else {
console.log('重试次数已达上限,使用备用方法')
this.fallbackCanvasConvert()
}
},
// canvas
fallbackCanvasConvert() {
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
success: (res) => {
this.tempFilePath = res.tempFilePath
console.log('海报生成成功(备用方法)', res.tempFilePath)
},
fail: (err) => {
console.log('海报生成失败', err)
uni.showToast({
title: '海报生成失败',
icon: 'none'
})
}
}, this)
},
// - H5
handleSaveImage() {
if (!this.tempFilePath) {
uni.showToast({
title: '图片还未生成完成',
icon: 'none'
})
return
}
// H5
uni.showModal({
title: '保存图片',
content: '请长按上方图片,选择"保存图片"即可保存到手机',
showCancel: false,
confirmText: '知道了'
})
}
}
}
</script>
<style lang="scss" scoped>
.placard {
display: flex;
align-items: start;
justify-content: center;
min-height: 100vh;
background-color: #f5f5f5;
padding: 48rpx 26rpx;
.placard-content {
display: flex;
flex-direction: column;
align-items: center;
.img-box {
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin-bottom: 20px;
img {
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: default;
}
}
.loading-box {
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
.loading-spinner {
width: 30px;
height: 30px;
border: 3px solid #f3f3f3;
border-top: 3px solid #84A73F;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}
.loading-text {
color: #666;
font-size: 14px;
}
}
}
.add-btn {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 80%;
height: 40px;
border-radius: 20px;
color: white;
font-size: 16px;
background: linear-gradient(to right, #84A73F, #D8FF8F);
margin-top: 20px;
box-shadow: 0 4px 12px rgba(132, 167, 63, 0.3);
cursor: pointer;
&:active {
transform: scale(0.98);
transition: transform 0.1s;
}
}
}
}
}
.hidden-canvas {
opacity: 0;
position: fixed;
top: -9999px;
left: -9999px;
pointer-events: none;
}
.qrcode {
position: fixed;
top: -9999px;
left: -9999px;
opacity: 0;
pointer-events: none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

+ 164
- 23
pages_order/mine/runningWater.vue View File

@ -4,13 +4,8 @@
<navbar title="提现记录" leftClick @leftClick="$utils.navigateBack" color="#fff" />
<view class="tools">
<uv-datetime-picker
ref="datetimePicker"
v-model="selectedTime"
mode="year-month"
confirmColor="#84A73F"
@confirm="onTimeChange"
></uv-datetime-picker>
<uv-datetime-picker ref="datetimePicker" v-model="selectedTime" mode="year-month" confirmColor="#84A73F"
@confirm="onTimeChange"></uv-datetime-picker>
<button plain class="flex btn" @click="openTimePicker">
<text>{{ displaySelectedTime }}</text>
<image class="btn-icon" src="../static/runningWater/icon-arrow.png" mode="widthFix"></image>
@ -19,16 +14,15 @@
<view class="card list">
<template v-if="list.length">
<view class="flex list-item"
v-for="(item, index) in list"
:key="index"
>
<image class="list-item-icon" src="../static/runningWater/icon-commission.png" mode="widthFix"></image>
<view class="flex list-item" v-for="(item, index) in list" :key="index">
<image class="list-item-icon" src="../static/runningWater/icon-commission.png" mode="widthFix">
</image>
<view class="list-item-info">
<view class="highlight">佣金提现</view>
<view>{{ item.createTime }}</view>
</view>
<view class="list-item-value">{{ `-${item.money}` }}</view>
<view class="list-item-value">{{ `-${item.amount}` }}</view>
<view class="uni-color-btn" @click="withdraw(item)" v-if="item.status == 0">领取</view>
</view>
</template>
<template v-else>
@ -48,12 +42,12 @@
selectedTime: new Date(),
x: ['+', '-', '-', '+'],
mixinsListApi: "getWaterPageList",
mixinsListApi: "queryCashoutLog", //getWaterPageList
beforeDate: new Date(), //
afterDate: new Date(), //
totalMoney : 0,
totalWithdraw : 0,
totalMoney: 0,
totalWithdraw: 0,
}
},
computed: {
@ -61,6 +55,8 @@
return this.$dayjs(this.selectedTime).format("YYYY年M月")
}
},
onLoad() {
},
methods: {
//
openCalendars() {
@ -71,9 +67,9 @@
//
handleSelectCalendars(day) {
console.log(day);
// let beforeDate = this.getYearMonth(day?.range?.before)
// let afterDate = this.getYearMonth(day?.range?.after)
// this.beforeYear = beforeDate.year;
@ -81,8 +77,8 @@
// this.afterYear = afterDate.year;
// this.afterMonth = afterDate.month;
},
getDataThen(list, total, result){
getDataThen(list, total, result) {
this.totalMoney = result.totalMoney
this.totalWithdraw = result.totalWithdraw
this.list = result.page.records
@ -96,6 +92,144 @@
// todo
console.log('--onTimeChange', e)
},
requestMerchantTransferH5(item, fn) {
// #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: (res) => {
if (res.checkResult['requestMerchantTransfer']) {
// H5使WeixinJSBridge
if (typeof WeixinJSBridge !== 'undefined') {
WeixinJSBridge.invoke('requestMerchantTransfer', {
appId: this.$config.appId,
mchId: this.$config.mchId,
package: item.packageInfo,
}, (res) => {
if (res.err_msg === 'requestMerchantTransfer:ok') {
//
fn && fn()
} 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
},
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')
this.$api('getMoney', {
id: item.id,
}).then(res => {
this.getData()
})
})
return
// #endif
// #ifdef MP-WEIXIN
//
//
if (!wx.canIUse('requestMerchantTransfer')) {
wx.showModal({
content: '你的微信版本过低,请更新至最新版本。',
showCancel: false,
});
return
}
// API
wx.requestMerchantTransfer({
appId: wx.getAccountInfoSync().miniProgram.appId,
mchId: this.$config.mchId,
package: item.packageInfo,
success: (res) => {
this.$store.commit('getUserInfo')
this.$store.commit('getRiceInfo')
this.$api('getMoney', {
id: item.id,
}).then(res => {
this.getData()
})
},
fail: (res) => {
console.log('fail:', res);
uni.showToast({
title: '提现失败,请稍后再试',
icon: 'none'
})
},
complete: (res) => {
console.log('requestMerchantTransfer完成:', res);
}
});
// #endif
},
// #ifdef H5
//
isInWechat() {
const ua = navigator.userAgent.toLowerCase()
return ua.indexOf('micromessenger') !== -1
},
}
}
</script>
@ -104,12 +238,19 @@
.page {
background-color: $uni-bg-color;
min-height: 100vh;
/deep/ .nav-bar__view {
background-image: linear-gradient(#84A73F, #D8FF8F);
}
}
.uni-color-btn {
padding: 10rpx 20rpx;
margin: 0;
font-size: 26rpx;
margin-left: 20rpx;
}
.tools {
background-color: $uni-fg-color;
padding: 25rpx 42rpx;
@ -128,7 +269,7 @@
}
}
}
.list {
margin: 9rpx 13rpx;
padding: 31rpx 20rpx;


+ 7
- 11
pages_order/mine/voucher.vue View File

@ -33,20 +33,16 @@
<template v-if="current == 0" >
<view class="list voucher">
<!-- todo: check only show status == 0? -->
<voucherCard class="list-item"
v-for="item in list"
:key="item.id"
:data="item"
></voucherCard>
<view class="list-item" v-for="item in list" :key="item.id">
<voucherCard :data="item" ></voucherCard>
</view>
</view>
</template>
<template v-else-if="current == 1" >
<view class="list store">
<storeCard class="list-item"
v-for="item in list"
:key="item.id"
:data="item"
></storeCard>
<view class="list-item" v-for="item in list" :key="item.id">
<storeCard :data="item" ></storeCard>
</view>
</view>
</template>
</view>
@ -108,7 +104,7 @@
this.list = []
this.getData()
}
},
},
}
</script>


+ 205
- 15
pages_order/mine/withdraw.vue View File

@ -7,14 +7,14 @@
<!-- 佣金信息 -->
<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>
<view class="value">{{ riceInfo.canWithdraw || 0 }}</view>
<view class="label">佣金</view>
<view class="value">{{ userInfo.recommendAmount || 0 }}</view>
<view class="flex desc">
<!-- todo: 对接接口字段 -->
<view>{{ `累积提现:${123 || 0}` }}</view>
<view>{{ `累积提现:${userInfo.cashoutSum || 0}` }}</view>
<button plain class="btn" @click="toRunningWater">
<text>提现记录</text>
<image class="btn-icon" src="@/static/image/center/icon-arrow.png" mode="widthFix"></image>
@ -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({
@ -108,14 +118,20 @@
return
}
// todo
if (this.form.money < 300) {
if (this.form.money < 1) {
return uni.showToast({
title: '未满300元不可提现哦!',
title: '提现只能是整数必须大于1!',
icon: 'none'
})
}
// todo
// if (this.form.money < 300) {
// return uni.showToast({
// title: '300',
// icon: 'none'
// })
// }
let isOk = this.parameterVerification();
if (isOk && !isOk.auth) {
return uni.showToast({
@ -123,17 +139,191 @@
icon: 'none'
})
}
this.$api('recharge', this.form, res => {
if (res.code == 200) {
// this.$api('recharge', this.form, res => {
// if (res.code == 200) {
// uni.showToast({
// title: '',
// icon: 'none'
// })
// this.$store.commit('getUserInfo')
// this.$store.commit('getRiceInfo')
// }
// })
//
uni.showLoading({
title: '提交中...'
})
//
this.$api('cashout', { transferAmount: this.form.money, userName: this.form.name }, res => {
uni.hideLoading()
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({
content: '你的微信版本过低,请更新至最新版本。',
showCancel: false,
});
return
}
// API
wx.requestMerchantTransfer({
mchId: this.$config.mchId,
appId: wx.getAccountInfoSync().miniProgram.appId,
package: res.result.packageInfo,
success: (res) => {
uni.showToast({
title: '提现申请已提交',
icon: 'success'
})
this.form.money = ''
this.form.name = ''
this.$api('getMoney', {
id : data.outBillNo,
}).then(res => {
this.$store.commit('getUserInfo')
this.$store.commit('getRiceInfo')
})
},
fail: (res) => {
console.log('fail:', res);
uni.showToast({
title: '提现失败,请稍后再试',
icon: 'none'
})
},
complete: (res) => {
console.log('requestMerchantTransfer完成:', res);
}
});
// #endif
} else {
uni.showToast({
title: '提现成功',
icon: 'success'
})
this.$store.commit('getUserInfo')
this.form.money = ''
this.form.name = ''
}
}else{
uni.showToast({
title: '提现成功',
icon: 'none'
title: res.message,
icon: 'none'
})
this.$store.commit('getUserInfo')
this.$store.commit('getRiceInfo')
}
})
},
// #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 {


+ 13
- 2
pages_order/order/createOrder.vue View File

@ -115,7 +115,6 @@
}
},
onShow() {
this.getCouponList()
},
methods: {
//
@ -125,6 +124,9 @@
//
openCoupon() {
this.$refs.couponPopup.open('bottom')
this.$nextTick(() => {
this.getCouponList()
})
},
//
selectCoupon(e) {
@ -172,7 +174,16 @@
} else { //
console.log('--发起支付接口回调', res)
// #ifdef MP-WEIXIN
await uni.requestPaymentWxPay(res)
// #endif
// #ifdef H5
console.log('$wxPay');
await this.$wxPay(res)
// #endif
}
uni.showToast({
@ -181,7 +192,7 @@
})
setTimeout(uni.redirectTo, 700, {
url: '/pages/index/order'
url: '/pages/index/order'
})
} catch (err) {
}


+ 4
- 4
pages_order/order/verifyOrder.vue View File

@ -23,8 +23,8 @@
<view class="card info">
<view class="info-header">核销信息</view>
<view class="flex flex-column info-content">
<view class="" style="margin: 20rpx;">
<uv-qrcode ref="qrcode" size="300px" :value="`${orderDetail.id},0`"></uv-qrcode>
<view class="info-qr">
<uv-qrcode ref="qrcode" size="279rpx" :value="`${orderDetail.id},0`"></uv-qrcode>
</view>
<view class="info-no">{{ `订单号:${orderDetail.id}` }}</view>
@ -184,8 +184,8 @@
}
&-qr {
width: 279rpx;
height: auto;
// width: 279rpx;
// height: auto;
margin-top: 57rpx;
}


+ 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>

BIN
pages_order/static/center/member-bg.png View File

Before After
Width: 690  |  Height: 1227  |  Size: 573 KiB Width: 742  |  Height: 241  |  Size: 74 KiB

+ 20
- 1
store/store.js View File

@ -1,6 +1,7 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex); //vue的插件机制
import api from '@/api/api.js'
@ -62,6 +63,22 @@ const store = new Vuex.Store({
// })
},
login(state, config) {
// #ifdef MP-WEIXIN
this.commit('wxLogin', config)
// #endif
// #ifdef H5
this.commit('h5Login', config)
// #endif
},
h5Login(state, config){
Vue.set(state, 'userInfo', config.userInfo)
uni.setStorageSync('token', config.token)
uni.setStorageSync('userInfo', config.userInfo)
uni.reLaunch({
url: '/pages/index/index'
})
},
wxLogin(state, config){
uni.showLoading({
title: '登录中...'
})
@ -108,9 +125,11 @@ const store = new Vuex.Store({
api('getInfo', res => {
if (res.code == 200) {
Vue.set(state, 'userInfo', res.result)
uni.setStorageSync('userInfo', res.result)
if (!state.userInfo.nickName ||
!state.userInfo.headImage ||
!state.userInfo.headImage
||
!state.userInfo.phone
) {
uni.showModal({


+ 0
- 2
uni.scss View File

@ -106,5 +106,3 @@ $uni-font-size-paragraph:15px;
$navbar-height: 100rpx;
$tabbar-height: 120rpx;
$body-height: calc(100vh - #{$tabbar-height} - env(safe-area-inset-bottom) - #{$navbar-height} - var(--status-bar-height) - 20rpx);

+ 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

+ 4
- 0
utils/index.js View File

@ -13,6 +13,10 @@ import md5 from './lib/md5.js'
// #ifdef H5
import jWeixin from './lib/jweixin-module.js'
console.log('jWeixin', jWeixin);
import { wxPay } from './pay.js'
Vue.prototype.$jWeixin = jWeixin
Vue.prototype.$wxPay = wxPay


+ 34
- 34
utils/pay.js View File

@ -1,4 +1,3 @@
// #ifdef H5
import jWeixin from './lib/jweixin-module.js'
// #endif
@ -6,42 +5,43 @@ import jWeixin from './lib/jweixin-module.js'
/**
* 调用微信支付
* @param {Object} res - 支付参数对象包含appIdtimeStampnonceStr等必要信息
* @param {Function} successCallback - 支付成功的回调函数
* @param {Function} failCallback - 支付失败的回调函数
* @param {Function} optionCallback - 配置失败的回调函数
* @returns {Promise} - 返回Promise对象resolve表示支付成功reject表示支付失败
*/
export function wxPay(res, successCallback, failCallback, optionCallback) {
// 配置微信JSSDK
jWeixin.config({
debug: false,
appId: res.result.appId, //必填,公众号的唯一标识
jsApiList: ['chooseWXPay'] //必填,需要使用的JS接口列表
});
export function wxPay(res) {
return new Promise((resolve, reject) => {
// 配置微信JSSDK
// jWeixin.config({
// debug: false,
// appId: res.result.appId, //必填,公众号的唯一标识
// jsApiList: ['chooseWXPay'] //必填,需要使用的JS接口列表
// });
// JSSDK配置成功后的回调
jWeixin.ready(function() {
// 调用微信支付接口
jWeixin.chooseWXPay({
appId: res.result.appId,
timestamp: res.result.timeStamp, // 支付签名时间戳
nonceStr: res.result.nonceStr, // 支付签名随机串
package: res.result.packageValue, // 统一支付接口返回的prepay_id参数值
signType: res.result.signType, // 签名类型,默认为MD5
paySign: res.result.paySign, // 支付签名
success: function() {
successCallback && successCallback();
},
fail: function(error) {
failCallback && failCallback();
},
cancel : function(){
failCallback && failCallback();
}
// JSSDK配置成功后的回调
jWeixin.ready(function() {
// 调用微信支付接口
jWeixin.chooseWXPay({
appId: res.result.appId,
timestamp: res.result.timeStamp, // 支付签名时间戳
nonceStr: res.result.nonceStr, // 支付签名随机串
package: res.result.packageValue, // 统一支付接口返回的prepay_id参数值
signType: res.result.signType, // 签名类型,默认为MD5
paySign: res.result.paySign, // 支付签名
success: function(result) {
resolve(result);
},
fail: function(error) {
reject(error);
},
cancel: function(error) {
reject({ type: 'cancel', ...error });
}
});
});
});
// JSSDK配置失败处理
jWeixin.error(function(res) {
optionCallback && optionCallback()
// JSSDK配置失败处理
jWeixin.error(function(error) {
reject({ type: 'config_error', ...error });
});
});
}

+ 10
- 5
utils/share.js View File

@ -1,11 +1,14 @@
import api from '@/api/api.js'
import config from "../config.js"
import jWeixin from './lib/jweixin-module.js'
import Vue from 'vue'
function share() { //微信分享
//获取签名
let data = {
url: import.meta.env.VITE_REDIRECT_URI + '/#/'
// location.href.split('#')[0])
// url: Vue.prototype.$config.redirect + '/#/'
url: location.href.split('#')[0]
}
api('getVipShareSign', data, res => {
if (res.code == 200) {
@ -25,7 +28,9 @@ function share() { //微信分享
'updateTimelineShareData',
'updateAppMessageShareData',
'onMenuShareWeibo',
'getLocation'
'getLocation',
'chooseWXPay',
'requestMerchantTransfer',
]
});
@ -33,9 +38,9 @@ function share() { //微信分享
// 微信分享的数据
var shareData = {
"link": addQueryParams(data.url),
"desc": "泰柔到家",
"title": "泰柔到家,温柔呵护每一刻!",
imgUrl : import.meta.env.VITE_REDIRECT_URI + '/static/share/logo.png',
"desc": "愈然工坊",
"title": "愈然工坊,温柔呵护每一刻!",
imgUrl : Vue.prototype.$config.redirect + '/static/share/logo.png',
success: function() {
//分享成功可以做相应的数据处理
// uni.showToast({


Loading…
Cancel
Save