Compare commits
19 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
0b7d15a3ed | |
|
|
439c41eb42 | |
|
|
801f4953ff | |
|
|
35cc05448f | |
|
|
17e2c6c906 | |
|
|
80c1a21db0 | |
|
|
4605e3538d | |
|
|
33e9728400 | |
|
|
9ab3c29168 | |
|
|
b3d6a3dc1b | |
|
|
9bff65377b | |
|
|
554e83f6bc | |
|
|
ecc9fa14e6 | |
|
|
dfd2fc52b2 | |
|
|
33bf3d47c5 | |
|
|
6a8b1e4f10 | |
|
|
d6a892fe6e | |
|
|
0cbf683633 | |
|
|
1941fd60e5 |
114
App.vue
114
App.vue
|
|
@ -4,10 +4,26 @@
|
|||
export default {
|
||||
globalData: {
|
||||
// 用于从首页跳转到服务页面时传递需要高亮的分类
|
||||
serviceCategory: null
|
||||
serviceCategory: null,
|
||||
// 扫码进入时携带的邀请码(小程序码 scene),格式:邀请类型-用户id,如 '0-123'
|
||||
inviteCode: null
|
||||
},
|
||||
onLaunch: function() {
|
||||
onLaunch: function(options) {
|
||||
console.log('App Launch')
|
||||
// 扫码进入:小程序码的 scene 即邀请码(格式 邀请类型-用户id,如 0-123、1-123)
|
||||
const scene = (options && options.query && options.query.scene) || (options && options.scene)
|
||||
const sceneStr = scene != null ? String(scene) : ''
|
||||
if (sceneStr && /^\d+-\d+$/.test(sceneStr)) {
|
||||
this.globalData.inviteCode = sceneStr
|
||||
uni.setStorageSync('inviteCode', sceneStr)
|
||||
// 未登录时直接进入登录注册页,便于邀请流程
|
||||
if (!uni.getStorageSync('token')) {
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
}, 50)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 检查登录状态
|
||||
this.checkLoginStatus()
|
||||
},
|
||||
|
|
@ -19,52 +35,52 @@
|
|||
console.log(options)
|
||||
|
||||
// 2. 注意:scene 返回的是数字类型,不是字符串!原生代码中写 '1038' 会导致判断失败
|
||||
// if (options.scene === 1038 &&
|
||||
// options.referrerInfo?.appId === 'wxef277996acc166c3') {
|
||||
if (options.scene === 1038 &&
|
||||
options.referrerInfo?.appId === 'wxef277996acc166c3') {
|
||||
|
||||
// // 从收银台小程序返回的逻辑
|
||||
// const extraData = options.referrerInfo.extraData;
|
||||
// console.log("extraData",extraData)
|
||||
// 从收银台小程序返回的逻辑
|
||||
const extraData = options.referrerInfo.extraData;
|
||||
console.log("extraData",extraData)
|
||||
|
||||
// if (!extraData) {
|
||||
// uni.showToast({
|
||||
// title: '当前通过物理按键返回,未接收到返参,建议自行查询交易结果',
|
||||
// icon: 'none',
|
||||
// duration: 3000
|
||||
// });
|
||||
// } else {
|
||||
// if (extraData.code === 'success') {
|
||||
// // 有返回成功标记后,启动轮询查询订单状态
|
||||
// // 1. 优先使用收银台回传的订单号
|
||||
// const orderNumberFromExtra =
|
||||
// extraData.orderNumber || extraData.reqsn || extraData.orderNo;
|
||||
// // 2. 如果对方没有回传,则使用我们在跳转前自己缓存到本地的订单号
|
||||
// const storedOrderNumber = uni.getStorageSync("lastOrderNumber");
|
||||
// const orderNumber = orderNumberFromExtra || storedOrderNumber;
|
||||
if (!extraData) {
|
||||
uni.showToast({
|
||||
title: '当前通过物理按键返回,未接收到返参,建议自行查询交易结果',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
} else {
|
||||
if (extraData.code === 'success') {
|
||||
// 有返回成功标记后,启动轮询查询订单状态
|
||||
// 1. 优先使用收银台回传的订单号
|
||||
const orderNumberFromExtra =
|
||||
extraData.orderNumber || extraData.reqsn || extraData.orderNo;
|
||||
// 2. 如果对方没有回传,则使用我们在跳转前自己缓存到本地的订单号
|
||||
const storedOrderNumber = uni.getStorageSync("lastOrderNumber");
|
||||
const orderNumber = orderNumberFromExtra || storedOrderNumber;
|
||||
|
||||
// if (orderNumber) {
|
||||
// this.startOrderStatusPolling(orderNumber);
|
||||
// } else {
|
||||
// uni.showToast({
|
||||
// title: '支付返回缺少订单号,请稍后在服务记录中查看',
|
||||
// icon: 'none',
|
||||
// duration: 3000
|
||||
// });
|
||||
// }
|
||||
// } else if (extraData.code === 'cancel') {
|
||||
// uni.showToast({
|
||||
// title: '支付已取消',
|
||||
// icon: 'none'
|
||||
// });
|
||||
// } else {
|
||||
// uni.showToast({
|
||||
// title: `支付失败:${extraData.errmsg || '未知错误'}`,
|
||||
// icon: 'none',
|
||||
// duration: 3000
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if (orderNumber) {
|
||||
this.startOrderStatusPolling(orderNumber);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '结算返回缺少订单号,请稍后在服务记录中查看',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
} else if (extraData.code === 'cancel') {
|
||||
uni.showToast({
|
||||
title: '已取消',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: `结算失败:${extraData.errmsg || '未知错误'}`,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
|
|
@ -102,7 +118,7 @@
|
|||
}, 100)
|
||||
}
|
||||
},
|
||||
// 启动轮询支付状态:回到小程序立马查一次,失败则每隔 3 秒再查,最多再查 3 次(共 4 次)
|
||||
// 启动轮询状态:回到小程序立马查一次,失败则每隔 3 秒再查,最多再查 3 次(共 4 次)
|
||||
startOrderStatusPolling(orderNumber) {
|
||||
if (this._orderStatusTimer) {
|
||||
clearInterval(this._orderStatusTimer);
|
||||
|
|
@ -120,7 +136,7 @@
|
|||
clearInterval(this._orderStatusTimer);
|
||||
this._orderStatusTimer = null;
|
||||
uni.removeStorageSync("lastOrderNumber");
|
||||
uni.showToast({ title: '支付成功', icon: 'success' });
|
||||
uni.showToast({ title: '结算成功', icon: 'success' });
|
||||
uni.navigateTo({
|
||||
url: '/pages/profileSub/serviceRecords?tab=pending_verification'
|
||||
});
|
||||
|
|
@ -130,13 +146,13 @@
|
|||
clearInterval(this._orderStatusTimer);
|
||||
this._orderStatusTimer = null;
|
||||
uni.showToast({
|
||||
title: '支付状态确认超时,请稍后在服务记录中查看',
|
||||
title: '状态确认超时,请稍后在服务记录中查看',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询订单支付状态失败:', error);
|
||||
console.error('查询订单状态失败:', error);
|
||||
if (times >= maxTimes) {
|
||||
clearInterval(this._orderStatusTimer);
|
||||
this._orderStatusTimer = null;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export function login(data) {
|
|||
* @param {String} data.phoneCode 手机 code, 小程序通过 wx.getPhoneNumber 方法获得
|
||||
* @param {String} data.loginCode 登录 code, 小程序通过 wx.login 方法获得
|
||||
* @param {String} data.state state 参数,必填,用于回调的随机值
|
||||
* @param {String} data.inviteCode 邀请码,可选(第一位是类型第二位是用户id,例如:1-1, 0-1)
|
||||
* @param {String} data.inviteCode 邀请码,可选。格式 "邀请类型-用户id":0=会员邀请会员,1=系统用户邀请会员。例如 0-123、1-123
|
||||
* @returns {Promise} 返回登录结果(包含token等)
|
||||
*/
|
||||
export function loginByPhone(data) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
// 基础URL配置(注意:末尾不要加斜杠)
|
||||
const BASE_URL = 'https://guangsh.manage.hschengtai.com'
|
||||
// const BASE_URL = 'http://192.168.5.134:48085'
|
||||
// const BASE_URL = 'http://192.168.0.97:48085'
|
||||
// const BASE_URL = 'http://192.168.5.135:48085'
|
||||
// 是否正在刷新token(防止并发刷新)
|
||||
let isRefreshing = false
|
||||
// 等待刷新完成的请求队列
|
||||
|
|
@ -279,7 +280,8 @@ export function request(options = {}) {
|
|||
uni.showModal({
|
||||
title: '提示',
|
||||
content: res.data.msg || res.data.message || '账号未登录,请前往登录',
|
||||
showCancel: false,
|
||||
showCancel: true,
|
||||
cancelText: '取消',
|
||||
confirmText: '去登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
|
|
@ -328,7 +330,8 @@ export function request(options = {}) {
|
|||
uni.showModal({
|
||||
title: '提示',
|
||||
content: errorMsg,
|
||||
showCancel: false,
|
||||
showCancel: true,
|
||||
cancelText: '取消',
|
||||
confirmText: '去登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,17 @@ export function createRealNameInfo(data) {
|
|||
})
|
||||
}
|
||||
|
||||
// 获得实名信息
|
||||
export function getRealNameInfo(data) {
|
||||
return request({
|
||||
url: '/app-api/member/user-real-name/get',
|
||||
method: 'GET',
|
||||
data: data,
|
||||
showLoading: false,
|
||||
needAuth: true // 需要token认证
|
||||
})
|
||||
}
|
||||
|
||||
// 我的收藏
|
||||
export function getMyCollect(data) {
|
||||
return request({
|
||||
|
|
@ -39,6 +50,17 @@ export function getMyCollect(data) {
|
|||
})
|
||||
}
|
||||
|
||||
// 业务数据获取,主要是分享,收藏等根据业务id 和类型获取数据
|
||||
export function getBusinessData(data) {
|
||||
return request({
|
||||
url: '/app-api/member/lu-buiness/get',
|
||||
method: 'GET',
|
||||
data: data,
|
||||
showLoading: false,
|
||||
needAuth: true // 需要token认证
|
||||
})
|
||||
}
|
||||
|
||||
// 创建会员建议
|
||||
export function createLaborUnionSuggest(data) {
|
||||
return request({
|
||||
|
|
@ -50,6 +72,17 @@ export function createLaborUnionSuggest(data) {
|
|||
})
|
||||
}
|
||||
|
||||
// 获得会员建议分页
|
||||
export function getLaborUnionSuggestPage(data) {
|
||||
return request({
|
||||
url: '/app-api/member/labor-union-suggest/page',
|
||||
method: 'GET',
|
||||
data: data,
|
||||
showLoading: false,
|
||||
needAuth: true // 需要token认证
|
||||
})
|
||||
}
|
||||
|
||||
// 发布消息
|
||||
export function createLaborUnionMessage(data) {
|
||||
return request({
|
||||
|
|
@ -60,3 +93,43 @@ export function createLaborUnionMessage(data) {
|
|||
needAuth: true // 需要token认证
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邀请小程序码图片。入参格式与后端约定一致,支持返回 URL 或 Base64。
|
||||
* @param {String} inviteCode 邀请码,格式 邀请类型-用户id,如 '0-123',会作为 scene 传给后端(若后端需数字可传 userId)
|
||||
* @returns {Promise<String>} 小程序码图片 URL 或 Data URL(Base64),失败则抛出
|
||||
*/
|
||||
export function getInviteQRCode(inviteCode) {
|
||||
// 入参示例:scene、path、width、autoColor、checkPath、hyaline
|
||||
const scene = inviteCode || ''
|
||||
const path = 'pages/login/login'
|
||||
return request({
|
||||
url: '/app-api/member/social-user/wxa-qrcode',
|
||||
method: 'POST',
|
||||
data: {
|
||||
scene,
|
||||
path,
|
||||
width: 430,
|
||||
autoColor: true,
|
||||
checkPath: true,
|
||||
hyaline: true
|
||||
},
|
||||
showLoading: false,
|
||||
needAuth: true
|
||||
}).then(res => {
|
||||
if (res == null) return null
|
||||
// 接口可能直接返回 base64 字符串(request 里 resolve 的 data 即为该字符串)
|
||||
if (typeof res === 'string') {
|
||||
const raw = res.trim()
|
||||
if (raw.startsWith('data:image')) return raw
|
||||
if (raw.length > 0) return 'data:image/png;base64,' + raw
|
||||
return null
|
||||
}
|
||||
const url = res.url || (res.data && res.data.url)
|
||||
if (url) return url
|
||||
const base64 = res.data && typeof res.data === 'string' ? res.data : (res.data && res.data.data)
|
||||
if (typeof base64 === 'string' && base64.length > 0)
|
||||
return base64.startsWith('data:image') ? base64 : 'data:image/png;base64,' + base64
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
|
@ -30,10 +30,10 @@ export function getGuildStoreDetail(id) {
|
|||
})
|
||||
}
|
||||
|
||||
// 获得工会优惠券
|
||||
export function getGuildCoupon(params = {}) {
|
||||
// 获得工会代金券
|
||||
export function getGuildVoucher(params = {}) {
|
||||
return request({
|
||||
url: '/app-api/member/labor-union-coupon/page',
|
||||
url: '/app-api/member/labor-union-voucher/page',
|
||||
method: 'GET',
|
||||
data: params,
|
||||
})
|
||||
|
|
@ -100,12 +100,12 @@ export function isNeedMap(params = {}) {
|
|||
})
|
||||
}
|
||||
|
||||
// 工会优惠券/商品下单并发起微信支付(需要后端返回小程序支付参数)
|
||||
// 工会代金券/商品下单并发起微信支付(需要后端返回小程序支付参数)
|
||||
// 约定:后端返回字段需包含 timeStamp、nonceStr、package、signType、paySign(或等价字段)
|
||||
// export function createGuildCouponWxPay(params = {}) {
|
||||
// export function createGuildVoucherWxPay(params = {}) {
|
||||
// return request({
|
||||
// // TODO: 如后端实际路径不同,请替换这里的 url
|
||||
// url: '/app-api/member/labor-union-coupon/pay',
|
||||
// url: '/app-api/member/labor-union-voucher/pay',
|
||||
// method: 'POST',
|
||||
// data: params,
|
||||
// showLoading: true,
|
||||
|
|
@ -136,18 +136,18 @@ export function updateLuOrderPayStatus(params = {}){
|
|||
})
|
||||
}
|
||||
|
||||
// 删除优惠卷购买 (暂时不用)
|
||||
// export function delCouponPurchaseBuy(id){
|
||||
// 删除代金券购买 (暂时不用)
|
||||
// export function delVoucherPurchaseBuy(id){
|
||||
// return request({
|
||||
// url: '/app-api/member/labor-union-coupon-purchase/delete?id='+id,
|
||||
// url: '/app-api/member/labor-union-voucher-purchase/delete?id='+id,
|
||||
// method: 'DELETE',
|
||||
// })
|
||||
// }
|
||||
|
||||
// // 取消优惠卷购买 (暂时不用)
|
||||
// export function cancelCouponPurchaseBuy(id){
|
||||
// // 取消代金券购买 (暂时不用)
|
||||
// export function cancelVoucherPurchaseBuy(id){
|
||||
// return request({
|
||||
// url: '/app-api/member/labor-union-coupon-purchase/cancel?id='+id,
|
||||
// url: '/app-api/member/labor-union-voucher-purchase/cancel?id='+id,
|
||||
// method: 'POST',
|
||||
// })
|
||||
// }
|
||||
|
|
@ -183,4 +183,22 @@ export function cancelOrder(params = {}){
|
|||
})
|
||||
}
|
||||
|
||||
// 获得优惠卷分页
|
||||
export function getLuCouponPage(params = {}) {
|
||||
return request({
|
||||
url: '/app-api/member/lu-coupon/page',
|
||||
method: 'GET',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
// 获得优惠卷详情
|
||||
export function getLuCouponDetail(id) {
|
||||
return request({
|
||||
url: '/app-api/member/lu-coupon/get',
|
||||
method: 'GET',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,13 @@
|
|||
</view> -->
|
||||
|
||||
<view class="action-item" @click="handleLike">
|
||||
<image class="action-icon" src="/static/service/like_icon.png" mode="aspectFill"></image>
|
||||
<text class="action-text">点赞</text>
|
||||
<image
|
||||
class="action-icon"
|
||||
:class="{ active: isLiked }"
|
||||
:src="isLiked ? '/static/service/like_icon_active.png' : '/static/service/like_icon.png'"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<text class="action-text" :class="{ active: isLiked }">点赞</text>
|
||||
</view>
|
||||
|
||||
<button class="action-item share-button" open-type="share" @click="handleShareClick">
|
||||
|
|
@ -16,8 +21,13 @@
|
|||
</button>
|
||||
|
||||
<view class="action-item" @click="handleCollect">
|
||||
<image class="action-icon" src="/static/service/collect_icon.png" mode="aspectFill"></image>
|
||||
<text class="action-text">收藏</text>
|
||||
<image
|
||||
class="action-icon"
|
||||
:class="{ active: isCollected }"
|
||||
:src="isCollected ? '/static/service/collect_icon_active.png' : '/static/service/collect_icon.png'"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<text class="action-text" :class="{ active: isCollected }">收藏</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
|
@ -255,11 +265,6 @@ export default {
|
|||
&.active {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.icon-text {
|
||||
font-size: 48rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.action-text {
|
||||
|
|
|
|||
29
pages.json
29
pages.json
|
|
@ -52,6 +52,13 @@
|
|||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "selectCoupon",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择优惠卷",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "mapDetail",
|
||||
"style": {
|
||||
|
|
@ -91,14 +98,8 @@
|
|||
"navigationBarTitleText": "投诉建议",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "postMessage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "发布消息",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -138,6 +139,20 @@
|
|||
"navigationBarTitleText": "隐私政策",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "myCoupons",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的优惠卷",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "couponDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "优惠卷详情",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
<template>
|
||||
<view class="complaints-page">
|
||||
<!-- 头部区域 -->
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="投诉建议" />
|
||||
|
||||
<!-- 表单内容区 -->
|
||||
<scroll-view class="form-content" scroll-y="true">
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<scroll-view
|
||||
class="form-content"
|
||||
scroll-y="true"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="listRefreshing"
|
||||
@refresherrefresh="onListRefresh"
|
||||
@scrolltolower="onListLoadMore"
|
||||
:lower-threshold="80"
|
||||
>
|
||||
<!-- 投诉建议表单 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">填写投诉建议</view>
|
||||
|
|
@ -52,12 +60,41 @@
|
|||
{{ loading ? '提交中...' : '提交' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 我的投诉建议列表 -->
|
||||
<view class="list-section">
|
||||
<view class="section-title">我的投诉建议</view>
|
||||
<view class="empty-list" v-if="!listLoading && suggestList.length === 0">
|
||||
<text class="empty-text">暂无投诉建议记录</text>
|
||||
</view>
|
||||
<view
|
||||
class="suggest-card"
|
||||
v-for="(item, index) in suggestList"
|
||||
:key="item.id || index"
|
||||
>
|
||||
<view class="card-header">
|
||||
<text class="card-title">{{ item.title || '无标题' }}</text>
|
||||
<text class="card-time">{{ formatTime(item.createTime) }}</text>
|
||||
</view>
|
||||
<text class="card-content" v-if="item.content">{{ item.content }}</text>
|
||||
<view class="card-reply" v-if="getReplyText(item)">
|
||||
<text class="reply-label">{{ item.replyName }}回复:</text>
|
||||
<text class="reply-text">{{ getReplyText(item) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-footer" v-if="suggestList.length > 0">
|
||||
<text v-if="listLoadMore" class="footer-text">加载中...</text>
|
||||
<text v-else-if="!listHasMore" class="footer-text">没有更多了</text>
|
||||
<text v-else class="footer-text">上拉加载更多</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createLaborUnionSuggest } from '@/api/profile.js'
|
||||
import { createLaborUnionSuggest,getLaborUnionSuggestPage } from '@/api/profile.js'
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
|
||||
export default {
|
||||
|
|
@ -66,22 +103,91 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
loading: false,
|
||||
formData: {
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
suggestList: [],
|
||||
listPageNo: 1,
|
||||
listPageSize: 10,
|
||||
listHasMore: true,
|
||||
listLoading: false,
|
||||
listLoadMore: false,
|
||||
listRefreshing: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44
|
||||
},
|
||||
// 是否可以提交
|
||||
canSubmit() {
|
||||
return this.formData.title && this.formData.title.trim().length >= 1
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0
|
||||
this.loadSuggestList()
|
||||
},
|
||||
methods: {
|
||||
formatTime(val) {
|
||||
if (val == null || val === '') return ''
|
||||
let ts = Number(val)
|
||||
if (!Number.isFinite(ts)) return val
|
||||
if (String(ts).length <= 10) ts *= 1000
|
||||
const d = new Date(ts)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const h = String(d.getHours()).padStart(2, '0')
|
||||
const min = String(d.getMinutes()).padStart(2, '0')
|
||||
return `${y}-${m}-${day} ${h}:${min}`
|
||||
},
|
||||
getReplyText(item) {
|
||||
const text = item.replyContent
|
||||
return text ? String(text).trim() : ''
|
||||
},
|
||||
getReplyTime(item) {
|
||||
const t = item.replyTime
|
||||
return t != null && t !== '' ? this.formatTime(t) : ''
|
||||
},
|
||||
async loadSuggestList(append = false) {
|
||||
if (this.listLoading) return
|
||||
this.listLoading = true
|
||||
try {
|
||||
const res = await getLaborUnionSuggestPage({
|
||||
pageNo: this.listPageNo,
|
||||
pageSize: this.listPageSize
|
||||
})
|
||||
const raw = res.list || res.records || (res.data && (res.data.list || res.data.records)) || []
|
||||
const list = Array.isArray(raw) ? raw : []
|
||||
this.suggestList = append ? this.suggestList.concat(list) : list
|
||||
this.listHasMore = list.length >= this.listPageSize
|
||||
} catch (e) {
|
||||
console.error('加载投诉建议列表失败:', e)
|
||||
if (!append) this.suggestList = []
|
||||
uni.showToast({ title: '加载列表失败', icon: 'none' })
|
||||
} finally {
|
||||
this.listLoading = false
|
||||
this.listLoadMore = false
|
||||
this.listRefreshing = false
|
||||
}
|
||||
},
|
||||
onListRefresh() {
|
||||
this.listRefreshing = true
|
||||
this.listPageNo = 1
|
||||
this.listHasMore = true
|
||||
this.loadSuggestList()
|
||||
},
|
||||
onListLoadMore() {
|
||||
if (!this.listHasMore || this.listLoadMore || this.listLoading) return
|
||||
this.listLoadMore = true
|
||||
this.listPageNo += 1
|
||||
this.loadSuggestList(true)
|
||||
},
|
||||
// 提交表单
|
||||
async handleSubmit() {
|
||||
// 验证标题
|
||||
|
|
@ -110,10 +216,11 @@ export default {
|
|||
icon: 'success'
|
||||
})
|
||||
|
||||
// 延迟返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
this.formData.title = ''
|
||||
this.formData.content = ''
|
||||
this.listPageNo = 1
|
||||
this.listHasMore = true
|
||||
this.loadSuggestList()
|
||||
} catch (error) {
|
||||
console.error('提交投诉建议失败:', error)
|
||||
uni.showToast({
|
||||
|
|
@ -136,6 +243,22 @@ export default {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #e2e8f1;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
|
|
@ -235,7 +358,7 @@ export default {
|
|||
|
||||
.submit-section {
|
||||
margin-top: 40rpx;
|
||||
padding-bottom: 40rpx;
|
||||
padding-bottom: 24rpx;
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
|
|
@ -260,4 +383,99 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-section {
|
||||
margin-top: 24rpx;
|
||||
padding-bottom: 40rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 10rpx;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
padding: 60rpx 0;
|
||||
text-align: center;
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.suggest-card {
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.card-title {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1819;
|
||||
}
|
||||
|
||||
.card-time {
|
||||
font-size: 22rpx;
|
||||
color: #999999;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.card-reply {
|
||||
background: #f0f6ff;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx;
|
||||
border-left: 4rpx solid #004294;
|
||||
|
||||
.reply-label {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #004294;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.reply-text {
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.reply-time {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #999999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-footer {
|
||||
padding: 24rpx 0;
|
||||
text-align: center;
|
||||
.footer-text {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<view class="activities-list-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="工会活动" :show-placeholder="true" />
|
||||
|
||||
<!-- 活动列表 -->
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<scroll-view
|
||||
class="activities-scroll"
|
||||
scroll-y="true"
|
||||
|
|
@ -62,6 +62,7 @@
|
|||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -75,6 +76,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
activitiesList: [],
|
||||
// 分页相关
|
||||
page: 1,
|
||||
|
|
@ -86,7 +88,14 @@ export default {
|
|||
refreshing: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
this.loadActivities();
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -185,7 +194,22 @@ export default {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 活动列表 */
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #e2e8f1;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.activities-scroll {
|
||||
flex: 1;
|
||||
height: 0; // 配合 flex: 1 使用,让 scroll-view 可以滚动
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<view class="my-collect-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="我的收藏" />
|
||||
|
||||
<!-- 收藏列表 -->
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<scroll-view
|
||||
class="collect-scroll"
|
||||
scroll-y="true"
|
||||
|
|
@ -58,10 +58,11 @@
|
|||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMyCollect } from "@/api/profile.js";
|
||||
import { getMyCollect,getBusinessData } from "@/api/profile.js";
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
|
||||
export default {
|
||||
|
|
@ -70,6 +71,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
collectList: [],
|
||||
// 分页相关
|
||||
pageNo: 1,
|
||||
|
|
@ -81,7 +83,14 @@ export default {
|
|||
refreshing: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
this.loadCollectList();
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -160,11 +169,17 @@ export default {
|
|||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
},
|
||||
|
||||
// 收藏项点击
|
||||
// 收藏项点击:跳转详情,详情页通过 getBusinessData(id: collectId, type: collectType) 拉取数据
|
||||
handleCollectClick(item) {
|
||||
// uni.navigateTo({
|
||||
// url: `/pages/detail/richTextDetail?id=${item.collectId}`,
|
||||
// });
|
||||
const collectId = item.collectId ?? item.id;
|
||||
const collectType = item.collectType ?? item.type ?? "";
|
||||
if (collectId == null || collectId === "") {
|
||||
uni.showToast({ title: "数据异常", icon: "none" });
|
||||
return;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/detail/richTextDetail?collectId=${encodeURIComponent(collectId)}&collectType=${encodeURIComponent(collectType)}&hideBar=1`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -179,7 +194,22 @@ export default {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 收藏列表 */
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #e2e8f1;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.collect-scroll {
|
||||
flex: 1;
|
||||
height: 0; // 配合 flex: 1 使用,让 scroll-view 可以滚动
|
||||
|
|
|
|||
|
|
@ -1,617 +0,0 @@
|
|||
<template>
|
||||
<view class="post-message-page">
|
||||
<!-- 头部区域 -->
|
||||
<NavHeader title="发布消息" />
|
||||
|
||||
<!-- 表单内容区 -->
|
||||
<scroll-view class="form-content" scroll-y="true">
|
||||
<!-- 基本信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
|
||||
<!-- 标题 -->
|
||||
<view class="form-item">
|
||||
<view class="input-wrapper">
|
||||
<text class="input-label">标题</text>
|
||||
<input
|
||||
class="input-field"
|
||||
type="text"
|
||||
v-model="formData.title"
|
||||
placeholder="请输入消息标题"
|
||||
maxlength="100"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 摘要 -->
|
||||
<view class="form-item">
|
||||
<view class="textarea-wrapper">
|
||||
<text class="textarea-label">摘要 <text class="optional">(选填)</text></text>
|
||||
<textarea
|
||||
class="textarea-field"
|
||||
v-model="formData.summary"
|
||||
placeholder="请输入消息摘要..."
|
||||
maxlength="200"
|
||||
:auto-height="true"
|
||||
></textarea>
|
||||
<view class="char-count">
|
||||
<text>{{ formData.summary.length }}/200</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容 -->
|
||||
<view class="form-item">
|
||||
<view class="textarea-wrapper">
|
||||
<text class="textarea-label">内容 <text class="optional">(选填)</text></text>
|
||||
<textarea
|
||||
class="textarea-field"
|
||||
v-model="formData.content"
|
||||
placeholder="请输入消息详细内容..."
|
||||
maxlength="2000"
|
||||
:auto-height="true"
|
||||
></textarea>
|
||||
<view class="char-count">
|
||||
<text>{{ formData.content.length }}/2000</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 封面图片 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">封面图片</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="upload-wrapper">
|
||||
<text class="upload-label">封面图片 <text class="optional">(选填)</text></text>
|
||||
<view class="upload-box" @click="chooseCoverImage">
|
||||
<image
|
||||
v-if="formData.coverUrl"
|
||||
class="uploaded-image"
|
||||
:src="formData.coverUrl"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<view v-else class="upload-placeholder">
|
||||
<text class="upload-icon">+</text>
|
||||
<text class="upload-text">点击上传封面</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 其他设置 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">其他设置</view>
|
||||
|
||||
<!-- 跳转链接 -->
|
||||
<view class="form-item">
|
||||
<view class="input-wrapper">
|
||||
<text class="input-label">跳转链接 <text class="optional">(选填)</text></text>
|
||||
<input
|
||||
class="input-field"
|
||||
type="text"
|
||||
v-model="formData.jumpUrl"
|
||||
placeholder="请输入跳转链接,如:https://www.example.com"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 消息类型 -->
|
||||
<view class="form-item">
|
||||
<view class="input-wrapper">
|
||||
<text class="input-label">消息类型 <text class="optional">(选填)</text></text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="messageTypeOptions"
|
||||
range-key="label"
|
||||
:value="messageTypeIndex"
|
||||
@change="handleMessageTypeChange"
|
||||
>
|
||||
<view class="picker-view">
|
||||
<text :class="['picker-text', messageTypeIndex === -1 ? 'placeholder' : '']">
|
||||
{{ messageTypeIndex !== -1 ? messageTypeOptions[messageTypeIndex].label : '请选择消息类型' }}
|
||||
</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 等级 -->
|
||||
<view class="form-item">
|
||||
<view class="input-wrapper">
|
||||
<text class="input-label">等级 <text class="optional">(选填)</text></text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="gradeOptions"
|
||||
range-key="label"
|
||||
:value="gradeIndex"
|
||||
@change="handleGradeChange"
|
||||
>
|
||||
<view class="picker-view">
|
||||
<text :class="['picker-text', gradeIndex === -1 ? 'placeholder' : '']">
|
||||
{{ gradeIndex !== -1 ? gradeOptions[gradeIndex].label : '请选择等级' }}
|
||||
</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开始时间 -->
|
||||
<view class="form-item">
|
||||
<view class="input-wrapper">
|
||||
<text class="input-label">开始时间 <text class="optional">(选填)</text></text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="startTimeDisplay"
|
||||
@change="handleStartTimeChange"
|
||||
>
|
||||
<view class="picker-view">
|
||||
<text :class="['picker-text', !startTimeDisplay ? 'placeholder' : '']">
|
||||
{{ startTimeDisplay || '请选择开始时间' }}
|
||||
</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 结束时间 -->
|
||||
<view class="form-item">
|
||||
<view class="input-wrapper">
|
||||
<text class="input-label">结束时间 <text class="optional">(选填)</text></text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="endTimeDisplay"
|
||||
@change="handleEndTimeChange"
|
||||
>
|
||||
<view class="picker-view">
|
||||
<text :class="['picker-text', !endTimeDisplay ? 'placeholder' : '']">
|
||||
{{ endTimeDisplay || '请选择结束时间' }}
|
||||
</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-section">
|
||||
<button
|
||||
class="submit-btn"
|
||||
:class="{ disabled: loading }"
|
||||
:disabled="loading"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{ loading ? '发布中...' : '发布' }}
|
||||
</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createLaborUnionMessage } from '@/api/profile.js'
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NavHeader
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
messageTypeIndex: -1,
|
||||
messageTypeOptions: [
|
||||
{ label: '类型1', value: 1 },
|
||||
{ label: '类型2', value: 2 },
|
||||
{ label: '类型3', value: 3 }
|
||||
],
|
||||
gradeIndex: -1,
|
||||
gradeOptions: [
|
||||
{ label: '普通', value: 0 },
|
||||
{ label: '重要', value: 1 }
|
||||
],
|
||||
formData: {
|
||||
title: '',
|
||||
summary: '',
|
||||
content: '',
|
||||
coverUrl: '',
|
||||
jumpUrl: '',
|
||||
messageType: null,
|
||||
grade: null,
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 开始时间显示(从 ISO 格式提取日期部分)
|
||||
startTimeDisplay() {
|
||||
if (!this.formData.startTime) return ''
|
||||
return this.formData.startTime.split('T')[0]
|
||||
},
|
||||
// 结束时间显示(从 ISO 格式提取日期部分)
|
||||
endTimeDisplay() {
|
||||
if (!this.formData.endTime) return ''
|
||||
return this.formData.endTime.split('T')[0]
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
},
|
||||
methods: {
|
||||
// 选择封面图片
|
||||
chooseCoverImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const tempFilePath = res.tempFilePaths[0]
|
||||
// 先显示本地预览
|
||||
this.formData.coverUrl = tempFilePath
|
||||
// 上传图片到服务器
|
||||
this.uploadImage(tempFilePath)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择图片失败:', err)
|
||||
uni.showToast({
|
||||
title: '选择图片失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 上传图片
|
||||
uploadImage(filePath) {
|
||||
uni.showLoading({
|
||||
title: '上传中...',
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 获取token
|
||||
const token = uni.getStorageSync('token')
|
||||
const BASE_URL = 'https://siji.chenjuncn.top'
|
||||
|
||||
uni.uploadFile({
|
||||
url: `${BASE_URL}/app-api/infra/file/upload`,
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'tenant-id': '1'
|
||||
},
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
try {
|
||||
const data = JSON.parse(res.data)
|
||||
if (data.code === 200 || data.code === 0) {
|
||||
// 上传成功,保存图片URL
|
||||
const imageUrl = data.data?.url || data.data || data.url
|
||||
if (imageUrl) {
|
||||
this.formData.coverUrl = imageUrl
|
||||
uni.showToast({
|
||||
title: '上传成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
} else {
|
||||
throw new Error('上传成功但未返回图片地址')
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.message || data.msg || '上传失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析上传结果失败:', error)
|
||||
uni.showToast({
|
||||
title: '上传失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
// 上传失败,清除预览
|
||||
this.formData.coverUrl = ''
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading()
|
||||
console.error('上传图片失败:', err)
|
||||
uni.showToast({
|
||||
title: '上传失败,请检查网络',
|
||||
icon: 'none'
|
||||
})
|
||||
// 上传失败,清除预览
|
||||
this.formData.coverUrl = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 消息类型选择
|
||||
handleMessageTypeChange(e) {
|
||||
this.messageTypeIndex = e.detail.value
|
||||
this.formData.messageType = this.messageTypeOptions[e.detail.value].value
|
||||
},
|
||||
|
||||
// 等级选择
|
||||
handleGradeChange(e) {
|
||||
this.gradeIndex = e.detail.value
|
||||
this.formData.grade = this.gradeOptions[e.detail.value].value
|
||||
},
|
||||
|
||||
// 开始时间选择
|
||||
handleStartTimeChange(e) {
|
||||
// 将日期转换为 ISO 格式 (date-time)
|
||||
const date = e.detail.value
|
||||
this.formData.startTime = date ? `${date}T00:00:00` : ''
|
||||
},
|
||||
|
||||
// 结束时间选择
|
||||
handleEndTimeChange(e) {
|
||||
// 将日期转换为 ISO 格式 (date-time)
|
||||
const date = e.detail.value
|
||||
this.formData.endTime = date ? `${date}T23:59:59` : ''
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
async handleSubmit() {
|
||||
try {
|
||||
this.loading = true
|
||||
|
||||
// 构建提交数据
|
||||
const submitData = {
|
||||
title: this.formData.title ? this.formData.title.trim() : '',
|
||||
summary: this.formData.summary ? this.formData.summary.trim() : '',
|
||||
content: this.formData.content ? this.formData.content.trim() : '',
|
||||
coverUrl: this.formData.coverUrl || '',
|
||||
jumpUrl: this.formData.jumpUrl ? this.formData.jumpUrl.trim() : '',
|
||||
messageType: this.formData.messageType,
|
||||
sourceType: 1, // 会员发布
|
||||
grade: this.formData.grade,
|
||||
startTime: this.formData.startTime || null,
|
||||
endTime: this.formData.endTime || null
|
||||
}
|
||||
|
||||
// 移除空值
|
||||
Object.keys(submitData).forEach(key => {
|
||||
if (submitData[key] === '' || submitData[key] === null || submitData[key] === undefined) {
|
||||
delete submitData[key]
|
||||
}
|
||||
})
|
||||
|
||||
// 调用接口
|
||||
const res = await createLaborUnionMessage(submitData)
|
||||
|
||||
uni.showToast({
|
||||
title: '发布成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 延迟返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} catch (error) {
|
||||
console.error('发布消息失败:', error)
|
||||
uni.showToast({
|
||||
title: error.message || '发布失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.post-message-page {
|
||||
min-height: 100vh;
|
||||
background: #e2e8f1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 0 30rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-top: 30rpx;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.input-label {
|
||||
width: 160rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
|
||||
.optional {
|
||||
color: #999999;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #1a1819;
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #1a1819;
|
||||
|
||||
&.placeholder {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
font-size: 40rpx;
|
||||
color: #cccccc;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-wrapper {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.textarea-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.optional {
|
||||
color: #999999;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-field {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1a1819;
|
||||
line-height: 1.6;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-wrapper {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.upload-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.optional {
|
||||
color: #999999;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-box {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
border: 2rpx dashed #d0d0d0;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fafafa;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.uploaded-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
|
||||
.upload-icon {
|
||||
font-size: 60rpx;
|
||||
color: #999999;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
margin-top: 40rpx;
|
||||
padding-bottom: 40rpx;
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #004294 0%, #0066cc 100%);
|
||||
border-radius: 44rpx;
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #cccccc;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<view class="map-detail-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="店铺位置" />
|
||||
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<!-- 地图区域 -->
|
||||
<view class="map-container" v-if="mapInitialized">
|
||||
<map
|
||||
|
|
@ -86,6 +87,7 @@
|
|||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -99,6 +101,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
shopId: null,
|
||||
storeInfo: {},
|
||||
loading: false,
|
||||
|
|
@ -118,6 +121,9 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
// // 计算距离文本
|
||||
// distanceText() {
|
||||
// if (
|
||||
|
|
@ -162,6 +168,8 @@ export default {
|
|||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
this.shopId = options.id;
|
||||
if (this.shopId) {
|
||||
// 从存储中读取用户位置信息
|
||||
|
|
@ -421,6 +429,22 @@ export default {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 地图容器 */
|
||||
.map-container {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<view class="rich-text-detail-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="rich-text-detail-page" :class="{ 'no-bottom-bar': hideBottomBar }">
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="详情" />
|
||||
|
||||
<!-- 内容区域 -->
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<scroll-view class="content-scroll" scroll-y="true">
|
||||
<!-- 加载中 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
|
|
@ -45,9 +45,9 @@
|
|||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<!-- 底部操作栏(从收藏进入时隐藏) -->
|
||||
<DetailActionBar
|
||||
v-if="detailData"
|
||||
v-if="detailData && !hideBottomBar"
|
||||
:liked="isLiked"
|
||||
:collected="isCollected"
|
||||
:id="detailId"
|
||||
|
|
@ -55,10 +55,12 @@
|
|||
:type="noticeType"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getGuildDetail } from "@/api/home.js";
|
||||
import { getBusinessData } from "@/api/profile.js";
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
import DetailActionBar from "@/components/DetailActionBar/DetailActionBar.vue";
|
||||
|
||||
|
|
@ -69,6 +71,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
detailId: null, // 详情ID(用于通过接口获取数据)
|
||||
title: "",
|
||||
content: "",
|
||||
|
|
@ -78,13 +81,28 @@ export default {
|
|||
isCollected: false, // 是否已收藏
|
||||
detailData: null, // 详情数据(通过接口获取时使用)
|
||||
noticeType: null, // 内容类型
|
||||
hideBottomBar: false, // 为 true 时不显示底部操作栏(如从收藏进入)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
// 支持两种模式:
|
||||
// 1. 通过 id 调用接口获取详情(原 activitiesDetail 的用法)
|
||||
// 2. 直接传入 title 和 content(原 richTextDetail 的用法)
|
||||
if (options.id) {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
// 支持三种模式:
|
||||
// 1. 从收藏进入:collectId + collectType,用 getBusinessData 拉取详情
|
||||
// 2. 通过 id 调用接口获取详情(原 activitiesDetail 的用法)
|
||||
// 3. 直接传入 title 和 content(原 richTextDetail 的用法)
|
||||
const collectId = options.collectId != null ? decodeURIComponent(String(options.collectId)) : null;
|
||||
const collectType = options.collectType != null ? decodeURIComponent(String(options.collectType)) : null;
|
||||
if (collectId != null && collectId !== "") {
|
||||
this.detailId = collectId;
|
||||
this.hideBottomBar = !!(options.hideBar || options.hideBar === "1");
|
||||
this.loadDetailByBusinessData(collectId, collectType || "");
|
||||
} else if (options.id) {
|
||||
this.detailId = options.id;
|
||||
this.loadDetailById();
|
||||
} else if (options.content) {
|
||||
|
|
@ -148,6 +166,35 @@ export default {
|
|||
},
|
||||
// #endif
|
||||
methods: {
|
||||
// 判断 VO 是否有数据(有数据即已点赞/已收藏)
|
||||
hasRespData(vo) {
|
||||
if (vo == null) return false;
|
||||
if (Array.isArray(vo)) return vo.length > 0;
|
||||
if (typeof vo === "object") return Object.keys(vo).length > 0;
|
||||
return !!vo;
|
||||
},
|
||||
// 通过收藏进入:getBusinessData(id: collectId, type: collectType) 拉取详情
|
||||
async loadDetailByBusinessData(collectId, collectType) {
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await getBusinessData({ id: collectId, type: collectType });
|
||||
if (res) {
|
||||
this.detailData = res;
|
||||
this.noticeType = res.noticeType ?? res.messageType ?? res.type ?? collectType;
|
||||
this.title = res.title || "详情";
|
||||
if (res.content) {
|
||||
this.parsedContent = this.parseHtmlContent(res.content);
|
||||
}
|
||||
this.isLiked = this.hasRespData(res.zanRespVO);
|
||||
this.isCollected = this.hasRespData(res.collectRespVO);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载详情失败:", error);
|
||||
uni.showToast({ title: "加载失败,请重试", icon: "none" });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
// 通过 ID 加载详情(原 activitiesDetail 的功能)
|
||||
async loadDetailById() {
|
||||
if (!this.detailId) {
|
||||
|
|
@ -165,13 +212,10 @@ export default {
|
|||
if (res.content) {
|
||||
this.parsedContent = this.parseHtmlContent(res.content);
|
||||
}
|
||||
// 设置点赞和收藏状态(如果接口返回了这些字段)
|
||||
if (res.isLiked !== undefined) {
|
||||
this.isLiked = res.isLiked;
|
||||
}
|
||||
if (res.isCollected !== undefined) {
|
||||
this.isCollected = res.isCollected;
|
||||
}
|
||||
// 点赞状态:zanRespVO 有数据表示已点赞
|
||||
this.isLiked = this.hasRespData(res.zanRespVO);
|
||||
// 收藏状态:collectRespVO 有数据表示已收藏
|
||||
this.isCollected = this.hasRespData(res.collectRespVO);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载详情失败:", error);
|
||||
|
|
@ -254,9 +298,28 @@ export default {
|
|||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding-bottom: 120rpx; // 为底部操作栏留出空间
|
||||
|
||||
&.no-bottom-bar {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #e2e8f1;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
height: 0; // 配合 flex: 1 使用,让 scroll-view 可以滚动
|
||||
|
|
|
|||
|
|
@ -0,0 +1,253 @@
|
|||
<template>
|
||||
<view class="select-coupon-page">
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="选择优惠卷" />
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<scroll-view class="coupon-list" scroll-y="true">
|
||||
<view
|
||||
class="coupon-item"
|
||||
v-for="(item, index) in coupons"
|
||||
:key="index"
|
||||
:class="{ disabled: !item.isApplicable }"
|
||||
@click="selectCoupon(item)"
|
||||
>
|
||||
<view class="coupon-left">
|
||||
<!-- 折扣类 -->
|
||||
<view class="coupon-price" v-if="item.type === 2">
|
||||
<text class="amount">{{ (item.discountPercent / 10).toFixed(1).replace(/\.0$/, '') }}</text>
|
||||
<text class="symbol" style="font-size: 24rpx; margin-left: 4rpx;">折</text>
|
||||
</view>
|
||||
<!-- 金额类 -->
|
||||
<view class="coupon-price" v-else>
|
||||
<text class="symbol">¥</text>
|
||||
<!-- 检查返回的字段是 discountPrice, discountAmount 还是抵扣金额相关字段,如果是金额类,应该有值 -->
|
||||
<text class="amount">{{ formatAmount(item.discountAmount || item.discountPrice || item.price || 0) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="coupon-condition" v-if="item.usePrice">
|
||||
满{{ formatAmount(item.usePrice) }}可用
|
||||
</view>
|
||||
<view class="coupon-condition" v-else>
|
||||
无门槛
|
||||
</view>
|
||||
</view>
|
||||
<view class="coupon-right">
|
||||
<view class="coupon-name">{{ item.name }}</view>
|
||||
<view class="coupon-time" v-if="item.type === 2 && item.discountLimit > 0" style="margin-bottom: 6rpx;">最多抵扣: ¥{{ formatAmount(item.discountLimit) }}</view>
|
||||
<view class="coupon-time" v-if="item.validEndTime">有效期至: {{ formatTimeStr(item.validEndTime) }}</view>
|
||||
</view>
|
||||
<view class="coupon-radio">
|
||||
<radio :checked="selectedCouponId === item.id" :disabled="!item.isApplicable" color="#d51c3c" style="transform:scale(0.8)" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="coupons.length === 0" class="empty-tip">暂无优惠卷</view>
|
||||
</scroll-view>
|
||||
<!-- 不使用优惠卷按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<button class="no-coupon-btn" @click="selectNone">不使用优惠卷</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
import { formatTime } from "@/utils/date.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NavHeader,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
coupons: [],
|
||||
selectedCouponId: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
|
||||
// 获取传递过来的数据
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
if (eventChannel && eventChannel.on) {
|
||||
eventChannel.on('acceptDataFromOpenerPage', (data) => {
|
||||
this.coupons = data.coupons || [];
|
||||
this.selectedCouponId = data.selectedCouponId || null;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectCoupon(item) {
|
||||
if (!item.isApplicable) {
|
||||
return;
|
||||
}
|
||||
this.selectedCouponId = item.id;
|
||||
this.confirmSelection(item);
|
||||
},
|
||||
selectNone() {
|
||||
this.selectedCouponId = null;
|
||||
this.confirmSelection(null);
|
||||
},
|
||||
confirmSelection(coupon) {
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
if (eventChannel && eventChannel.emit) {
|
||||
eventChannel.emit('acceptDataFromOpenedPage', { coupon });
|
||||
}
|
||||
uni.navigateBack();
|
||||
},
|
||||
formatTimeStr(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
return formatTime(timestamp, 'YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
// 将分转换为元,并处理小数显示
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0';
|
||||
const yuan = amount / 100;
|
||||
// 如果是整数,直接显示整数;如果有小数,保留两位小数但去掉末尾的0
|
||||
return Number.isInteger(yuan) ? yuan.toString() : yuan.toFixed(2).replace(/\.?0+$/, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-coupon-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.coupon-list {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.coupon-item {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
align-items: center;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
background-color: #fafafa;
|
||||
|
||||
.coupon-price {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.coupon-name {
|
||||
color: #999 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 160rpx;
|
||||
border-right: 2rpx dashed #eee;
|
||||
padding-right: 20rpx;
|
||||
|
||||
.coupon-price {
|
||||
color: #d51c3c;
|
||||
|
||||
.symbol {
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-condition {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-right {
|
||||
flex: 1;
|
||||
padding-left: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.coupon-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.coupon-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-radio {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
padding: 20rpx 40rpx calc(20rpx + env(safe-area-inset-bottom));
|
||||
background-color: #fff;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
.no-coupon-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
font-size: 32rpx;
|
||||
border-radius: 44rpx;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<view class="store-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="店铺详情" />
|
||||
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<!-- 顶部店铺信息(点击进入地图页) -->
|
||||
<view class="store-header" @click="handleGoMap">
|
||||
<view class="store-brand">
|
||||
|
|
@ -41,10 +42,15 @@
|
|||
|
||||
<!-- 中间菜单列表(可滑动) -->
|
||||
<scroll-view class="menu-list" scroll-y="true">
|
||||
<view class="menu-item" v-for="(item, index) in menuList" :key="index">
|
||||
<view
|
||||
class="menu-item"
|
||||
v-for="(item, index) in menuList"
|
||||
:key="index"
|
||||
:class="{ 'out-of-stock': item.stock === 0 }"
|
||||
>
|
||||
<view
|
||||
class="checkbox"
|
||||
:class="{ checked: item.selected }"
|
||||
:class="{ checked: item.selected, disabled: item.stock === 0 }"
|
||||
@click="toggleMenuItem(index)"
|
||||
>
|
||||
<text v-if="item.selected" class="checkmark">✓</text>
|
||||
|
|
@ -74,17 +80,21 @@
|
|||
<view
|
||||
class="quantity-btn minus"
|
||||
:class="{
|
||||
disabled: (item.quantity || 0) <= 0,
|
||||
disabled: (item.quantity || 0) <= 0 || item.stock === 0,
|
||||
}"
|
||||
@click="decreaseQuantity(index)"
|
||||
>-</view
|
||||
>
|
||||
<text class="quantity-number">{{ item.quantity || 0 }}</text>
|
||||
<view class="quantity-btn plus" @click="increaseQuantity(index)"
|
||||
<view
|
||||
class="quantity-btn plus"
|
||||
:class="{ disabled: item.stock === 0 }"
|
||||
@click="increaseQuantity(index)"
|
||||
>+</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.stock === 0" class="stock-tip">库存不足,不可选择</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
|
@ -92,8 +102,8 @@
|
|||
<!-- 底部结算栏(固定) -->
|
||||
<view class="checkout-footer">
|
||||
<view class="total-info">
|
||||
<text class="total-label">总金额¥</text>
|
||||
<text class="total-amount">{{ totalAmount.toFixed(2) }}</text>
|
||||
<text class="total-label">应付¥</text>
|
||||
<text class="total-amount">{{ payableAmountYuan.toFixed(2) }}</text>
|
||||
<view class="member-benefit">
|
||||
<image
|
||||
class="crown-icon"
|
||||
|
|
@ -103,6 +113,15 @@
|
|||
<text class="benefit-text">{{ memberLevelName }}优惠</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="coupon-brief" @click="openCouponSelect">
|
||||
<image class="coupon-icon" src="/static/service/coupon-icon.png" mode="aspectFill"></image>
|
||||
<text class="coupon-text">{{ selectedCoupon ? selectedCoupon.name : '选择优惠卷' }}</text>
|
||||
<text class="coupon-deduction" v-if="selectedCoupon && couponDiscountFen > 0">-¥{{ (couponDiscountFen / 100).toFixed(2) }}</text>
|
||||
<text class="coupon-arrow">›</text>
|
||||
</view>
|
||||
<view class="settle-info">
|
||||
<text class="settle-text">资金结算方:{{ storeInfo.name || '商户' }}</text>
|
||||
</view>
|
||||
<button
|
||||
class="checkout-btn"
|
||||
:class="{ disabled: totalAmount <= 0 }"
|
||||
|
|
@ -113,14 +132,16 @@
|
|||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getGuildStoreDetail,
|
||||
getGuildCoupon,
|
||||
getGuildVoucher,
|
||||
getPaySign,
|
||||
appBuy
|
||||
appBuy,
|
||||
getLuCouponPage
|
||||
} from "@/api/service";
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
|
||||
|
|
@ -130,6 +151,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
storeInfo: {},
|
||||
menuList: [],
|
||||
categoryLabel: "",
|
||||
|
|
@ -137,9 +159,16 @@ export default {
|
|||
userInfo: {},
|
||||
distance: null,
|
||||
paying: false,
|
||||
coupons: [],
|
||||
selectedCoupon: null,
|
||||
selectedCouponId: null,
|
||||
couponLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
// 计算总金额(只计算选中且数量大于0的商品)
|
||||
totalAmount() {
|
||||
return this.menuList.reduce((total, item) => {
|
||||
|
|
@ -151,6 +180,36 @@ export default {
|
|||
return total;
|
||||
}, 0);
|
||||
},
|
||||
totalAmountFen() {
|
||||
return Math.round(this.totalAmount * 100);
|
||||
},
|
||||
couponDiscountFen() {
|
||||
if (!this.selectedCoupon) return 0;
|
||||
const now = Date.now();
|
||||
if ((this.selectedCoupon.validStartTime && now < this.selectedCoupon.validStartTime) ||
|
||||
(this.selectedCoupon.validEndTime && now > this.selectedCoupon.validEndTime)) {
|
||||
return 0;
|
||||
}
|
||||
if (!this.isCouponApplicable(this.selectedCoupon)) return 0;
|
||||
const totalFen = this.totalAmountFen;
|
||||
if (this.selectedCoupon.type === 2) {
|
||||
const percent = this.selectedCoupon.discountPercent || 0;
|
||||
const raw = Math.round(totalFen * (1 - percent / 100));
|
||||
const limit = this.selectedCoupon.discountLimit || 0;
|
||||
const val = limit > 0 ? Math.min(raw, limit) : raw;
|
||||
return Math.max(0, val);
|
||||
}
|
||||
const amt = this.selectedCoupon.discountAmount || this.selectedCoupon.discountPrice || this.selectedCoupon.price || 0;
|
||||
return Math.max(0, Math.min(amt, totalFen));
|
||||
},
|
||||
payableAmountFen() {
|
||||
const val = this.totalAmountFen - this.couponDiscountFen;
|
||||
if (val <= 0 && this.totalAmountFen > 0) return 1;
|
||||
return Math.max(0, val);
|
||||
},
|
||||
payableAmountYuan() {
|
||||
return this.payableAmountFen / 100;
|
||||
},
|
||||
// 是否有会员优惠
|
||||
hasMemberDiscount() {
|
||||
return this.menuList.some((item) => item.selected && item.discount);
|
||||
|
|
@ -164,7 +223,8 @@ export default {
|
|||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
console.log(options, 111110);
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
// 从路由参数获取店铺ID和分类标签
|
||||
this.shopId = options.id;
|
||||
this.categoryLabel = options.categoryLabel;
|
||||
|
|
@ -249,10 +309,10 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 加载工会优惠券
|
||||
// 加载工会代金券
|
||||
async loadStoreMenu(shopId) {
|
||||
try {
|
||||
const res = await getGuildCoupon({ shopId });
|
||||
const res = await getGuildVoucher({ shopId });
|
||||
console.log(res, 11119);
|
||||
if (res && res.list) {
|
||||
this.menuList = res.list;
|
||||
|
|
@ -268,6 +328,10 @@ export default {
|
|||
// 切换菜单项选择状态
|
||||
toggleMenuItem(index) {
|
||||
const item = this.menuList[index];
|
||||
// 库存不足时不可选择
|
||||
if (item && item.stock === 0) {
|
||||
return;
|
||||
}
|
||||
item.selected = !item.selected;
|
||||
if (!item.selected) {
|
||||
item.quantity = 0;
|
||||
|
|
@ -275,22 +339,32 @@ export default {
|
|||
// 选中时,如果当前数量为 0 或未设置,则默认数量为 1
|
||||
item.quantity = 1;
|
||||
}
|
||||
this.autoSelectBestCoupon();
|
||||
},
|
||||
|
||||
// 增加数量
|
||||
increaseQuantity(index) {
|
||||
const item = this.menuList[index];
|
||||
// 库存不足时不可增加数量
|
||||
if (item && item.stock === 0) {
|
||||
return;
|
||||
}
|
||||
// 如果未选中,先选中
|
||||
if (!item.selected) {
|
||||
item.selected = true;
|
||||
}
|
||||
// 增加数量
|
||||
item.quantity = (item.quantity || 0) + 1;
|
||||
this.autoSelectBestCoupon();
|
||||
},
|
||||
|
||||
// 减少数量
|
||||
decreaseQuantity(index) {
|
||||
const item = this.menuList[index];
|
||||
// 库存不足时不处理数量变化
|
||||
if (item && item.stock === 0) {
|
||||
return;
|
||||
}
|
||||
const currentQuantity = item.quantity || 0;
|
||||
// 确保数量不能小于0
|
||||
if (currentQuantity > 0) {
|
||||
|
|
@ -300,6 +374,7 @@ export default {
|
|||
item.selected = false;
|
||||
}
|
||||
}
|
||||
this.autoSelectBestCoupon();
|
||||
},
|
||||
|
||||
// 格式化价格:将分转换为元(除以100,保留两位小数)
|
||||
|
|
@ -313,6 +388,94 @@ export default {
|
|||
return yuan.toFixed(2);
|
||||
},
|
||||
|
||||
async openCouponSelect() {
|
||||
if (this.totalAmountFen <= 0) {
|
||||
uni.showToast({ title: "请先选择商品", icon: "none" });
|
||||
return;
|
||||
}
|
||||
await this.loadUserCoupons();
|
||||
const eventChannelData = {
|
||||
coupons: this.coupons,
|
||||
selectedCouponId: this.selectedCouponId,
|
||||
};
|
||||
uni.navigateTo({
|
||||
url: "/pages/detail/selectCoupon",
|
||||
success: (res) => {
|
||||
res.eventChannel.emit("acceptDataFromOpenerPage", eventChannelData);
|
||||
res.eventChannel.on("acceptDataFromOpenedPage", ({ coupon }) => {
|
||||
this.selectedCoupon = coupon;
|
||||
this.selectedCouponId = coupon ? coupon.id : null;
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
async loadUserCoupons() {
|
||||
if (this.couponLoading) return;
|
||||
this.couponLoading = true;
|
||||
try {
|
||||
const res = await getLuCouponPage({ pageNo: 1, pageSize: 100, status: 0 });
|
||||
const list = (res && res.list) || [];
|
||||
const enhanced = list.map((c) => {
|
||||
const copy = { ...c };
|
||||
copy.isApplicable = this.isCouponApplicable(copy);
|
||||
return copy;
|
||||
});
|
||||
this.coupons = enhanced;
|
||||
if (!this.selectedCoupon && enhanced.length > 0) {
|
||||
this.autoSelectBestCoupon();
|
||||
}
|
||||
} finally {
|
||||
this.couponLoading = false;
|
||||
}
|
||||
},
|
||||
isCouponApplicable(coupon) {
|
||||
const now = Date.now();
|
||||
if (coupon.status !== 0) return false;
|
||||
if (coupon.validStartTime && now < coupon.validStartTime) return false;
|
||||
if (coupon.validEndTime && now > coupon.validEndTime) return false;
|
||||
const totalFen = this.totalAmountFen;
|
||||
if (coupon.usePrice && totalFen < coupon.usePrice) return false;
|
||||
const selectedIds = this.menuList.filter(i => i.selected && i.quantity > 0).map(i => i.id);
|
||||
if (selectedIds.length === 0) return false;
|
||||
if (coupon.productScope === 1) return true;
|
||||
if (coupon.productScope === 2) {
|
||||
const ids = Array.isArray(coupon.voucherIds)
|
||||
? coupon.voucherIds
|
||||
: (typeof coupon.voucherIds === "string" ? coupon.voucherIds.split(",").map(s => Number(s.trim())).filter(Boolean) : []);
|
||||
return selectedIds.some(id => ids.includes(id));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
autoSelectBestCoupon() {
|
||||
if (!this.coupons || this.coupons.length === 0) return;
|
||||
const applicable = this.coupons.filter(c => this.isCouponApplicable(c));
|
||||
if (applicable.length === 0) {
|
||||
this.selectedCoupon = null;
|
||||
this.selectedCouponId = null;
|
||||
return;
|
||||
}
|
||||
let best = null;
|
||||
let maxDeduct = -1;
|
||||
applicable.forEach(c => {
|
||||
const deduct = (() => {
|
||||
if (c.type === 2) {
|
||||
const percent = c.discountPercent || 0;
|
||||
const raw = Math.round(this.totalAmountFen * (1 - percent / 100));
|
||||
const limit = c.discountLimit || 0;
|
||||
return Math.max(0, limit > 0 ? Math.min(raw, limit) : raw);
|
||||
}
|
||||
return Math.max(0, Math.min(c.discountAmount || c.discountPrice || c.price || 0, this.totalAmountFen));
|
||||
})();
|
||||
if (deduct > maxDeduct) {
|
||||
maxDeduct = deduct;
|
||||
best = c;
|
||||
}
|
||||
});
|
||||
this.selectedCoupon = best || null;
|
||||
this.selectedCouponId = best ? best.id : null;
|
||||
this.coupons = this.coupons.map(c => ({ ...c, isApplicable: this.isCouponApplicable(c) }));
|
||||
},
|
||||
|
||||
// 去结算
|
||||
async handleCheckout() {
|
||||
if (this.totalAmount <= 0) {
|
||||
|
|
@ -332,20 +495,21 @@ export default {
|
|||
return;
|
||||
}
|
||||
//
|
||||
// 把选中的数据拼接成这样的数据 couponId:num;couponId:num 例如 2324:1;2325:2
|
||||
const couponStr = selectedItems
|
||||
// 把选中的数据拼接成这样的数据 voucherId:num;voucherId:num 例如 2324:1;2325:2
|
||||
const voucherStr = selectedItems
|
||||
.map((item) => `${item.id}:${item.quantity}`)
|
||||
.join(";");
|
||||
|
||||
// this.totalAmount 是元, 要转换成分
|
||||
const trxamt = (this.totalAmount * 100).toFixed(0);
|
||||
const trxamtFen = this.payableAmountFen;
|
||||
const trxamt = String(trxamtFen);
|
||||
|
||||
console.log("购买信息: " + couponStr);
|
||||
console.log("购买信息: " + voucherStr);
|
||||
console.log("金额:" + trxamt);
|
||||
const res = await appBuy({
|
||||
shopId: this.shopId,
|
||||
couponData: couponStr,
|
||||
voucherData: voucherStr,
|
||||
payableAmount: trxamt,
|
||||
couponId: this.selectedCouponId || undefined,
|
||||
});
|
||||
console.log(res);
|
||||
if (!res) {
|
||||
|
|
@ -355,7 +519,15 @@ export default {
|
|||
});
|
||||
}
|
||||
// 调用支付信息 res 为订单信息
|
||||
uni.showModal({
|
||||
title: "确认结算",
|
||||
content: `将跳转至商户收款小程序完成结算。\n资金直接结算至:${this.storeInfo.name || '商户'}(商户号:${res.tlPayCusid || '已配置'})。是否继续?`,
|
||||
success: (mres) => {
|
||||
if (mres.confirm) {
|
||||
this.handlePay(res);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 支付调用
|
||||
async handlePay(order) {
|
||||
|
|
@ -363,38 +535,43 @@ export default {
|
|||
// const reqsn =
|
||||
// "mini" + Date.now() + "" + Math.floor(Math.random() * 10000);
|
||||
|
||||
const couponArray = order.couponPurchaseRespVOS || [];
|
||||
const couponMap = new Map();
|
||||
const voucherArray = order.voucherPurchaseRespVOS || [];
|
||||
const voucherMap = new Map();
|
||||
|
||||
couponArray.forEach((item) => {
|
||||
const key = item.couponId;
|
||||
voucherArray.forEach((item) => {
|
||||
const key = item.voucherId;
|
||||
if (!key) return;
|
||||
|
||||
// 从 Map 中获取已有数组,没有则取空数组
|
||||
const currentList = couponMap.get(key) || [];
|
||||
const currentList = voucherMap.get(key) || [];
|
||||
currentList.push(item);
|
||||
// 更新 Map 中的值
|
||||
couponMap.set(key, currentList);
|
||||
voucherMap.set(key, currentList);
|
||||
});
|
||||
let bodyStr = ''
|
||||
couponMap.forEach((couponList, couponId) => {
|
||||
console.log(`优惠券ID:${couponId}`);
|
||||
console.log(`优惠券列表:`, couponList);
|
||||
const name = couponList[0] && couponList[0].couponName ? couponList[0].couponName : "商品";
|
||||
bodyStr = bodyStr + name + 'x' + couponList.length + ';';
|
||||
voucherMap.forEach((voucherList, voucherId) => {
|
||||
console.log(`代金券ID:${voucherId}`);
|
||||
console.log(`代金券列表:`, voucherList);
|
||||
const name = voucherList[0] && voucherList[0].voucherName ? voucherList[0].voucherName : "商品";
|
||||
bodyStr = bodyStr + name + 'x' + voucherList.length + ';';
|
||||
});
|
||||
|
||||
const randomstr = Math.floor(Math.random() * 10000000) + "";
|
||||
// 金额:优先用订单实付金额(分),无则用 "1" 测试
|
||||
const trxamt =
|
||||
order.payableAmount != null && order.payableAmount !== ""
|
||||
? String(order.payableAmount)
|
||||
: "1";
|
||||
// 定义请求参数(与服务记录页「立即支付」保持一致)
|
||||
if (order.payableAmount == null || order.payableAmount === ""){
|
||||
uni.showToast({ title: "结算金额异常", icon: "none" });
|
||||
return;
|
||||
}
|
||||
if (order.tlPayCusid == null || order.tlPayCusid == ""){
|
||||
uni.showToast({ title: "当前商户还未配置结算信息不可购买", icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 定义请求参数(与服务记录页「去结算」保持一致)
|
||||
let params = {
|
||||
appid: "00390105", // 平台分配的appid
|
||||
appid: "00390105", // 通联分配的appid
|
||||
body: bodyStr, //
|
||||
cusid: order.tlPayCusid, // 平台分配的商户号
|
||||
cusid: order.tlPayCusid, // 通联分配的商户号
|
||||
notify_url:
|
||||
"https://guangsh.manage.hschengtai.com/admin-api/member/lu-order/tlNotice", // 保持原值不变
|
||||
orgid: "56479107392N35H",
|
||||
|
|
@ -405,7 +582,7 @@ export default {
|
|||
reqsn: order.orderNumber,
|
||||
sign: "",
|
||||
signtype: "RSA",
|
||||
trxamt: "1",
|
||||
trxamt: String(order.payableAmount),
|
||||
version: "12",
|
||||
};
|
||||
console.log(params)
|
||||
|
|
@ -428,8 +605,8 @@ export default {
|
|||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("获取支付签名失败:", e);
|
||||
uni.showToast({ title: "支付准备失败,请稍后重试", icon: "none" });
|
||||
console.error("获取签名失败:", e);
|
||||
uni.showToast({ title: "结算准备失败,请稍后重试", icon: "none" });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -445,6 +622,22 @@ export default {
|
|||
padding-bottom: 120rpx; // 为底部结算栏留出空间
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #e2e8f1;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 顶部店铺信息 */
|
||||
.store-header {
|
||||
background-color: #ffffff;
|
||||
|
|
@ -555,6 +748,10 @@ export default {
|
|||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&.out-of-stock {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
|
|
@ -566,6 +763,11 @@ export default {
|
|||
margin-right: 19rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.disabled {
|
||||
border-color: #dddddd;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background-color: #004294;
|
||||
border-color: #004294;
|
||||
|
|
@ -670,6 +872,12 @@ export default {
|
|||
font-weight: 500;
|
||||
background-color: #ffffff;
|
||||
border-radius: 4rpx;
|
||||
|
||||
&.disabled {
|
||||
color: #bbbbbb;
|
||||
border-color: #dddddd;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.quantity-number {
|
||||
|
|
@ -682,6 +890,11 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
.stock-tip {
|
||||
margin-top: 10rpx;
|
||||
font-size: 20rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -703,6 +916,8 @@ export default {
|
|||
.total-info {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
position: relative;
|
||||
top: -15rpx;
|
||||
|
||||
.total-label {
|
||||
font-family: PingFang-SC, PingFang-SC;
|
||||
|
|
@ -726,6 +941,7 @@ export default {
|
|||
background: rgba(255, 107, 0, 0.1);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-top: -4rpx;
|
||||
|
||||
.crown-icon {
|
||||
width: 23rpx;
|
||||
|
|
@ -741,6 +957,50 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.coupon-brief {
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
top: -68rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 12rpx;
|
||||
background: #fff7f8;
|
||||
border: 1rpx dashed #ffd4d8;
|
||||
border-radius: 10rpx;
|
||||
color: #d51c3c;
|
||||
}
|
||||
.settle-info {
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
bottom: 10rpx;
|
||||
}
|
||||
.settle-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
.coupon-icon {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
.coupon-text {
|
||||
font-size: 24rpx;
|
||||
margin-right: 8rpx;
|
||||
max-width: 280rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.coupon-deduction {
|
||||
font-size: 24rpx;
|
||||
color: #d51c3c;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
.coupon-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #d51c3c;
|
||||
}
|
||||
|
||||
.checkout-btn {
|
||||
color: #ffffff;
|
||||
font-family: PingFang-SC, PingFang-SC;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
<template>
|
||||
<view class="home-page">
|
||||
<!-- 应用头部 -->
|
||||
<view class="header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<!-- 应用头部(固定,滑动时不动) -->
|
||||
<view
|
||||
class="header header-fixed"
|
||||
:style="{ paddingTop: statusBarHeight + 'px' }"
|
||||
>
|
||||
<image class="logo" src="/static/home/logo.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 主体内容(预留头部高度,避免被遮挡) -->
|
||||
<view class="main-content" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<!-- 招募宣传横幅 -->
|
||||
<swiper
|
||||
class="recruit-banner"
|
||||
|
|
@ -39,8 +44,7 @@
|
|||
></image>
|
||||
<text class="title-text">公会福利</text>
|
||||
</view>
|
||||
<view class="section-more" @click="handleViewAllBenefits">
|
||||
查看全部 >
|
||||
<view class="section-more" @click="handleViewAllBenefits" v-html="'查看全部 >'">
|
||||
</view>
|
||||
</view>
|
||||
<view class="benefits-list">
|
||||
|
|
@ -67,8 +71,7 @@
|
|||
></image>
|
||||
<text class="title-text">公会活动</text>
|
||||
</view>
|
||||
<view class="section-more" @click="handleViewAllActivities">
|
||||
查看全部 >
|
||||
<view class="section-more" @click="handleViewAllActivities" v-html="'查看全部 >'">
|
||||
</view>
|
||||
</view>
|
||||
<view class="activities-list">
|
||||
|
|
@ -102,6 +105,7 @@
|
|||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -121,6 +125,8 @@ export default {
|
|||
statusBarHeight: 0,
|
||||
// 导航栏高度(状态栏 + 导航栏内容)
|
||||
navBarHeight: 0,
|
||||
// 头部总高度(用于主体 padding-top,约 72rpx ≈ 36px)
|
||||
headerContentHeight: 44,
|
||||
bannerList: [],
|
||||
// 公会福利列表
|
||||
benefitsList: [
|
||||
|
|
@ -153,6 +159,11 @@ export default {
|
|||
activitiesList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + this.headerContentHeight;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
this.getSystemInfo();
|
||||
this.loadHomeData();
|
||||
|
|
@ -384,6 +395,15 @@ export default {
|
|||
width: 306rpx;
|
||||
height: 72rpx;
|
||||
}
|
||||
|
||||
&.header-fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background-color: #e2e8f1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 招募宣传横幅 */
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
<template>
|
||||
<view class="login-page">
|
||||
<!-- 状态栏占位 -->
|
||||
<!-- <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view> -->
|
||||
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="登录" />
|
||||
|
||||
<!-- 登录内容区 -->
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<view class="login-content">
|
||||
<!-- Logo区域 -->
|
||||
<view class="logo-section">
|
||||
|
|
@ -94,6 +92,7 @@
|
|||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -118,6 +117,9 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44
|
||||
},
|
||||
// 判断是否可以登录
|
||||
canLogin() {
|
||||
return (
|
||||
|
|
@ -142,13 +144,17 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
// 保存页面参数(state 和 inviteCode)
|
||||
// 保存页面参数(state、inviteCode;扫码进入时邀请码在 scene 中)
|
||||
if (options.state) {
|
||||
this.state = options.state
|
||||
}
|
||||
if (options.inviteCode) {
|
||||
this.inviteCode = options.inviteCode
|
||||
}
|
||||
if (options.scene && /^\d+-\d+$/.test(String(options.scene))) {
|
||||
this.inviteCode = String(options.scene)
|
||||
uni.setStorageSync('inviteCode', this.inviteCode)
|
||||
}
|
||||
|
||||
// 判断是否显示返回按钮(从其他页面跳转过来时显示)
|
||||
const pages = getCurrentPages()
|
||||
|
|
@ -492,6 +498,22 @@ export default {
|
|||
background: linear-gradient(180deg, #e2e8f1 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: linear-gradient(180deg, #e2e8f1 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
<template>
|
||||
<view class="profile-page">
|
||||
<view class="header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<!-- 头部固定,滑动时不动 -->
|
||||
<view
|
||||
class="header header-fixed"
|
||||
:style="{ paddingTop: statusBarHeight + 'px' }"
|
||||
>
|
||||
<text class="header-text">个人中心</text>
|
||||
</view>
|
||||
|
||||
<!-- 主体内容(预留头部高度) -->
|
||||
<view class="profile-main" :style="{ paddingTop: navBarHeight + 'px' }">
|
||||
<!-- 用户资料卡片 -->
|
||||
<view
|
||||
class="user-card"
|
||||
|
|
@ -57,6 +63,9 @@
|
|||
<text class="user-id">NO.{{ userInfo.id }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="invite-btn" @click="goToInvite">
|
||||
<image src="https://resource2.ctshenglong.cn/20260309/yaoqingma_btn_1773020094853.png" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="member-benefits-btn" @click="goToMemberBenefits">
|
||||
<image :src="memberLevelIcon" mode="aspectFill"></image>
|
||||
</view>
|
||||
|
|
@ -101,7 +110,14 @@
|
|||
<text class="menu-text">投诉建议</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" @tap="goToPostMessage">
|
||||
<view class="menu-item" @tap="goToMyCoupons">
|
||||
<view class="menu-icon"
|
||||
><image src="/static/profile/my_icon.png" mode="aspectFill"></image
|
||||
></view>
|
||||
<text class="menu-text">我的优惠卷</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<!-- <view class="menu-item" @tap="goToPostMessage">
|
||||
<view class="menu-icon"
|
||||
><image
|
||||
src="/static/profile/publish-message.png"
|
||||
|
|
@ -110,7 +126,7 @@
|
|||
></view>
|
||||
<text class="menu-text">发布消息</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<!-- 退出登录按钮 -->
|
||||
|
|
@ -118,10 +134,39 @@
|
|||
<button class="logout-btn" @click="handleLogout">退出登录</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 邀请海报弹窗(图5样式) -->
|
||||
<view class="invite-modal-mask" v-if="inviteModalVisible" @click="closeInviteModal"></view>
|
||||
<view class="invite-modal-wrap" v-if="inviteModalVisible" @click.stop>
|
||||
<view class="invite-poster" id="invite-poster">
|
||||
<view class="invite-poster-close" @click="closeInviteModal">×</view>
|
||||
<view class="poster-inner">
|
||||
<view class="poster-tag">邀请好友</view>
|
||||
<view class="poster-qr-wrap" v-if="inviteQRCodeUrl">
|
||||
<view class="poster-qr-outer">
|
||||
<image class="poster-qr-image" :src="inviteQRCodeUrl" mode="aspectFit"></image>
|
||||
</view>
|
||||
<text class="poster-qr-tip">长按识别图中二维码</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="poster-actions">
|
||||
<view class="poster-btn poster-btn-cancel" @click="closeInviteModal">取消</view>
|
||||
<view class="poster-btn poster-btn-save" @click="savePosterImage">保存图片</view>
|
||||
</view>
|
||||
<!-- 用于导出海报的 canvas(隐藏) -->
|
||||
<canvas
|
||||
class="poster-canvas"
|
||||
type="2d"
|
||||
id="poster-canvas"
|
||||
:style="{ width: posterCanvasWidth + 'px', height: posterCanvasHeight + 'px' }"
|
||||
></canvas>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUserInfo } from "@/api/profile.js";
|
||||
import { getUserInfo, getInviteQRCode } from "@/api/profile.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
|
@ -134,6 +179,12 @@ export default {
|
|||
},
|
||||
noticeNum: 3,
|
||||
uploading: false, // 是否正在上传头像
|
||||
inviteModalVisible: false,
|
||||
inviteQRCodeUrl: "",
|
||||
inviteCodeStr: "", // 邀请码,格式:邀请类型-用户id,如 0-123
|
||||
posterCanvasWidth: 680,
|
||||
posterCanvasHeight: 860,
|
||||
posterSaving: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -234,6 +285,185 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
// 邀请好友:弹出邀请二维码。邀请码格式 "邀请类型-用户id",扫码后打开小程序登录注册页,登录接口 /app-api/member/auth/weixin-mini-app-login 使用 inviteCode
|
||||
// 邀请类型:0=会员邀请会员,1=系统用户邀请会员。当前为会员邀请,故用 0-用户id
|
||||
async goToInvite() {
|
||||
const uid = this.userInfo && this.userInfo.id;
|
||||
if (!uid) {
|
||||
uni.showToast({ title: "请先登录", icon: "none" });
|
||||
return;
|
||||
}
|
||||
const inviteCodeStr = `0-${uid}`;
|
||||
this.inviteCodeStr = inviteCodeStr;
|
||||
this.inviteModalVisible = true;
|
||||
this.inviteQRCodeUrl = "";
|
||||
|
||||
try {
|
||||
const url = await getInviteQRCode(inviteCodeStr);
|
||||
if (url) {
|
||||
this.inviteQRCodeUrl = url;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("获取邀请二维码失败:", e);
|
||||
}
|
||||
},
|
||||
closeInviteModal() {
|
||||
this.inviteModalVisible = false;
|
||||
},
|
||||
|
||||
// 保存海报图片到相册(按图5样式绘制 canvas 后导出)
|
||||
async savePosterImage() {
|
||||
if (this.posterSaving || !this.inviteQRCodeUrl) return;
|
||||
this.posterSaving = true;
|
||||
uni.showLoading({ title: "生成中...", mask: true });
|
||||
const dpr = uni.getSystemInfoSync().pixelRatio || 2;
|
||||
const w = this.posterCanvasWidth;
|
||||
const h = this.posterCanvasHeight;
|
||||
const padding = 48;
|
||||
const qrSize = 260;
|
||||
const qrTop = 220;
|
||||
const avatarSize = 68;
|
||||
|
||||
try {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query
|
||||
.select("#poster-canvas")
|
||||
.fields({ node: true, size: true })
|
||||
.exec(async (res) => {
|
||||
if (!res || !res[0] || !res[0].node) {
|
||||
uni.hideLoading();
|
||||
this.posterSaving = false;
|
||||
uni.showToast({ title: "生成失败,请重试", icon: "none" });
|
||||
return;
|
||||
}
|
||||
const canvas = res[0].node;
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = w * dpr;
|
||||
canvas.height = h * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
// 白底圆角
|
||||
ctx.fillStyle = "#ffffff";
|
||||
const r = 16;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding + r, padding);
|
||||
ctx.lineTo(w - padding - r, padding);
|
||||
ctx.quadraticCurveTo(w - padding, padding, w - padding, padding + r);
|
||||
ctx.lineTo(w - padding, h - padding - r);
|
||||
ctx.quadraticCurveTo(w - padding, h - padding, w - padding - r, h - padding);
|
||||
ctx.lineTo(padding + r, h - padding);
|
||||
ctx.quadraticCurveTo(padding, h - padding, padding, h - padding - r);
|
||||
ctx.lineTo(padding, padding + r);
|
||||
ctx.quadraticCurveTo(padding, padding, padding + r, padding);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// 文案
|
||||
ctx.fillStyle = "#333333";
|
||||
ctx.font = "bold 14px PingFang SC, sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
const nickname = (this.userInfo.nickname || "微信用户").slice(0, 20);
|
||||
ctx.font = "bold 16px PingFang SC, sans-serif";
|
||||
ctx.fillText(nickname, w / 2, 96);
|
||||
ctx.font = "13px PingFang SC, sans-serif";
|
||||
ctx.fillStyle = "#666666";
|
||||
ctx.fillText("长按识别图中二维码", w / 2, qrTop + qrSize + 44);
|
||||
|
||||
const drawImage = (src) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = canvas.createImage();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = () => reject(new Error("图片加载失败"));
|
||||
img.src = src;
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const qrImg = await drawImage(this.inviteQRCodeUrl);
|
||||
const qrX = (w - qrSize) / 2;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(w / 2, qrTop + qrSize / 2, qrSize / 2, 0, Math.PI * 2);
|
||||
ctx.closePath();
|
||||
ctx.clip();
|
||||
ctx.drawImage(qrImg, qrX, qrTop, qrSize, qrSize);
|
||||
ctx.restore();
|
||||
|
||||
const avatarSrc = this.avatarSrc;
|
||||
const avatarImg = await drawImage(avatarSrc);
|
||||
const ax = (w - avatarSize) / 2;
|
||||
const ay = qrTop + (qrSize - avatarSize) / 2;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(w / 2, qrTop + qrSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
||||
ctx.closePath();
|
||||
ctx.clip();
|
||||
ctx.drawImage(avatarImg, ax, ay, avatarSize, avatarSize);
|
||||
ctx.restore();
|
||||
} catch (imgErr) {
|
||||
console.warn("海报图片绘制跳过:", imgErr);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select("#poster-canvas")
|
||||
.fields({ node: true, size: true })
|
||||
.exec((r2) => {
|
||||
if (!r2 || !r2[0] || !r2[0].node) {
|
||||
uni.hideLoading();
|
||||
this.posterSaving = false;
|
||||
uni.showToast({ title: "导出失败", icon: "none" });
|
||||
return;
|
||||
}
|
||||
const node = r2[0].node;
|
||||
uni.canvasToTempFilePath({
|
||||
canvas: node,
|
||||
fileType: "png",
|
||||
quality: 1,
|
||||
success: (pathRes) => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: pathRes.tempFilePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
this.posterSaving = false;
|
||||
uni.showToast({ title: "已保存到相册", icon: "success" });
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
this.posterSaving = false;
|
||||
if (err.errMsg && err.errMsg.indexOf("auth") !== -1) {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "需要您授权保存图片到相册",
|
||||
confirmText: "去设置",
|
||||
success: (s) => {
|
||||
if (s.confirm) uni.openSetting();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.showToast({ title: "保存失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
this.posterSaving = false;
|
||||
uni.showToast({ title: "导出失败", icon: "none" });
|
||||
},
|
||||
});
|
||||
});
|
||||
}, 400);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("保存海报失败:", e);
|
||||
uni.hideLoading();
|
||||
this.posterSaving = false;
|
||||
uni.showToast({ title: "生成失败,请重试", icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
// 会员权益
|
||||
goToMemberBenefits() {
|
||||
uni.navigateTo({
|
||||
|
|
@ -260,6 +490,11 @@ export default {
|
|||
url: "/pages/activities/complaints",
|
||||
});
|
||||
},
|
||||
goToMyCoupons() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/profileSub/myCoupons",
|
||||
});
|
||||
},
|
||||
goToPostMessage() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/activities/postMessage",
|
||||
|
|
@ -458,6 +693,15 @@ export default {
|
|||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
|
||||
&.header-fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: radial-gradient(0% 0% at 0% 0%, #ffffff 0%, #e2e8f1 100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* 用户资料卡片 */
|
||||
|
|
@ -565,6 +809,18 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.invite-btn {
|
||||
position: absolute;
|
||||
right: 255rpx;
|
||||
bottom: 24rpx;
|
||||
width: 175rpx;
|
||||
height: 42rpx;
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.member-benefits-btn {
|
||||
position: absolute;
|
||||
right: 50rpx;
|
||||
|
|
@ -702,5 +958,153 @@ export default {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 邀请海报弹窗(图5样式) */
|
||||
.invite-modal-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 900;
|
||||
}
|
||||
.invite-modal-wrap {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 901;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.invite-poster {
|
||||
width: 100%;
|
||||
max-width: 660rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
overflow: visible;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
|
||||
position: relative;
|
||||
}
|
||||
.invite-poster-close {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 20rpx;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 44rpx;
|
||||
color: #999999;
|
||||
z-index: 2;
|
||||
}
|
||||
.poster-inner {
|
||||
padding: 48rpx 48rpx 44rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.poster-tag {
|
||||
position: absolute;
|
||||
top: 28rpx;
|
||||
left: 28rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
font-family: PingFang-SC, PingFang-SC;
|
||||
}
|
||||
.poster-nickname {
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
font-family: PingFang-SC, PingFang-SC;
|
||||
color: #333333;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.poster-qr-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.poster-qr-outer {
|
||||
width: 380rpx;
|
||||
height: 380rpx;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.poster-qr-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.poster-qr-avatar-wrap {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
padding: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.poster-qr-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.poster-qr-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
margin-top: 28rpx;
|
||||
font-family: PingFang-SC, PingFang-SC;
|
||||
}
|
||||
.poster-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 28rpx;
|
||||
margin-top: 32rpx;
|
||||
width: 100%;
|
||||
max-width: 660rpx;
|
||||
}
|
||||
.poster-btn {
|
||||
flex: 1;
|
||||
max-width: 280rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
font-family: PingFang-SC, PingFang-SC;
|
||||
}
|
||||
.poster-btn-cancel {
|
||||
background: #f0f0f0;
|
||||
color: #666666;
|
||||
}
|
||||
.poster-btn-save {
|
||||
background: #004294;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.poster-canvas {
|
||||
position: fixed;
|
||||
left: -9999px;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,404 @@
|
|||
<template>
|
||||
<view class="coupon-detail-page">
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="优惠卷详情" />
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<view v-if="!loading && detail" class="content">
|
||||
<!-- 优惠卷基础信息卡片 -->
|
||||
<view class="coupon-card">
|
||||
<view class="coupon-left">
|
||||
<view class="coupon-price" v-if="detail.type === 2">
|
||||
<text class="amount">{{ (detail.discountPercent / 10).toFixed(1).replace(/\.0$/, '') }}</text>
|
||||
<text class="symbol" style="font-size: 24rpx; margin-left: 4rpx;">折</text>
|
||||
</view>
|
||||
<view class="coupon-price" v-else>
|
||||
<text class="symbol">¥</text>
|
||||
<text class="amount">{{ formatAmount(detail.discountAmount || detail.discountPrice || detail.price || 0) }}</text>
|
||||
</view>
|
||||
<view class="coupon-condition" v-if="detail.usePrice">满{{ formatAmount(detail.usePrice) }}可用</view>
|
||||
<view class="coupon-condition" v-else>无门槛</view>
|
||||
</view>
|
||||
<view class="coupon-right">
|
||||
<view class="coupon-name">{{ detail.name }}</view>
|
||||
<view class="coupon-time" v-if="detail.type === 2 && detail.discountLimit > 0">最多抵扣: ¥{{ formatAmount(detail.discountLimit) }}</view>
|
||||
<view class="coupon-time" v-if="detail.validEndTime">有效期至: {{ formatTimeStr(detail.validEndTime) }}</view>
|
||||
</view>
|
||||
<view class="coupon-status-stamp" v-if="detail.status === 1">已使用</view>
|
||||
<view class="coupon-status-stamp" v-else-if="detail.status === 2">已过期</view>
|
||||
<view class="coupon-status-stamp" v-else-if="detail.status === 3">已作废</view>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="info-section">
|
||||
<view class="info-title">使用说明</view>
|
||||
<view class="info-item">
|
||||
<text class="label">可用范围:</text>
|
||||
<text class="value">{{ detail.productScope === 1 ? '全部代金券通用' : '指定代金券可用' }}</text>
|
||||
</view>
|
||||
<view class="info-item" v-if="detail.validStartTime && detail.validEndTime">
|
||||
<text class="label">有效期限:</text>
|
||||
<text class="value">{{ formatTimeStr(detail.validStartTime) }} 至 {{ formatTimeStr(detail.validEndTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 适用商品列表 -->
|
||||
<view class="goods-section" v-if="detail.productScope === 2 && detail.voucherList && detail.voucherList.length > 0">
|
||||
<view class="section-title">可用代金卷</view>
|
||||
<view class="goods-list">
|
||||
<view class="goods-item" v-for="(item, index) in detail.voucherList" :key="index">
|
||||
<image class="goods-img" :src="item.coverUrl || item.picUrl || '/static/home/entry_icon.png'" mode="aspectFill"></image>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ item.name }}</view>
|
||||
<view class="goods-shop-name" v-if="item.shopName">门店: {{ item.shopName }}</view>
|
||||
<view class="goods-meta-list">
|
||||
<view class="goods-meta-item" v-if="item.stock !== undefined">
|
||||
<text class="meta-label">库存:</text>
|
||||
<text class="meta-value">{{ item.stock }}份</text>
|
||||
</view>
|
||||
<view class="goods-meta-item" v-if="item.useRule">
|
||||
<text class="meta-label">使用规则:</text>
|
||||
<text class="meta-value">{{ item.useRule }}</text>
|
||||
</view>
|
||||
<view class="goods-meta-item" v-if="item.validDays">
|
||||
<text class="meta-label">有效天数:</text>
|
||||
<text class="meta-value">{{ item.validDays }}天</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-price-row">
|
||||
<text class="goods-price">¥{{ formatAmount(item.salePrice || item.sellPrice) }}</text>
|
||||
<text class="goods-original-price" v-if="item.originalPrice">¥{{ formatAmount(item.originalPrice) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading-state">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
import { getLuCouponDetail } from "@/api/service.js";
|
||||
import { formatTime } from "@/utils/date.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NavHeader,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
couponId: null,
|
||||
detail: null,
|
||||
loading: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
|
||||
if (options.id) {
|
||||
this.couponId = options.id;
|
||||
this.loadDetail();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadDetail() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await getLuCouponDetail(this.couponId);
|
||||
if (res) {
|
||||
this.detail = res;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '获取详情失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载优惠卷详情失败:", error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
formatTimeStr(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
return formatTime(timestamp, 'YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0';
|
||||
const yuan = amount / 100;
|
||||
return Number.isInteger(yuan) ? yuan.toString() : yuan.toFixed(2).replace(/\.?0+$/, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.coupon-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 100rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.coupon-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.coupon-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 180rpx;
|
||||
border-right: 2rpx dashed #eee;
|
||||
padding-right: 20rpx;
|
||||
|
||||
.coupon-price {
|
||||
color: #d51c3c;
|
||||
|
||||
.symbol {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 56rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-condition {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-right {
|
||||
flex: 1;
|
||||
padding-left: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.coupon-name {
|
||||
font-size: 34rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.coupon-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-status-stamp {
|
||||
position: absolute;
|
||||
right: -20rpx;
|
||||
top: 20rpx;
|
||||
width: 120rpx;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
background-color: rgba(153, 153, 153, 0.1);
|
||||
color: #999;
|
||||
font-size: 20rpx;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center;
|
||||
border: 1rpx solid #999;
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.info-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
width: 140rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.goods-list {
|
||||
.goods-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-right: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
.goods-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.goods-shop-name {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.goods-meta-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.goods-meta-item {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 4rpx;
|
||||
|
||||
.meta-label {
|
||||
color: #999;
|
||||
}
|
||||
.meta-value {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-price-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 32rpx;
|
||||
color: #d51c3c;
|
||||
font-weight: bold;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.goods-original-price {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
<template>
|
||||
<view class="my-coupons-page">
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="我的优惠卷" />
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<!-- Tab 切换 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === '0' }"
|
||||
@click="switchTab('0')"
|
||||
>
|
||||
<text class="tab-text">待使用</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === '1' }"
|
||||
@click="switchTab('1')"
|
||||
>
|
||||
<text class="tab-text">已使用</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === '2' }"
|
||||
@click="switchTab('2')"
|
||||
>
|
||||
<text class="tab-text">已过期</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'all' }"
|
||||
@click="switchTab('all')"
|
||||
>
|
||||
<text class="tab-text">全部</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<scroll-view
|
||||
class="coupon-list"
|
||||
scroll-y="true"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="handleRefresh"
|
||||
@scrolltolower="handleLoadMore"
|
||||
:lower-threshold="100"
|
||||
>
|
||||
<!-- 空数据提示 -->
|
||||
<view class="empty-state" v-if="!loading && coupons.length === 0">
|
||||
<image
|
||||
class="empty-icon"
|
||||
src="/static/home/entry_icon.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="empty-text">暂无优惠卷</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="coupon-item"
|
||||
v-for="(item, index) in coupons"
|
||||
:key="index"
|
||||
:class="{ disabled: isCouponDisabled(item) }"
|
||||
@click="goToDetail(item)"
|
||||
>
|
||||
<view class="coupon-left">
|
||||
<!-- 折扣类 -->
|
||||
<view class="coupon-price" v-if="item.type === 2">
|
||||
<text class="amount">{{ (item.discountPercent / 10).toFixed(1).replace(/\.0$/, '') }}</text>
|
||||
<text class="symbol" style="font-size: 24rpx; margin-left: 4rpx;">折</text>
|
||||
</view>
|
||||
<!-- 金额类 -->
|
||||
<view class="coupon-price" v-else>
|
||||
<text class="symbol">¥</text>
|
||||
<text class="amount">{{ formatAmount(item.discountAmount || item.discountPrice || item.price || 0) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="coupon-condition" v-if="item.usePrice">
|
||||
满{{ formatAmount(item.usePrice) }}可用
|
||||
</view>
|
||||
<view class="coupon-condition" v-else>
|
||||
无门槛
|
||||
</view>
|
||||
</view>
|
||||
<view class="coupon-right">
|
||||
<view class="coupon-name">{{ item.name }}</view>
|
||||
<view class="coupon-time" v-if="item.type === 2 && item.discountLimit > 0" style="margin-bottom: 6rpx;">最多抵扣: ¥{{ formatAmount(item.discountLimit) }}</view>
|
||||
<view class="coupon-time" v-if="item.validEndTime">有效期至: {{ formatTimeStr(item.validEndTime) }}</view>
|
||||
</view>
|
||||
<view class="coupon-status-stamp" v-if="item.status === 1">已使用</view>
|
||||
<view class="coupon-status-stamp" v-else-if="item.status === 2">已过期</view>
|
||||
<view class="coupon-status-stamp" v-else-if="item.status === 3">已作废</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
import { getLuCouponPage } from "@/api/service.js";
|
||||
import { formatTime } from "@/utils/date.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NavHeader,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
currentTab: '0', // 默认待使用
|
||||
coupons: [],
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
hasMore: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
switchTab(tab) {
|
||||
if (this.currentTab === tab) return;
|
||||
this.currentTab = tab;
|
||||
this.refreshData();
|
||||
},
|
||||
async loadData() {
|
||||
if (this.loading || !this.hasMore) return;
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const params = {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize
|
||||
};
|
||||
|
||||
if (this.currentTab !== 'all') {
|
||||
params.status = Number(this.currentTab);
|
||||
}
|
||||
|
||||
const res = await getLuCouponPage(params);
|
||||
if (res && res.list) {
|
||||
if (this.pageNo === 1) {
|
||||
this.coupons = res.list;
|
||||
} else {
|
||||
this.coupons = this.coupons.concat(res.list);
|
||||
}
|
||||
this.total = res.total || 0;
|
||||
this.hasMore = this.coupons.length < this.total;
|
||||
this.pageNo++;
|
||||
} else {
|
||||
this.hasMore = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载优惠卷失败:", error);
|
||||
uni.showToast({
|
||||
title: "加载失败",
|
||||
icon: "none"
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.refreshing = false;
|
||||
}
|
||||
},
|
||||
refreshData() {
|
||||
this.pageNo = 1;
|
||||
this.hasMore = true;
|
||||
this.coupons = [];
|
||||
this.loadData();
|
||||
},
|
||||
handleRefresh() {
|
||||
if (this.refreshing) return;
|
||||
this.refreshing = true;
|
||||
this.pageNo = 1;
|
||||
this.hasMore = true;
|
||||
this.loadData();
|
||||
},
|
||||
handleLoadMore() {
|
||||
this.loadData();
|
||||
},
|
||||
goToDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/profileSub/couponDetail?id=${item.id}`
|
||||
});
|
||||
},
|
||||
isCouponDisabled(item) {
|
||||
return item.status === 1 || item.status === 2 || item.status === 3;
|
||||
},
|
||||
formatTimeStr(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
return formatTime(timestamp, 'YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
// 将分转换为元,并处理小数显示
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0';
|
||||
const yuan = amount / 100;
|
||||
return Number.isInteger(yuan) ? yuan.toString() : yuan.toFixed(2).replace(/\.?0+$/, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my-coupons-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
height: 88rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.tab-text {
|
||||
color: #d51c3c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 40rpx;
|
||||
height: 6rpx;
|
||||
background-color: #d51c3c;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-list {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 200rpx;
|
||||
|
||||
.empty-icon {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 20rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
background-color: #fafafa;
|
||||
|
||||
.coupon-price {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.coupon-name {
|
||||
color: #999 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 160rpx;
|
||||
border-right: 2rpx dashed #eee;
|
||||
padding-right: 20rpx;
|
||||
|
||||
.coupon-price {
|
||||
color: #d51c3c;
|
||||
|
||||
.symbol {
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-condition {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-right {
|
||||
flex: 1;
|
||||
padding-left: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.coupon-name {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.coupon-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-status-stamp {
|
||||
position: absolute;
|
||||
right: -20rpx;
|
||||
top: 20rpx;
|
||||
width: 120rpx;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
background-color: rgba(153, 153, 153, 0.1);
|
||||
color: #999;
|
||||
font-size: 20rpx;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center;
|
||||
border: 1rpx solid #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
<template>
|
||||
<view class="rules-page">
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="积分会员制度" />
|
||||
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<scroll-view class="content-scroll" scroll-y="true">
|
||||
<!-- 一、会员等级与积分门槛 -->
|
||||
<view class="section">
|
||||
|
|
@ -68,6 +70,7 @@
|
|||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -77,6 +80,7 @@ export default {
|
|||
components: { NavHeader },
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
levelRows: [
|
||||
{ level: "初级", points: "0", name: "筑巢会员" },
|
||||
{ level: "中级", points: "1000", name: "安居会员" },
|
||||
|
|
@ -112,24 +116,49 @@ export default {
|
|||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rules-page {
|
||||
height: 100vh;
|
||||
background-color: #ffffff;
|
||||
background: radial-gradient(0% 0% at 0% 0%, #ffffff 0%, #e2e8f1 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: radial-gradient(0% 0% at 0% 0%, #ffffff 0%, #e2e8f1 100%);
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 20rpx 24rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
background: radial-gradient(0% 0% at 0% 0%, #ffffff 0%, #e2e8f1 100%);
|
||||
}
|
||||
|
||||
.section {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
<template>
|
||||
<view class="privacy-page">
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="隐私政策" />
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<scroll-view class="content" scroll-y="true">
|
||||
<view class="privacy-content">
|
||||
<text class="privacy-text">{{ privacyText }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -18,6 +22,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
privacyText: `隐私政策
|
||||
|
||||
本隐私政策(以下简称"本政策")由广厦千万间司机公会(以下简称"我们")制定,旨在说明我们通过运营的微信小程序(以下简称"小程序")向您(以下简称"用户")提供餐饮、维修、保险、健康等服务过程中,对用户个人信息的收集、使用、存储、传输、共享、披露及保护等相关事宜。您在使用我们的服务时,即视为您已充分阅读、理解并同意本政策的全部内容,包括我们对本政策的后续更新。如您不同意本政策,请勿使用我们的服务。
|
||||
|
|
@ -30,13 +35,13 @@ export default {
|
|||
为向您提供高质量的服务,我们仅收集为实现服务目的所必需的个人信息,具体包括:
|
||||
1.基本身份信息:包括您的姓名、身份证号、联系电话等,用于完成身份核验、建立服务关系;
|
||||
2.车辆相关信息:包括您的车牌号、车辆行驶证信息等,用于为您精准匹配维修、保险等服务;
|
||||
3.服务相关信息:包括您的餐饮偏好、维修需求、保险配置需求、健康状况(仅收集服务必需的部分)、服务订单记录、费用支付记录、评价反馈等,用于处理订单、优化服务体验;
|
||||
3.服务相关信息:包括您的餐饮偏好、维修需求、保险配置需求、健康状况(仅收集服务必需的部分)、服务订单记录、费用结算记录、评价反馈等,用于处理订单、优化服务体验;
|
||||
4.其他必要信息:为保障服务安全、履行法律法规义务所必需的其他信息。
|
||||
|
||||
(二)收集方式
|
||||
1.您主动提供:您在小程序注册账号、提交服务申请、填写资料、购买服务、发表评价时,主动向我们提供的个人信息;
|
||||
2.服务过程获取:在为您提供服务的过程中,我们通过小程序系统自动记录的相关信息,如服务使用记录、支付记录等;
|
||||
3.第三方协助获取:在取得您明确授权的前提下,我们从合法的第三方机构(如保险机构、支付机构、合作维修商家等)获取的必要信息,用于完成服务对接;
|
||||
2.服务过程获取:在为您提供服务的过程中,我们通过小程序系统自动记录的相关信息,如服务使用记录、结算记录等;
|
||||
3.第三方协助获取:在取得您明确授权的前提下,我们从合法的第三方机构(如保险机构、结算机构、合作维修商家等)获取的必要信息,用于完成服务对接;
|
||||
4.其他合法方式:依据法律法规规定或有权机关要求,合法收集的相关信息。
|
||||
|
||||
三、个人信息的使用目的
|
||||
|
|
@ -68,7 +73,7 @@ export default {
|
|||
(一)信息共享
|
||||
我们不会随意向第三方共享您的个人信息,除非符合以下情形:
|
||||
1.经您明确同意或授权:我们将在您授权的范围内,向您指定的第三方共享必要的个人信息;
|
||||
2.服务提供必需:为向您提供约定的服务,需向合作的第三方机构(如保险机构、维修商家、支付机构等)共享必要的个人信息,且该第三方已签署保密协议,承诺严格保护您的个人信息;
|
||||
2.服务提供必需:为向您提供约定的服务,需向合作的第三方机构(如保险机构、维修商家、结算机构等)共享必要的个人信息,且该第三方已签署保密协议,承诺严格保护您的个人信息;
|
||||
3.法律法规要求:依据法律法规的规定、司法机关或行政机关的合法要求,向相关部门披露或共享个人信息;
|
||||
4.保护合法权益:为保护我们的合法权益、服务秩序或社会公共利益,在合理必要的范围内共享个人信息;
|
||||
5.匿名化处理:经过匿名化处理的信息,因其已无法识别您的身份,共享此类信息无需经过您的同意。
|
||||
|
|
@ -102,7 +107,16 @@ export default {
|
|||
|
||||
广厦千万间司机公会`
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -114,6 +128,22 @@ export default {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,25 @@
|
|||
<template>
|
||||
<view class="real-name-auth-page">
|
||||
<!-- 头部区域 -->
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="实名认证" />
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<!-- 加载中 -->
|
||||
<view class="loading-wrap" v-if="realNameLoading">
|
||||
<text class="loading-text">加载实名信息中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容区 -->
|
||||
<scroll-view class="form-content" scroll-y="true">
|
||||
<scroll-view class="form-content" scroll-y="true" v-else>
|
||||
<!-- 基本信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
|
||||
<!-- 实名状态提示(不可编辑时显示) -->
|
||||
<view class="status-tip" v-if="!formEditable && realNameStatus != null">
|
||||
<text class="status-tip-text">当前状态:{{ realNameStatusText }},不可修改</text>
|
||||
</view>
|
||||
|
||||
<!-- 真实姓名 -->
|
||||
<view class="form-item">
|
||||
<view class="input-wrapper">
|
||||
|
|
@ -19,6 +30,7 @@
|
|||
v-model="formData.realName"
|
||||
placeholder="请输入真实姓名"
|
||||
maxlength="20"
|
||||
:disabled="!formEditable"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -28,6 +40,7 @@
|
|||
<view class="input-wrapper">
|
||||
<text class="input-label">证件类型</text>
|
||||
<picker
|
||||
v-if="formEditable"
|
||||
mode="selector"
|
||||
:range="idTypeOptions"
|
||||
range-key="label"
|
||||
|
|
@ -40,6 +53,7 @@
|
|||
</text>
|
||||
</view>
|
||||
</picker>
|
||||
<text v-else class="picker-text readonly">{{ idTypeLabel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
|
@ -53,6 +67,7 @@
|
|||
v-model="formData.idNumber"
|
||||
placeholder="请输入证件号码"
|
||||
maxlength="30"
|
||||
:disabled="!formEditable"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -66,7 +81,7 @@
|
|||
<view class="form-item">
|
||||
<view class="upload-wrapper">
|
||||
<text class="upload-label">身份证正面</text>
|
||||
<view class="upload-box" @click="chooseImage('idFrontImg')">
|
||||
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('idFrontImg')">
|
||||
<image
|
||||
v-if="formData.idFrontImg"
|
||||
class="uploaded-image"
|
||||
|
|
@ -85,7 +100,7 @@
|
|||
<view class="form-item">
|
||||
<view class="upload-wrapper">
|
||||
<text class="upload-label">身份证背面</text>
|
||||
<view class="upload-box" @click="chooseImage('idBackImg')">
|
||||
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('idBackImg')">
|
||||
<image
|
||||
v-if="formData.idBackImg"
|
||||
class="uploaded-image"
|
||||
|
|
@ -109,7 +124,7 @@
|
|||
<view class="form-item">
|
||||
<view class="upload-wrapper">
|
||||
<text class="upload-label">驾驶证正面</text>
|
||||
<view class="upload-box" @click="chooseImage('driverLicenseFrontImg')">
|
||||
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('driverLicenseFrontImg')">
|
||||
<image
|
||||
v-if="formData.driverLicenseFrontImg"
|
||||
class="uploaded-image"
|
||||
|
|
@ -128,7 +143,7 @@
|
|||
<view class="form-item">
|
||||
<view class="upload-wrapper">
|
||||
<text class="upload-label">驾驶证背面</text>
|
||||
<view class="upload-box" @click="chooseImage('driverLicenseBackImg')">
|
||||
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('driverLicenseBackImg')">
|
||||
<image
|
||||
v-if="formData.driverLicenseBackImg"
|
||||
class="uploaded-image"
|
||||
|
|
@ -152,7 +167,7 @@
|
|||
<view class="form-item">
|
||||
<view class="upload-wrapper">
|
||||
<text class="upload-label">行驶证正面</text>
|
||||
<view class="upload-box" @click="chooseImage('vehicleLicenseFrontImg')">
|
||||
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('vehicleLicenseFrontImg')">
|
||||
<image
|
||||
v-if="formData.vehicleLicenseFrontImg"
|
||||
class="uploaded-image"
|
||||
|
|
@ -171,7 +186,7 @@
|
|||
<view class="form-item">
|
||||
<view class="upload-wrapper">
|
||||
<text class="upload-label">行驶证背面</text>
|
||||
<view class="upload-box" @click="chooseImage('vehicleLicenseBackImg')">
|
||||
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('vehicleLicenseBackImg')">
|
||||
<image
|
||||
v-if="formData.vehicleLicenseBackImg"
|
||||
class="uploaded-image"
|
||||
|
|
@ -199,13 +214,14 @@
|
|||
placeholder="请输入备注信息(选填)"
|
||||
maxlength="200"
|
||||
:auto-height="true"
|
||||
:disabled="!formEditable"
|
||||
></textarea>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 协议同意区域 -->
|
||||
<view class="agreement-section">
|
||||
<!-- 协议同意区域(仅可编辑时显示) -->
|
||||
<view class="agreement-section" v-if="formEditable">
|
||||
<view class="agreement-checkbox" @click="toggleAgreement">
|
||||
<view class="checkbox-icon" :class="{ checked: agreedToTerms }">
|
||||
<text v-if="agreedToTerms" class="checkmark">✓</text>
|
||||
|
|
@ -219,8 +235,8 @@
|
|||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-section">
|
||||
<!-- 提交按钮(仅可编辑时显示) -->
|
||||
<view class="submit-section" v-if="formEditable">
|
||||
<button
|
||||
class="submit-btn"
|
||||
:class="{ disabled: loading || !agreedToTerms }"
|
||||
|
|
@ -232,10 +248,11 @@
|
|||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createRealNameInfo } from '@/api/profile.js'
|
||||
import { createRealNameInfo, getRealNameInfo } from '@/api/profile.js'
|
||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||
|
||||
export default {
|
||||
|
|
@ -244,7 +261,10 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
loading: false,
|
||||
realNameLoading: true, // 实名信息加载中
|
||||
realNameStatus: null, // 实名状态:0 未认证/没有 1 待审核 2 已通过 3 已退回 4 已过期
|
||||
agreedToTerms: false, // 是否同意协议
|
||||
idTypeIndex: 0,
|
||||
idTypeOptions: [
|
||||
|
|
@ -265,9 +285,79 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44
|
||||
},
|
||||
// 仅 没有(无数据/0)、退回(3)、过期(4) 可编辑;待审核(1)、已通过(2) 不可编辑
|
||||
formEditable() {
|
||||
const s = this.realNameStatus
|
||||
if (s == null) return true
|
||||
return s === 0 || s === 3 || s === 4
|
||||
},
|
||||
realNameStatusText() {
|
||||
const map = { 0: '未认证', 1: '待审核', 2: '已通过', 3: '已退回', 4: '已过期' }
|
||||
return map[this.realNameStatus] ?? '未知'
|
||||
},
|
||||
idTypeLabel() {
|
||||
if (this.formData.idType == null) return '请选择证件类型'
|
||||
const opt = this.idTypeOptions.find(item => item.value === this.formData.idType)
|
||||
return opt ? opt.label : '请选择证件类型'
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0
|
||||
this.loadRealNameInfo()
|
||||
},
|
||||
methods: {
|
||||
async loadRealNameInfo() {
|
||||
this.realNameLoading = true
|
||||
try {
|
||||
const userInfo = uni.getStorageSync('userInfo')
|
||||
if (!userInfo || !userInfo.id) {
|
||||
this.realNameStatus = 0
|
||||
return
|
||||
}
|
||||
const res = await getRealNameInfo({ id: userInfo.id })
|
||||
this.fillFormFromApi(res)
|
||||
} catch (e) {
|
||||
console.error('获取实名信息失败:', e)
|
||||
this.realNameStatus = 0
|
||||
} finally {
|
||||
this.realNameLoading = false
|
||||
}
|
||||
},
|
||||
fillFormFromApi(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
this.realNameStatus = 0
|
||||
return
|
||||
}
|
||||
const s = data.status != null ? Number(data.status) : (data.id ? 1 : 0)
|
||||
this.realNameStatus = s
|
||||
this.formData.realName = data.realName ?? ''
|
||||
this.formData.idType = data.idType != null ? data.idType : null
|
||||
this.formData.idNumber = data.idNumber ?? ''
|
||||
this.formData.idFrontImg = data.idFrontImg ?? ''
|
||||
this.formData.idBackImg = data.idBackImg ?? ''
|
||||
this.formData.driverLicenseFrontImg = data.driverLicenseFrontImg ?? ''
|
||||
this.formData.driverLicenseBackImg = data.driverLicenseBackImg ?? ''
|
||||
this.formData.vehicleLicenseFrontImg = data.vehicleLicenseFrontImg ?? ''
|
||||
this.formData.vehicleLicenseBackImg = data.vehicleLicenseBackImg ?? ''
|
||||
this.formData.imgVerifyRemark = data.imgVerifyRemark ?? ''
|
||||
const idx = this.idTypeOptions.findIndex(item => item.value === this.formData.idType)
|
||||
this.idTypeIndex = idx >= 0 ? idx : 0
|
||||
},
|
||||
|
||||
toggleAgreement() {
|
||||
this.agreedToTerms = !this.agreedToTerms
|
||||
},
|
||||
goToUserAgreement() {
|
||||
uni.navigateTo({ url: '/pages/profileSub/userAgreement' })
|
||||
},
|
||||
goToPrivacyPolicy() {
|
||||
uni.navigateTo({ url: '/pages/profileSub/privacyPolicy' })
|
||||
},
|
||||
|
||||
// 证件类型选择
|
||||
handleIdTypeChange(e) {
|
||||
|
|
@ -430,6 +520,34 @@ export default {
|
|||
background: #e2e8f1;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #e2e8f1;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 0;
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.form-content {
|
||||
height: calc(100vh - 120rpx);
|
||||
padding: 0 30rpx 40rpx;
|
||||
|
|
@ -446,6 +564,17 @@ export default {
|
|||
margin-bottom: 24rpx;
|
||||
padding-left: 10rpx;
|
||||
}
|
||||
|
||||
.status-tip {
|
||||
margin-bottom: 20rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
background: rgba(255, 152, 0, 0.1);
|
||||
border-radius: 12rpx;
|
||||
.status-tip-text {
|
||||
font-size: 26rpx;
|
||||
color: #e65100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
|
|
@ -487,6 +616,10 @@ export default {
|
|||
&.placeholder {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
&.readonly {
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
|
|
@ -546,6 +679,11 @@ export default {
|
|||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<view class="service-records-page">
|
||||
<!-- 头部区域 -->
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="服务记录" />
|
||||
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<!-- Tab 切换 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
|
|
@ -10,21 +11,21 @@
|
|||
:class="{ active: currentTab === 'pending_payment' }"
|
||||
@click="switchTab('pending_payment')"
|
||||
>
|
||||
<text class="tab-text">待支付</text>
|
||||
<text class="tab-text">待结算</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'pending_verification' }"
|
||||
@click="switchTab('pending_verification')"
|
||||
>
|
||||
<text class="tab-text">已完成</text>
|
||||
<text class="tab-text">待核销</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'chargeback' }"
|
||||
@click="switchTab('chargeback')"
|
||||
:class="{ active: currentTab === 'completed' }"
|
||||
@click="switchTab('completed')"
|
||||
>
|
||||
<text class="tab-text">已退款</text>
|
||||
<text class="tab-text">已完成</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
|
|
@ -77,13 +78,13 @@
|
|||
<view
|
||||
class="record-goods"
|
||||
v-for="(goods, gIndex) in getGoodsList(item)"
|
||||
:key="goods.id || goods.couponId || gIndex"
|
||||
:key="goods.id || goods.voucherId || gIndex"
|
||||
>
|
||||
<image
|
||||
class="goods-image"
|
||||
:src="
|
||||
goods.coverUrl ||
|
||||
goods.couponCoverUrl ||
|
||||
goods.voucherCoverUrl ||
|
||||
goods.picUrl ||
|
||||
'/static/home/entry_icon.png'
|
||||
"
|
||||
|
|
@ -91,7 +92,7 @@
|
|||
></image>
|
||||
<view class="goods-info">
|
||||
<text class="goods-title">{{
|
||||
goods.couponName || goods.name
|
||||
goods.voucherName || goods.name
|
||||
}}</text>
|
||||
<!-- <text class="goods-subtitle">
|
||||
订单号:{{ item.orderNumber }}
|
||||
|
|
@ -119,6 +120,7 @@
|
|||
src="/static/home/qr_code_icon.png"
|
||||
mode="aspectFill"
|
||||
class="qr-code-icon"
|
||||
:class="{ gray: item.status === 4 }"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -142,7 +144,7 @@
|
|||
取消订单
|
||||
</button>
|
||||
<button class="action-btn pay-btn" @click.stop="handlePay(item)">
|
||||
立即支付
|
||||
去结算
|
||||
</button>
|
||||
</view>
|
||||
<view class="record-actions" v-else-if="item.status === 1">
|
||||
|
|
@ -205,19 +207,20 @@
|
|||
<view class="qr-modal-close" @click="closeQrModal">×</view>
|
||||
</view>
|
||||
<view class="qr-modal-body">
|
||||
<view class="qr-code-box" v-if="qrModalData.couponCode">
|
||||
<view class="qr-code-box" v-if="qrModalData.voucherCode">
|
||||
<image
|
||||
class="qr-code-image"
|
||||
:src="qrCodeImageUrl"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<!-- <text class="qr-code-text">{{ qrModalData.couponCode }}</text> -->
|
||||
<!-- <text class="qr-code-text">{{ qrModalData.voucherCode }}</text> -->
|
||||
</view>
|
||||
<text v-else class="qr-code-empty">暂无券码</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -230,6 +233,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
currentTab: "pending_payment", // 当前选中的 tab
|
||||
refreshing: false,
|
||||
loading: false,
|
||||
|
|
@ -247,31 +251,35 @@ export default {
|
|||
qrModalVisible: false,
|
||||
qrModalData: {
|
||||
useStatus: 0,
|
||||
couponCode: "",
|
||||
voucherCode: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
currentList() {
|
||||
return this.recordsMap[this.currentTab] || [];
|
||||
},
|
||||
qrCodeImageUrl() {
|
||||
const code = this.qrModalData.couponCode || "";
|
||||
const code = this.qrModalData.voucherCode || "";
|
||||
if (!code) return "";
|
||||
return `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(code)}`;
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
// 如果通过参数传入 tab,优先使用传入的 tab 值
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
if (options && options.tab) {
|
||||
this.currentTab = options.tab;
|
||||
}
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
// 一个订单可能包含多个商品(couponPurchaseRespVOS)
|
||||
// 一个订单可能包含多个商品(voucherPurchaseRespVOS)
|
||||
getGoodsList(order) {
|
||||
const list = (order && order.couponPurchaseRespVOS) || [];
|
||||
const list = (order && order.voucherPurchaseRespVOS) || [];
|
||||
return Array.isArray(list) && list.length ? list : [order || {}];
|
||||
},
|
||||
// 金额分转元(去掉后两位)
|
||||
|
|
@ -298,7 +306,7 @@ export default {
|
|||
// 根据当前 tab 映射到接口所需的 status 值
|
||||
getStatusValue() {
|
||||
const map = {
|
||||
pending_payment: 0, // 待支付
|
||||
pending_payment: 0, // 待结算
|
||||
pending_verification: 1, // 已完成
|
||||
chargeback: 3, // 已退款
|
||||
cancelled: 4, // 已取消
|
||||
|
|
@ -316,7 +324,7 @@ export default {
|
|||
// 获取 Tab 标签文本
|
||||
getTabLabel() {
|
||||
const labels = {
|
||||
pending_payment: "待支付",
|
||||
pending_payment: "待结算",
|
||||
pending_verification: "待核销",
|
||||
completed: "已完成",
|
||||
cancelled: "已取消",
|
||||
|
|
@ -326,7 +334,7 @@ export default {
|
|||
// 获取状态文本
|
||||
getStatusText(status) {
|
||||
return status === 0
|
||||
? "待支付"
|
||||
? "待结算"
|
||||
: status === 1
|
||||
? "已完成"
|
||||
: status === 3
|
||||
|
|
@ -451,30 +459,30 @@ export default {
|
|||
},
|
||||
});
|
||||
},
|
||||
// 立即支付(与店铺详情页 handlePay 逻辑一致,跳转收银台小程序)
|
||||
// 去结算(与店铺详情页 handlePay 逻辑一致,跳转收银台小程序)
|
||||
async handlePay(item) {
|
||||
if (!item || !item.orderNumber) {
|
||||
uni.showToast({ title: "订单信息异常", icon: "none" });
|
||||
return;
|
||||
}
|
||||
const couponArray = item.couponPurchaseRespVOS || [];
|
||||
const couponMap = new Map();
|
||||
couponArray.forEach((goods) => {
|
||||
const key = goods.couponId;
|
||||
const voucherArray = item.voucherPurchaseRespVOS || [];
|
||||
const voucherMap = new Map();
|
||||
voucherArray.forEach((goods) => {
|
||||
const key = goods.voucherId;
|
||||
if (!key) return;
|
||||
const currentList = couponMap.get(key) || [];
|
||||
const currentList = voucherMap.get(key) || [];
|
||||
currentList.push(goods);
|
||||
couponMap.set(key, currentList);
|
||||
voucherMap.set(key, currentList);
|
||||
});
|
||||
let bodyStr = "";
|
||||
couponMap.forEach((couponList) => {
|
||||
voucherMap.forEach((voucherList) => {
|
||||
bodyStr =
|
||||
bodyStr +
|
||||
(couponList[0] && couponList[0].couponName
|
||||
? couponList[0].couponName
|
||||
(voucherList[0] && voucherList[0].voucherName
|
||||
? voucherList[0].voucherName
|
||||
: "商品") +
|
||||
"x" +
|
||||
couponList.length +
|
||||
voucherList.length +
|
||||
";";
|
||||
});
|
||||
|
||||
|
|
@ -520,8 +528,8 @@ export default {
|
|||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("获取支付签名失败:", e);
|
||||
uni.showToast({ title: "支付准备失败,请稍后重试", icon: "none" });
|
||||
console.error("获取签名失败:", e);
|
||||
uni.showToast({ title: "结算准备失败,请稍后重试", icon: "none" });
|
||||
}
|
||||
},
|
||||
// 查看详情
|
||||
|
|
@ -538,15 +546,15 @@ export default {
|
|||
icon: "none",
|
||||
});
|
||||
},
|
||||
// 点击二维码图标:打开弹窗,展示券状态 + 根据 couponCode 生成二维码
|
||||
// 点击二维码图标:打开弹窗,展示券状态 + 根据 voucherCode 生成二维码
|
||||
handleQrCode(item, goods) {
|
||||
const useStatus = goods?.useStatus ?? item?.useStatus ?? 0;
|
||||
const couponCode =
|
||||
(goods && (goods.couponCode || goods.couponNo)) ||
|
||||
item?.couponCode ||
|
||||
item?.couponNo ||
|
||||
const voucherCode =
|
||||
(goods && (goods.voucherCode || goods.voucherNo)) ||
|
||||
item?.voucherCode ||
|
||||
item?.voucherNo ||
|
||||
"";
|
||||
this.qrModalData = { useStatus, couponCode };
|
||||
this.qrModalData = { useStatus, voucherCode };
|
||||
this.qrModalVisible = true;
|
||||
},
|
||||
getQrUseStatusText(useStatus) {
|
||||
|
|
@ -577,6 +585,22 @@ export default {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #e2e8f1;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Tab 切换栏 */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
|
|
@ -843,6 +867,11 @@ export default {
|
|||
.qr-code-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
|
||||
&.gray {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
<template>
|
||||
<view class="agreement-page">
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="用户服务协议" />
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<scroll-view class="content" scroll-y="true">
|
||||
<view class="agreement-content">
|
||||
<text class="agreement-text">{{ agreementText }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -18,6 +22,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
agreementText: `用户服务协议
|
||||
|
||||
鉴于甲方运营微信小程序(以下简称"小程序"),为乙方提供餐饮、维修、保险、健康等专属收费服务;乙方自愿使用甲方提供的服务,为明确双方权利义务,根据《中华人民共和国民法典》《中华人民共和国个人信息保护法》等相关法律法规,甲乙双方本着平等、自愿、公平、诚实信用的原则,达成如下协议,以资共同遵守。
|
||||
|
|
@ -30,11 +35,11 @@ export default {
|
|||
4.健康服务:包括但不限于司机专属健康体检套餐、健康咨询、职业病预防指导等收费服务。
|
||||
1.2 甲方有权根据市场需求、业务发展等情况调整服务内容及收费项目,相关调整将通过小程序公告或短信提前通知乙方。乙方继续使用服务的,视为认可调整后的内容。
|
||||
|
||||
二、服务费用与支付
|
||||
二、服务费用与结算
|
||||
2.1 收费标准:本协议项下服务均为收费服务,具体收费标准、计费方式、服务明细均在小程序对应服务页面明确公示,乙方可自行查看并选择购买。
|
||||
2.2 价格调整:甲方可根据市场行情、服务成本等因素调整收费标准,调整后将通过小程序公告提前7个自然日公示,公示期满后生效。
|
||||
2.3 支付方式:乙方应通过小程序内指定支付渠道(如微信支付)支付费用,支付成功后视为订单确认,甲方依订单约定提供服务。
|
||||
2.4 套餐续费:套餐类、周期类服务需一次性支付对应周期费用,乙方应在服务期满前按小程序提示续费,逾期未续费的,服务自动终止。
|
||||
2.3 结算方式:乙方应通过小程序内指定渠道结算费用,结算成功后视为订单确认,甲方依订单约定提供服务。
|
||||
2.4 套餐续费:套餐类、周期类服务需一次性结算对应周期费用,乙方应在服务期满前按小程序提示续费,逾期未续费的,服务自动终止。
|
||||
|
||||
三、费用退还
|
||||
3.1 因甲方原因(如无法提供约定服务、服务存在重大瑕疵无法补救)导致服务无法履行的,甲方应根据乙方未使用的服务内容或时长,退还相应费用。
|
||||
|
|
@ -45,14 +50,14 @@ export default {
|
|||
4.1 乙方权利:
|
||||
1.要求甲方按协议及订单约定提供符合标准的服务;
|
||||
2.监督服务质量,对服务问题提出改进建议;
|
||||
3.查询、更正本人的个人信息及服务订单、支付记录;
|
||||
3.查询、更正本人的个人信息及服务订单、结算记录;
|
||||
4.符合退款条件时,申请退还相应服务费用;
|
||||
5.按约定申请终止本协议。
|
||||
4.2 乙方义务:
|
||||
1.向甲方提供真实、准确、完整的个人信息及相关资料,不得提供虚假信息或隐瞒重要事实;
|
||||
2.遵守国家法律法规及甲方发布的服务规则,不得利用服务从事违法违规、损害他人合法权益的活动;
|
||||
3.妥善保管小程序账号、密码等身份信息,对账号下所有操作行为承担责任,发现账号泄露、被盗用应及时通知甲方;
|
||||
4.按协议及小程序公示标准,及时、足额支付服务费用;
|
||||
4.按协议及小程序公示标准,及时、足额结算服务费用;
|
||||
5.配合甲方完成服务必需的信息核验、服务对接等工作。
|
||||
|
||||
五、甲方权利与义务
|
||||
|
|
@ -80,14 +85,14 @@ export default {
|
|||
1.提供虚假信息、伪造证明材料,影响服务开展的;
|
||||
2.违反协议或甲方服务规则,经通知后限期未改正的;
|
||||
3.利用服务从事违法违规活动的;
|
||||
4.逾期支付费用超过15日,经催告仍未支付的;
|
||||
4.逾期结算费用超过15日,经催告仍未结算的;
|
||||
5.其他严重损害甲方合法权益的行为。
|
||||
7.4 因不可抗力、政策调整等不可归责于双方的原因导致协议无法履行的,协议自动终止,双方互不担责,甲方退还乙方未使用部分的服务费用。
|
||||
|
||||
八、违约责任
|
||||
8.1 任何一方违反协议约定,给对方造成损失的,应承担全部赔偿责任(包括直接损失、维权产生的律师费、诉讼费等合理费用)。
|
||||
8.2 甲方未按约定提供服务的,除退还相应费用外,还应按乙方已支付对应服务费用的10%支付违约金;违约金不足以弥补损失的,补足差额。
|
||||
8.3 乙方未按时支付费用的,每逾期一日按逾期金额的0.5%支付违约金;逾期超过15日的,甲方有权终止协议并要求乙方赔偿损失。
|
||||
8.2 甲方未按约定提供服务的,除退还相应费用外,还应按乙方已结算对应服务费用的10%承担违约金;违约金不足以弥补损失的,补足差额。
|
||||
8.3 乙方未按时结算费用的,每逾期一日按逾期金额的0.5%计违约金;逾期超过15日的,甲方有权终止协议并要求乙方赔偿损失。
|
||||
8.4 乙方利用服务从事违法违规活动或损害第三方权益的,法律责任由乙方自行承担,给甲方造成损失的,乙方全额赔偿。
|
||||
|
||||
九、争议解决
|
||||
|
|
@ -98,7 +103,16 @@ export default {
|
|||
10.2 甲方通过小程序公告、短信发送的通知、公示,自发布或送达之日起生效,视为已履行告知义务,乙方应及时关注。
|
||||
10.3 本协议条款被认定为无效或不可执行的,不影响其他条款的效力。`
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -110,6 +124,22 @@ export default {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<view class="webview-page">
|
||||
<!-- 头部导航栏 -->
|
||||
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
|
||||
<NavHeader title="网页" />
|
||||
|
||||
<!-- WebView 容器 -->
|
||||
</view>
|
||||
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
|
||||
<view class="webview-container">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<web-view :src="webviewUrl"></web-view>
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -27,10 +28,18 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: 0,
|
||||
webviewUrl: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
headerHeight() {
|
||||
return this.statusBarHeight + 44;
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
// 从路由参数获取要加载的 URL
|
||||
if (options.url) {
|
||||
this.webviewUrl = decodeURIComponent(options.url);
|
||||
|
|
@ -56,6 +65,22 @@ export default {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-fixed-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.webview-container {
|
||||
flex: 1;
|
||||
height: 0; // 配合 flex: 1 使用
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Loading…
Reference in New Issue