Compare commits

..

No commits in common. "main" and "cz_dev" have entirely different histories.
main ... cz_dev

30 changed files with 888 additions and 2797 deletions

116
App.vue
View File

@ -4,26 +4,10 @@
export default { export default {
globalData: { globalData: {
// //
serviceCategory: null, serviceCategory: null
// scene-id '0-123'
inviteCode: null
}, },
onLaunch: function(options) { onLaunch: function() {
console.log('App Launch') console.log('App Launch')
// scene -id 0-1231-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() this.checkLoginStatus()
}, },
@ -35,52 +19,52 @@
console.log(options) console.log(options)
// 2. scene '1038' // 2. scene '1038'
if (options.scene === 1038 && // if (options.scene === 1038 &&
options.referrerInfo?.appId === 'wxef277996acc166c3') { // options.referrerInfo?.appId === 'wxef277996acc166c3') {
// // //
const extraData = options.referrerInfo.extraData; // const extraData = options.referrerInfo.extraData;
console.log("extraData",extraData) // console.log("extraData",extraData)
if (!extraData) { // if (!extraData) {
uni.showToast({ // uni.showToast({
title: '当前通过物理按键返回,未接收到返参,建议自行查询交易结果', // title: '',
icon: 'none', // icon: 'none',
duration: 3000 // duration: 3000
}); // });
} else { // } else {
if (extraData.code === 'success') { // if (extraData.code === 'success') {
// // //
// 1. 使 // // 1. 使
const orderNumberFromExtra = // const orderNumberFromExtra =
extraData.orderNumber || extraData.reqsn || extraData.orderNo; // extraData.orderNumber || extraData.reqsn || extraData.orderNo;
// 2. 使 // // 2. 使
const storedOrderNumber = uni.getStorageSync("lastOrderNumber"); // const storedOrderNumber = uni.getStorageSync("lastOrderNumber");
const orderNumber = orderNumberFromExtra || storedOrderNumber; // const orderNumber = orderNumberFromExtra || storedOrderNumber;
if (orderNumber) { // if (orderNumber) {
this.startOrderStatusPolling(orderNumber); // this.startOrderStatusPolling(orderNumber);
} else { // } else {
uni.showToast({ // uni.showToast({
title: '结算返回缺少订单号,请稍后在服务记录中查看', // title: '',
icon: 'none', // icon: 'none',
duration: 3000 // duration: 3000
}); // });
} // }
} else if (extraData.code === 'cancel') { // } else if (extraData.code === 'cancel') {
uni.showToast({ // uni.showToast({
title: '已取消', // title: '',
icon: 'none' // icon: 'none'
}); // });
} else { // } else {
uni.showToast({ // uni.showToast({
title: `结算失败:${extraData.errmsg || '未知错误'}`, // title: `${extraData.errmsg || ''}`,
icon: 'none', // icon: 'none',
duration: 3000 // duration: 3000
}); // });
} // }
} // }
} // }
}, },
onHide: function() { onHide: function() {
console.log('App Hide') console.log('App Hide')
@ -118,7 +102,7 @@
}, 100) }, 100)
} }
}, },
// 3 3 4 // 3 3 4
startOrderStatusPolling(orderNumber) { startOrderStatusPolling(orderNumber) {
if (this._orderStatusTimer) { if (this._orderStatusTimer) {
clearInterval(this._orderStatusTimer); clearInterval(this._orderStatusTimer);
@ -136,7 +120,7 @@
clearInterval(this._orderStatusTimer); clearInterval(this._orderStatusTimer);
this._orderStatusTimer = null; this._orderStatusTimer = null;
uni.removeStorageSync("lastOrderNumber"); uni.removeStorageSync("lastOrderNumber");
uni.showToast({ title: '结算成功', icon: 'success' }); uni.showToast({ title: '支付成功', icon: 'success' });
uni.navigateTo({ uni.navigateTo({
url: '/pages/profileSub/serviceRecords?tab=pending_verification' url: '/pages/profileSub/serviceRecords?tab=pending_verification'
}); });
@ -146,13 +130,13 @@
clearInterval(this._orderStatusTimer); clearInterval(this._orderStatusTimer);
this._orderStatusTimer = null; this._orderStatusTimer = null;
uni.showToast({ uni.showToast({
title: '状态确认超时,请稍后在服务记录中查看', title: '支付状态确认超时,请稍后在服务记录中查看',
icon: 'none', icon: 'none',
duration: 3000 duration: 3000
}); });
} }
} catch (error) { } catch (error) {
console.error('查询订单状态失败:', error); console.error('查询订单支付状态失败:', error);
if (times >= maxTimes) { if (times >= maxTimes) {
clearInterval(this._orderStatusTimer); clearInterval(this._orderStatusTimer);
this._orderStatusTimer = null; this._orderStatusTimer = null;
@ -169,4 +153,4 @@
<style> <style>
/*每个页面公共css */ /*每个页面公共css */
</style> </style>

View File

@ -27,7 +27,7 @@ export function login(data) {
* @param {String} data.phoneCode 手机 code, 小程序通过 wx.getPhoneNumber 方法获得 * @param {String} data.phoneCode 手机 code, 小程序通过 wx.getPhoneNumber 方法获得
* @param {String} data.loginCode 登录 code, 小程序通过 wx.login 方法获得 * @param {String} data.loginCode 登录 code, 小程序通过 wx.login 方法获得
* @param {String} data.state state 参数必填用于回调的随机值 * @param {String} data.state state 参数必填用于回调的随机值
* @param {String} data.inviteCode 邀请码可选格式 "邀请类型-用户id"0=会员邀请会员1=系统用户邀请会员例如 0-1231-123 * @param {String} data.inviteCode 邀请码可选第一位是类型第二位是用户id例如1-1, 0-1
* @returns {Promise} 返回登录结果包含token等 * @returns {Promise} 返回登录结果包含token等
*/ */
export function loginByPhone(data) { export function loginByPhone(data) {

View File

@ -5,8 +5,7 @@
// 基础URL配置注意末尾不要加斜杠 // 基础URL配置注意末尾不要加斜杠
const BASE_URL = 'https://guangsh.manage.hschengtai.com' const BASE_URL = 'https://guangsh.manage.hschengtai.com'
// const BASE_URL = 'http://192.168.0.97:48085' // const BASE_URL = 'http://192.168.5.134:48085'
// const BASE_URL = 'http://192.168.5.135:48085'
// 是否正在刷新token防止并发刷新 // 是否正在刷新token防止并发刷新
let isRefreshing = false let isRefreshing = false
// 等待刷新完成的请求队列 // 等待刷新完成的请求队列
@ -280,8 +279,7 @@ export function request(options = {}) {
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: res.data.msg || res.data.message || '账号未登录,请前往登录', content: res.data.msg || res.data.message || '账号未登录,请前往登录',
showCancel: true, showCancel: false,
cancelText: '取消',
confirmText: '去登录', confirmText: '去登录',
success: (modalRes) => { success: (modalRes) => {
if (modalRes.confirm) { if (modalRes.confirm) {
@ -330,8 +328,7 @@ export function request(options = {}) {
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: errorMsg, content: errorMsg,
showCancel: true, showCancel: false,
cancelText: '取消',
confirmText: '去登录', confirmText: '去登录',
success: (modalRes) => { success: (modalRes) => {
if (modalRes.confirm) { if (modalRes.confirm) {

View File

@ -28,17 +28,6 @@ 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) { export function getMyCollect(data) {
return request({ return request({
@ -50,17 +39,6 @@ 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) { export function createLaborUnionSuggest(data) {
return request({ return request({
@ -72,17 +50,6 @@ 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) { export function createLaborUnionMessage(data) {
return request({ return request({
@ -92,44 +59,4 @@ export function createLaborUnionMessage(data) {
showLoading: false, showLoading: false,
needAuth: true // 需要token认证 needAuth: true // 需要token认证
}) })
}
/**
* 获取邀请小程序码图片入参格式与后端约定一致支持返回 URL Base64
* @param {String} inviteCode 邀请码格式 邀请类型-用户id '0-123'会作为 scene 传给后端若后端需数字可传 userId
* @returns {Promise<String>} 小程序码图片 URL Data URLBase64失败则抛出
*/
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
})
} }

View File

@ -30,10 +30,10 @@ export function getGuildStoreDetail(id) {
}) })
} }
// 获得工会代金 // 获得工会优惠
export function getGuildVoucher(params = {}) { export function getGuildCoupon(params = {}) {
return request({ return request({
url: '/app-api/member/labor-union-voucher/page', url: '/app-api/member/labor-union-coupon/page',
method: 'GET', method: 'GET',
data: params, data: params,
}) })
@ -100,12 +100,12 @@ export function isNeedMap(params = {}) {
}) })
} }
// 工会代金券/商品下单并发起微信支付(需要后端返回小程序支付参数) // 工会优惠券/商品下单并发起微信支付(需要后端返回小程序支付参数)
// 约定:后端返回字段需包含 timeStamp、nonceStr、package、signType、paySign或等价字段 // 约定:后端返回字段需包含 timeStamp、nonceStr、package、signType、paySign或等价字段
// export function createGuildVoucherWxPay(params = {}) { // export function createGuildCouponWxPay(params = {}) {
// return request({ // return request({
// // TODO: 如后端实际路径不同,请替换这里的 url // // TODO: 如后端实际路径不同,请替换这里的 url
// url: '/app-api/member/labor-union-voucher/pay', // url: '/app-api/member/labor-union-coupon/pay',
// method: 'POST', // method: 'POST',
// data: params, // data: params,
// showLoading: true, // showLoading: true,
@ -136,18 +136,18 @@ export function updateLuOrderPayStatus(params = {}){
}) })
} }
// 删除代金券购买 (暂时不用) // 删除优惠卷购买 (暂时不用)
// export function delVoucherPurchaseBuy(id){ // export function delCouponPurchaseBuy(id){
// return request({ // return request({
// url: '/app-api/member/labor-union-voucher-purchase/delete?id='+id, // url: '/app-api/member/labor-union-coupon-purchase/delete?id='+id,
// method: 'DELETE', // method: 'DELETE',
// }) // })
// } // }
// // 取消代金券购买 (暂时不用) // // 取消优惠卷购买 (暂时不用)
// export function cancelVoucherPurchaseBuy(id){ // export function cancelCouponPurchaseBuy(id){
// return request({ // return request({
// url: '/app-api/member/labor-union-voucher-purchase/cancel?id='+id, // url: '/app-api/member/labor-union-coupon-purchase/cancel?id='+id,
// method: 'POST', // method: 'POST',
// }) // })
// } // }
@ -183,22 +183,4 @@ 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 },
})
}

View File

@ -6,13 +6,8 @@
</view> --> </view> -->
<view class="action-item" @click="handleLike"> <view class="action-item" @click="handleLike">
<image <image class="action-icon" src="/static/service/like_icon.png" mode="aspectFill"></image>
class="action-icon" <text class="action-text">点赞</text>
: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> </view>
<button class="action-item share-button" open-type="share" @click="handleShareClick"> <button class="action-item share-button" open-type="share" @click="handleShareClick">
@ -21,13 +16,8 @@
</button> </button>
<view class="action-item" @click="handleCollect"> <view class="action-item" @click="handleCollect">
<image <image class="action-icon" src="/static/service/collect_icon.png" mode="aspectFill"></image>
class="action-icon" <text class="action-text">收藏</text>
: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>
</view> </view>
</template> </template>
@ -265,6 +255,11 @@ export default {
&.active { &.active {
transform: scale(1.1); transform: scale(1.1);
} }
.icon-text {
font-size: 48rpx;
line-height: 1;
}
} }
.action-text { .action-text {

View File

@ -52,13 +52,6 @@
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, },
{
"path": "selectCoupon",
"style": {
"navigationBarTitleText": "选择优惠卷",
"navigationStyle": "custom"
}
},
{ {
"path": "mapDetail", "path": "mapDetail",
"style": { "style": {
@ -98,8 +91,14 @@
"navigationBarTitleText": "投诉建议", "navigationBarTitleText": "投诉建议",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
},
{
"path": "postMessage",
"style": {
"navigationBarTitleText": "发布消息",
"navigationStyle": "custom"
}
} }
] ]
}, },
{ {
@ -139,20 +138,6 @@
"navigationBarTitleText": "隐私政策", "navigationBarTitleText": "隐私政策",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
},
{
"path": "myCoupons",
"style": {
"navigationBarTitleText": "我的优惠卷",
"navigationStyle": "custom"
}
},
{
"path": "couponDetail",
"style": {
"navigationBarTitleText": "优惠卷详情",
"navigationStyle": "custom"
}
} }
] ]
} }

View File

@ -1,18 +1,10 @@
<template> <template>
<view class="complaints-page"> <view class="complaints-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 头部区域 -->
<NavHeader title="投诉建议" /> <NavHeader title="投诉建议" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }"> <!-- 表单内容区 -->
<scroll-view <scroll-view class="form-content" scroll-y="true">
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="form-section">
<view class="section-title">填写投诉建议</view> <view class="section-title">填写投诉建议</view>
@ -60,41 +52,12 @@
{{ loading ? '提交中...' : '提交' }} {{ loading ? '提交中...' : '提交' }}
</button> </button>
</view> </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> </scroll-view>
</view>
</view> </view>
</template> </template>
<script> <script>
import { createLaborUnionSuggest,getLaborUnionSuggestPage } from '@/api/profile.js' import { createLaborUnionSuggest } from '@/api/profile.js'
import NavHeader from "@/components/NavHeader/NavHeader.vue"; import NavHeader from "@/components/NavHeader/NavHeader.vue";
export default { export default {
@ -103,91 +66,22 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
loading: false, loading: false,
formData: { formData: {
title: '', title: '',
content: '' content: ''
}, }
suggestList: [],
listPageNo: 1,
listPageSize: 10,
listHasMore: true,
listLoading: false,
listLoadMore: false,
listRefreshing: false
} }
}, },
computed: { computed: {
headerHeight() {
return this.statusBarHeight + 44
},
// //
canSubmit() { canSubmit() {
return this.formData.title && this.formData.title.trim().length >= 1 return this.formData.title && this.formData.title.trim().length >= 1
} }
}, },
onLoad() { onLoad() {
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
this.loadSuggestList()
}, },
methods: { 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() { async handleSubmit() {
// //
@ -216,11 +110,10 @@ export default {
icon: 'success' icon: 'success'
}) })
this.formData.title = '' //
this.formData.content = '' setTimeout(() => {
this.listPageNo = 1 uni.navigateBack()
this.listHasMore = true }, 1500)
this.loadSuggestList()
} catch (error) { } catch (error) {
console.error('提交投诉建议失败:', error) console.error('提交投诉建议失败:', error)
uni.showToast({ uni.showToast({
@ -243,22 +136,6 @@ export default {
flex-direction: column; 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 { .form-content {
flex: 1; flex: 1;
height: 0; height: 0;
@ -358,7 +235,7 @@ export default {
.submit-section { .submit-section {
margin-top: 40rpx; margin-top: 40rpx;
padding-bottom: 24rpx; padding-bottom: 40rpx;
.submit-btn { .submit-btn {
width: 100%; width: 100%;
@ -383,99 +260,4 @@ 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> </style>

View File

@ -1,9 +1,9 @@
<template> <template>
<view class="activities-list-page"> <view class="activities-list-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 顶部导航栏 -->
<NavHeader title="工会活动" :show-placeholder="true" /> <NavHeader title="工会活动" :show-placeholder="true" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }"> <!-- 活动列表 -->
<scroll-view <scroll-view
class="activities-scroll" class="activities-scroll"
scroll-y="true" scroll-y="true"
@ -61,7 +61,6 @@
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
</view>
</view> </view>
</template> </template>
@ -76,7 +75,6 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
activitiesList: [], activitiesList: [],
// //
page: 1, page: 1,
@ -88,14 +86,7 @@ export default {
refreshing: false, refreshing: false,
}; };
}, },
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
},
onLoad() { onLoad() {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
this.loadActivities(); this.loadActivities();
}, },
methods: { methods: {
@ -194,22 +185,7 @@ export default {
overflow: hidden; 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 { .activities-scroll {
flex: 1; flex: 1;
height: 0; // flex: 1 使 scroll-view height: 0; // flex: 1 使 scroll-view

View File

@ -1,9 +1,9 @@
<template> <template>
<view class="my-collect-page"> <view class="my-collect-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 顶部导航栏 -->
<NavHeader title="我的收藏" /> <NavHeader title="我的收藏" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }"> <!-- 收藏列表 -->
<scroll-view <scroll-view
class="collect-scroll" class="collect-scroll"
scroll-y="true" scroll-y="true"
@ -57,12 +57,11 @@
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
</view>
</view> </view>
</template> </template>
<script> <script>
import { getMyCollect,getBusinessData } from "@/api/profile.js"; import { getMyCollect } from "@/api/profile.js";
import NavHeader from "@/components/NavHeader/NavHeader.vue"; import NavHeader from "@/components/NavHeader/NavHeader.vue";
export default { export default {
@ -71,7 +70,6 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
collectList: [], collectList: [],
// //
pageNo: 1, pageNo: 1,
@ -83,14 +81,7 @@ export default {
refreshing: false, refreshing: false,
}; };
}, },
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
},
onLoad() { onLoad() {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
this.loadCollectList(); this.loadCollectList();
}, },
methods: { methods: {
@ -169,17 +160,11 @@ export default {
return `${year}-${month}-${day} ${hours}:${minutes}`; return `${year}-${month}-${day} ${hours}:${minutes}`;
}, },
// getBusinessData(id: collectId, type: collectType) //
handleCollectClick(item) { handleCollectClick(item) {
const collectId = item.collectId ?? item.id; // uni.navigateTo({
const collectType = item.collectType ?? item.type ?? ""; // url: `/pages/detail/richTextDetail?id=${item.collectId}`,
if (collectId == null || collectId === "") { // });
uni.showToast({ title: "数据异常", icon: "none" });
return;
}
uni.navigateTo({
url: `/pages/detail/richTextDetail?collectId=${encodeURIComponent(collectId)}&collectType=${encodeURIComponent(collectType)}&hideBar=1`,
});
}, },
}, },
}; };
@ -194,22 +179,7 @@ export default {
overflow: hidden; 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 { .collect-scroll {
flex: 1; flex: 1;
height: 0; // flex: 1 使 scroll-view height: 0; // flex: 1 使 scroll-view

View File

@ -0,0 +1,617 @@
<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>

View File

@ -1,9 +1,8 @@
<template> <template>
<view class="map-detail-page"> <view class="map-detail-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 顶部导航栏 -->
<NavHeader title="店铺位置" /> <NavHeader title="店铺位置" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
<!-- 地图区域 --> <!-- 地图区域 -->
<view class="map-container" v-if="mapInitialized"> <view class="map-container" v-if="mapInitialized">
<map <map
@ -86,7 +85,6 @@
<view class="loading-mask" v-if="loading"> <view class="loading-mask" v-if="loading">
<text class="loading-text">加载中...</text> <text class="loading-text">加载中...</text>
</view> </view>
</view>
</view> </view>
</template> </template>
@ -101,7 +99,6 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
shopId: null, shopId: null,
storeInfo: {}, storeInfo: {},
loading: false, loading: false,
@ -121,9 +118,6 @@ export default {
}; };
}, },
computed: { computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
// // // //
// distanceText() { // distanceText() {
// if ( // if (
@ -168,8 +162,6 @@ export default {
}, },
}, },
onLoad(options) { onLoad(options) {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
this.shopId = options.id; this.shopId = options.id;
if (this.shopId) { if (this.shopId) {
// //
@ -429,22 +421,6 @@ export default {
overflow: hidden; 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 { .map-container {
width: 100%; width: 100%;

View File

@ -1,9 +1,9 @@
<template> <template>
<view class="rich-text-detail-page" :class="{ 'no-bottom-bar': hideBottomBar }"> <view class="rich-text-detail-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 顶部导航栏 -->
<NavHeader title="详情" /> <NavHeader title="详情" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }"> <!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y="true"> <scroll-view class="content-scroll" scroll-y="true">
<!-- 加载中 --> <!-- 加载中 -->
<view class="loading-state" v-if="loading"> <view class="loading-state" v-if="loading">
@ -45,22 +45,20 @@
</view> </view>
</scroll-view> </scroll-view>
<!-- 底部操作栏从收藏进入时隐藏 --> <!-- 底部操作栏 -->
<DetailActionBar <DetailActionBar
v-if="detailData && !hideBottomBar" v-if="detailData"
:liked="isLiked" :liked="isLiked"
:collected="isCollected" :collected="isCollected"
:id="detailId" :id="detailId"
:zanType="noticeType" :zanType="noticeType"
:type="noticeType" :type="noticeType"
/> />
</view>
</view> </view>
</template> </template>
<script> <script>
import { getGuildDetail } from "@/api/home.js"; import { getGuildDetail } from "@/api/home.js";
import { getBusinessData } from "@/api/profile.js";
import NavHeader from "@/components/NavHeader/NavHeader.vue"; import NavHeader from "@/components/NavHeader/NavHeader.vue";
import DetailActionBar from "@/components/DetailActionBar/DetailActionBar.vue"; import DetailActionBar from "@/components/DetailActionBar/DetailActionBar.vue";
@ -71,7 +69,6 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
detailId: null, // ID detailId: null, // ID
title: "", title: "",
content: "", content: "",
@ -81,28 +78,13 @@ export default {
isCollected: false, // isCollected: false, //
detailData: null, // 使 detailData: null, // 使
noticeType: null, // noticeType: null, //
hideBottomBar: false, // true
}; };
}, },
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
},
onLoad(options) { onLoad(options) {
const systemInfo = uni.getSystemInfoSync(); //
this.statusBarHeight = systemInfo.statusBarHeight || 0; // 1. id activitiesDetail
// // 2. title content richTextDetail
// 1. collectId + collectType getBusinessData if (options.id) {
// 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.detailId = options.id;
this.loadDetailById(); this.loadDetailById();
} else if (options.content) { } else if (options.content) {
@ -166,35 +148,6 @@ export default {
}, },
// #endif // #endif
methods: { 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 // ID activitiesDetail
async loadDetailById() { async loadDetailById() {
if (!this.detailId) { if (!this.detailId) {
@ -212,10 +165,13 @@ export default {
if (res.content) { if (res.content) {
this.parsedContent = this.parseHtmlContent(res.content); this.parsedContent = this.parseHtmlContent(res.content);
} }
// zanRespVO //
this.isLiked = this.hasRespData(res.zanRespVO); if (res.isLiked !== undefined) {
// collectRespVO this.isLiked = res.isLiked;
this.isCollected = this.hasRespData(res.collectRespVO); }
if (res.isCollected !== undefined) {
this.isCollected = res.isCollected;
}
} }
} catch (error) { } catch (error) {
console.error("加载详情失败:", error); console.error("加载详情失败:", error);
@ -298,28 +254,9 @@ export default {
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
padding-bottom: 120rpx; // 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 { .content-scroll {
flex: 1; flex: 1;
height: 0; // flex: 1 使 scroll-view height: 0; // flex: 1 使 scroll-view

View File

@ -1,253 +0,0 @@
<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>

View File

@ -1,9 +1,8 @@
<template> <template>
<view class="store-page"> <view class="store-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 顶部导航栏 -->
<NavHeader title="店铺详情" /> <NavHeader title="店铺详情" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
<!-- 顶部店铺信息点击进入地图页 --> <!-- 顶部店铺信息点击进入地图页 -->
<view class="store-header" @click="handleGoMap"> <view class="store-header" @click="handleGoMap">
<view class="store-brand"> <view class="store-brand">
@ -42,15 +41,10 @@
<!-- 中间菜单列表可滑动 --> <!-- 中间菜单列表可滑动 -->
<scroll-view class="menu-list" scroll-y="true"> <scroll-view class="menu-list" scroll-y="true">
<view <view class="menu-item" v-for="(item, index) in menuList" :key="index">
class="menu-item"
v-for="(item, index) in menuList"
:key="index"
:class="{ 'out-of-stock': item.stock === 0 }"
>
<view <view
class="checkbox" class="checkbox"
:class="{ checked: item.selected, disabled: item.stock === 0 }" :class="{ checked: item.selected }"
@click="toggleMenuItem(index)" @click="toggleMenuItem(index)"
> >
<text v-if="item.selected" class="checkmark"></text> <text v-if="item.selected" class="checkmark"></text>
@ -80,21 +74,17 @@
<view <view
class="quantity-btn minus" class="quantity-btn minus"
:class="{ :class="{
disabled: (item.quantity || 0) <= 0 || item.stock === 0, disabled: (item.quantity || 0) <= 0,
}" }"
@click="decreaseQuantity(index)" @click="decreaseQuantity(index)"
>-</view >-</view
> >
<text class="quantity-number">{{ item.quantity || 0 }}</text> <text class="quantity-number">{{ item.quantity || 0 }}</text>
<view <view class="quantity-btn plus" @click="increaseQuantity(index)"
class="quantity-btn plus"
:class="{ disabled: item.stock === 0 }"
@click="increaseQuantity(index)"
>+</view >+</view
> >
</view> </view>
</view> </view>
<view v-if="item.stock === 0" class="stock-tip"></view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
@ -102,8 +92,8 @@
<!-- 底部结算栏固定 --> <!-- 底部结算栏固定 -->
<view class="checkout-footer"> <view class="checkout-footer">
<view class="total-info"> <view class="total-info">
<text class="total-label">应付¥</text> <text class="total-label">总金额¥</text>
<text class="total-amount">{{ payableAmountYuan.toFixed(2) }}</text> <text class="total-amount">{{ totalAmount.toFixed(2) }}</text>
<view class="member-benefit"> <view class="member-benefit">
<image <image
class="crown-icon" class="crown-icon"
@ -113,15 +103,6 @@
<text class="benefit-text">{{ memberLevelName }}优惠</text> <text class="benefit-text">{{ memberLevelName }}优惠</text>
</view> </view>
</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 <button
class="checkout-btn" class="checkout-btn"
:class="{ disabled: totalAmount <= 0 }" :class="{ disabled: totalAmount <= 0 }"
@ -131,17 +112,15 @@
去结算 去结算
</button> </button>
</view> </view>
</view>
</view> </view>
</template> </template>
<script> <script>
import { import {
getGuildStoreDetail, getGuildStoreDetail,
getGuildVoucher, getGuildCoupon,
getPaySign, getPaySign,
appBuy, appBuy
getLuCouponPage
} from "@/api/service"; } from "@/api/service";
import NavHeader from "@/components/NavHeader/NavHeader.vue"; import NavHeader from "@/components/NavHeader/NavHeader.vue";
@ -151,7 +130,6 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
storeInfo: {}, storeInfo: {},
menuList: [], menuList: [],
categoryLabel: "", categoryLabel: "",
@ -159,16 +137,9 @@ export default {
userInfo: {}, userInfo: {},
distance: null, distance: null,
paying: false, paying: false,
coupons: [],
selectedCoupon: null,
selectedCouponId: null,
couponLoading: false,
}; };
}, },
computed: { computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
// 0 // 0
totalAmount() { totalAmount() {
return this.menuList.reduce((total, item) => { return this.menuList.reduce((total, item) => {
@ -180,36 +151,6 @@ export default {
return total; return total;
}, 0); }, 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() { hasMemberDiscount() {
return this.menuList.some((item) => item.selected && item.discount); return this.menuList.some((item) => item.selected && item.discount);
@ -223,8 +164,7 @@ export default {
}, },
}, },
onLoad(options) { onLoad(options) {
const systemInfo = uni.getSystemInfoSync(); console.log(options, 111110);
this.statusBarHeight = systemInfo.statusBarHeight || 0;
// ID // ID
this.shopId = options.id; this.shopId = options.id;
this.categoryLabel = options.categoryLabel; this.categoryLabel = options.categoryLabel;
@ -309,10 +249,10 @@ export default {
} }
}, },
// //
async loadStoreMenu(shopId) { async loadStoreMenu(shopId) {
try { try {
const res = await getGuildVoucher({ shopId }); const res = await getGuildCoupon({ shopId });
console.log(res, 11119); console.log(res, 11119);
if (res && res.list) { if (res && res.list) {
this.menuList = res.list; this.menuList = res.list;
@ -328,10 +268,6 @@ export default {
// //
toggleMenuItem(index) { toggleMenuItem(index) {
const item = this.menuList[index]; const item = this.menuList[index];
//
if (item && item.stock === 0) {
return;
}
item.selected = !item.selected; item.selected = !item.selected;
if (!item.selected) { if (!item.selected) {
item.quantity = 0; item.quantity = 0;
@ -339,32 +275,22 @@ export default {
// 0 1 // 0 1
item.quantity = 1; item.quantity = 1;
} }
this.autoSelectBestCoupon();
}, },
// //
increaseQuantity(index) { increaseQuantity(index) {
const item = this.menuList[index]; const item = this.menuList[index];
//
if (item && item.stock === 0) {
return;
}
// //
if (!item.selected) { if (!item.selected) {
item.selected = true; item.selected = true;
} }
// //
item.quantity = (item.quantity || 0) + 1; item.quantity = (item.quantity || 0) + 1;
this.autoSelectBestCoupon();
}, },
// //
decreaseQuantity(index) { decreaseQuantity(index) {
const item = this.menuList[index]; const item = this.menuList[index];
//
if (item && item.stock === 0) {
return;
}
const currentQuantity = item.quantity || 0; const currentQuantity = item.quantity || 0;
// 0 // 0
if (currentQuantity > 0) { if (currentQuantity > 0) {
@ -374,7 +300,6 @@ export default {
item.selected = false; item.selected = false;
} }
} }
this.autoSelectBestCoupon();
}, },
// 100 // 100
@ -388,94 +313,6 @@ export default {
return yuan.toFixed(2); 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() { async handleCheckout() {
if (this.totalAmount <= 0) { if (this.totalAmount <= 0) {
@ -495,21 +332,20 @@ export default {
return; return;
} }
// //
// voucherId:num;voucherId:num 2324:1;2325:2 // couponId:num;couponId:num 2324:1;2325:2
const voucherStr = selectedItems const couponStr = selectedItems
.map((item) => `${item.id}:${item.quantity}`) .map((item) => `${item.id}:${item.quantity}`)
.join(";"); .join(";");
const trxamtFen = this.payableAmountFen; // this.totalAmount ,
const trxamt = String(trxamtFen); const trxamt = (this.totalAmount * 100).toFixed(0);
console.log("购买信息: " + voucherStr); console.log("购买信息: " + couponStr);
console.log("金额:" + trxamt); console.log("金额:" + trxamt);
const res = await appBuy({ const res = await appBuy({
shopId: this.shopId, shopId: this.shopId,
voucherData: voucherStr, couponData: couponStr,
payableAmount: trxamt, payableAmount: trxamt,
couponId: this.selectedCouponId || undefined,
}); });
console.log(res); console.log(res);
if (!res) { if (!res) {
@ -519,15 +355,7 @@ export default {
}); });
} }
// res // res
uni.showModal({ this.handlePay(res);
title: "确认结算",
content: `将跳转至商户收款小程序完成结算。\n资金直接结算至${this.storeInfo.name || '商户'}(商户号:${res.tlPayCusid || '已配置'})。是否继续?`,
success: (mres) => {
if (mres.confirm) {
this.handlePay(res);
}
}
});
}, },
// //
async handlePay(order) { async handlePay(order) {
@ -535,43 +363,38 @@ export default {
// const reqsn = // const reqsn =
// "mini" + Date.now() + "" + Math.floor(Math.random() * 10000); // "mini" + Date.now() + "" + Math.floor(Math.random() * 10000);
const voucherArray = order.voucherPurchaseRespVOS || []; const couponArray = order.couponPurchaseRespVOS || [];
const voucherMap = new Map(); const couponMap = new Map();
voucherArray.forEach((item) => { couponArray.forEach((item) => {
const key = item.voucherId; const key = item.couponId;
if (!key) return; if (!key) return;
// Map // Map
const currentList = voucherMap.get(key) || []; const currentList = couponMap.get(key) || [];
currentList.push(item); currentList.push(item);
// Map // Map
voucherMap.set(key, currentList); couponMap.set(key, currentList);
}); });
let bodyStr = '' let bodyStr = ''
voucherMap.forEach((voucherList, voucherId) => { couponMap.forEach((couponList, couponId) => {
console.log(`代金券ID${voucherId}`); console.log(`优惠券ID${couponId}`);
console.log(`代金券列表:`, voucherList); console.log(`优惠券列表:`, couponList);
const name = voucherList[0] && voucherList[0].voucherName ? voucherList[0].voucherName : "商品"; const name = couponList[0] && couponList[0].couponName ? couponList[0].couponName : "商品";
bodyStr = bodyStr + name + 'x' + voucherList.length + ';'; bodyStr = bodyStr + name + 'x' + couponList.length + ';';
}); });
const randomstr = Math.floor(Math.random() * 10000000) + ""; const randomstr = Math.floor(Math.random() * 10000000) + "";
// "1" // "1"
if (order.payableAmount == null || order.payableAmount === ""){ const trxamt =
uni.showToast({ title: "结算金额异常", icon: "none" }); order.payableAmount != null && order.payableAmount !== ""
return; ? String(order.payableAmount)
} : "1";
if (order.tlPayCusid == null || order.tlPayCusid == ""){ //
uni.showToast({ title: "当前商户还未配置结算信息不可购买", icon: "none" });
return;
}
//
let params = { let params = {
appid: "00390105", // appid appid: "00390105", // appid
body: bodyStr, // body: bodyStr, //
cusid: order.tlPayCusid, // cusid: order.tlPayCusid, //
notify_url: notify_url:
"https://guangsh.manage.hschengtai.com/admin-api/member/lu-order/tlNotice", // "https://guangsh.manage.hschengtai.com/admin-api/member/lu-order/tlNotice", //
orgid: "56479107392N35H", orgid: "56479107392N35H",
@ -582,7 +405,7 @@ export default {
reqsn: order.orderNumber, reqsn: order.orderNumber,
sign: "", sign: "",
signtype: "RSA", signtype: "RSA",
trxamt: String(order.payableAmount), trxamt: "1",
version: "12", version: "12",
}; };
console.log(params) console.log(params)
@ -605,8 +428,8 @@ export default {
}, },
}); });
} catch (e) { } catch (e) {
console.error("获取签名失败:", e); console.error("获取支付签名失败:", e);
uni.showToast({ title: "结算准备失败,请稍后重试", icon: "none" }); uni.showToast({ title: "支付准备失败,请稍后重试", icon: "none" });
} }
}, },
}, },
@ -622,22 +445,6 @@ export default {
padding-bottom: 120rpx; // 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 { .store-header {
background-color: #ffffff; background-color: #ffffff;
@ -748,10 +555,6 @@ export default {
align-items: center; align-items: center;
position: relative; position: relative;
&.out-of-stock {
opacity: 0.6;
}
.checkbox { .checkbox {
width: 30rpx; width: 30rpx;
height: 30rpx; height: 30rpx;
@ -763,11 +566,6 @@ export default {
margin-right: 19rpx; margin-right: 19rpx;
flex-shrink: 0; flex-shrink: 0;
&.disabled {
border-color: #dddddd;
background-color: #f5f5f5;
}
&.checked { &.checked {
background-color: #004294; background-color: #004294;
border-color: #004294; border-color: #004294;
@ -872,12 +670,6 @@ export default {
font-weight: 500; font-weight: 500;
background-color: #ffffff; background-color: #ffffff;
border-radius: 4rpx; border-radius: 4rpx;
&.disabled {
color: #bbbbbb;
border-color: #dddddd;
background-color: #f5f5f5;
}
} }
.quantity-number { .quantity-number {
@ -890,11 +682,6 @@ export default {
} }
} }
} }
.stock-tip {
margin-top: 10rpx;
font-size: 20rpx;
color: #999999;
}
} }
} }
} }
@ -916,8 +703,6 @@ export default {
.total-info { .total-info {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
position: relative;
top: -15rpx;
.total-label { .total-label {
font-family: PingFang-SC, PingFang-SC; font-family: PingFang-SC, PingFang-SC;
@ -941,7 +726,6 @@ export default {
background: rgba(255, 107, 0, 0.1); background: rgba(255, 107, 0, 0.1);
padding: 4rpx 12rpx; padding: 4rpx 12rpx;
border-radius: 8rpx; border-radius: 8rpx;
margin-top: -4rpx;
.crown-icon { .crown-icon {
width: 23rpx; width: 23rpx;
@ -957,50 +741,6 @@ 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 { .checkout-btn {
color: #ffffff; color: #ffffff;
font-family: PingFang-SC, PingFang-SC; font-family: PingFang-SC, PingFang-SC;
@ -1014,4 +754,4 @@ export default {
} }
} }
</style> </style>

View File

@ -1,15 +1,10 @@
<template> <template>
<view class="home-page"> <view class="home-page">
<!-- 应用头部固定滑动时不动 --> <!-- 应用头部 -->
<view <view class="header" :style="{ paddingTop: statusBarHeight + 'px' }">
class="header header-fixed"
:style="{ paddingTop: statusBarHeight + 'px' }"
>
<image class="logo" src="/static/home/logo.png" mode="aspectFit"></image> <image class="logo" src="/static/home/logo.png" mode="aspectFit"></image>
</view> </view>
<!-- 主体内容预留头部高度避免被遮挡 -->
<view class="main-content" :style="{ paddingTop: headerHeight + 'px' }">
<!-- 招募宣传横幅 --> <!-- 招募宣传横幅 -->
<swiper <swiper
class="recruit-banner" class="recruit-banner"
@ -44,7 +39,8 @@
></image> ></image>
<text class="title-text">公会福利</text> <text class="title-text">公会福利</text>
</view> </view>
<view class="section-more" @click="handleViewAllBenefits" v-html="'查看全部 >'"> <view class="section-more" @click="handleViewAllBenefits">
查看全部 >
</view> </view>
</view> </view>
<view class="benefits-list"> <view class="benefits-list">
@ -71,7 +67,8 @@
></image> ></image>
<text class="title-text">公会活动</text> <text class="title-text">公会活动</text>
</view> </view>
<view class="section-more" @click="handleViewAllActivities" v-html="'查看全部 >'"> <view class="section-more" @click="handleViewAllActivities">
查看全部 >
</view> </view>
</view> </view>
<view class="activities-list"> <view class="activities-list">
@ -104,7 +101,6 @@
</view> </view>
</view> </view>
</view> </view>
</view>
</view> </view>
</template> </template>
@ -125,8 +121,6 @@ export default {
statusBarHeight: 0, statusBarHeight: 0,
// + // +
navBarHeight: 0, navBarHeight: 0,
// padding-top 72rpx 36px
headerContentHeight: 44,
bannerList: [], bannerList: [],
// //
benefitsList: [ benefitsList: [
@ -159,11 +153,6 @@ export default {
activitiesList: [], activitiesList: [],
}; };
}, },
computed: {
headerHeight() {
return this.statusBarHeight + this.headerContentHeight;
},
},
onLoad() { onLoad() {
this.getSystemInfo(); this.getSystemInfo();
this.loadHomeData(); this.loadHomeData();
@ -395,15 +384,6 @@ export default {
width: 306rpx; width: 306rpx;
height: 72rpx; height: 72rpx;
} }
&.header-fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background-color: #e2e8f1;
}
} }
/* 招募宣传横幅 */ /* 招募宣传横幅 */

View File

@ -1,9 +1,11 @@
<template> <template>
<view class="login-page"> <view class="login-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 状态栏占位 -->
<NavHeader title="登录" /> <!-- <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view> -->
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }"> <NavHeader title="登录" />
<!-- 登录内容区 -->
<view class="login-content"> <view class="login-content">
<!-- Logo区域 --> <!-- Logo区域 -->
<view class="logo-section"> <view class="logo-section">
@ -91,7 +93,6 @@
</view> </view>
</view> </view>
</view> </view>
</view>
</view> </view>
</template> </template>
@ -117,9 +118,6 @@ export default {
} }
}, },
computed: { computed: {
headerHeight() {
return this.statusBarHeight + 44
},
// //
canLogin() { canLogin() {
return ( return (
@ -144,17 +142,13 @@ export default {
return return
} }
// stateinviteCode scene // state inviteCode
if (options.state) { if (options.state) {
this.state = options.state this.state = options.state
} }
if (options.inviteCode) { if (options.inviteCode) {
this.inviteCode = 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() const pages = getCurrentPages()
@ -498,22 +492,6 @@ export default {
background: linear-gradient(180deg, #e2e8f1 0%, #ffffff 100%); 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 { .status-bar {
background-color: transparent; background-color: transparent;
} }

View File

@ -1,15 +1,9 @@
<template> <template>
<view class="profile-page"> <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> <text class="header-text">个人中心</text>
</view> </view>
<!-- 主体内容预留头部高度 -->
<view class="profile-main" :style="{ paddingTop: navBarHeight + 'px' }">
<!-- 用户资料卡片 --> <!-- 用户资料卡片 -->
<view <view
class="user-card" class="user-card"
@ -63,9 +57,6 @@
<text class="user-id">NO.{{ userInfo.id }}</text> <text class="user-id">NO.{{ userInfo.id }}</text>
</view> </view>
</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"> <view class="member-benefits-btn" @click="goToMemberBenefits">
<image :src="memberLevelIcon" mode="aspectFill"></image> <image :src="memberLevelIcon" mode="aspectFill"></image>
</view> </view>
@ -110,14 +101,7 @@
<text class="menu-text">投诉建议</text> <text class="menu-text">投诉建议</text>
<text class="menu-arrow"></text> <text class="menu-arrow"></text>
</view> </view>
<view class="menu-item" @tap="goToMyCoupons"> <view class="menu-item" @tap="goToPostMessage">
<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" <view class="menu-icon"
><image ><image
src="/static/profile/publish-message.png" src="/static/profile/publish-message.png"
@ -126,47 +110,18 @@
></view> ></view>
<text class="menu-text">发布消息</text> <text class="menu-text">发布消息</text>
<text class="menu-arrow"></text> <text class="menu-arrow"></text>
</view> --> </view>
</view> </view>
<!-- 退出登录按钮 --> <!-- 退出登录按钮 -->
<view class="logout-section"> <view class="logout-section">
<button class="logout-btn" @click="handleLogout">退</button> <button class="logout-btn" @click="handleLogout">退</button>
</view> </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> </view>
</template> </template>
<script> <script>
import { getUserInfo, getInviteQRCode } from "@/api/profile.js"; import { getUserInfo } from "@/api/profile.js";
export default { export default {
data() { data() {
@ -179,12 +134,6 @@ export default {
}, },
noticeNum: 3, noticeNum: 3,
uploading: false, // uploading: false, //
inviteModalVisible: false,
inviteQRCodeUrl: "",
inviteCodeStr: "", // -id 0-123
posterCanvasWidth: 680,
posterCanvasHeight: 860,
posterSaving: false,
}; };
}, },
computed: { computed: {
@ -285,185 +234,6 @@ 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() { goToMemberBenefits() {
uni.navigateTo({ uni.navigateTo({
@ -490,11 +260,6 @@ export default {
url: "/pages/activities/complaints", url: "/pages/activities/complaints",
}); });
}, },
goToMyCoupons() {
uni.navigateTo({
url: "/pages/profileSub/myCoupons",
});
},
goToPostMessage() { goToPostMessage() {
uni.navigateTo({ uni.navigateTo({
url: "/pages/activities/postMessage", url: "/pages/activities/postMessage",
@ -693,15 +458,6 @@ export default {
height: 88rpx; height: 88rpx;
line-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%);
}
} }
/* 用户资料卡片 */ /* 用户资料卡片 */
@ -809,18 +565,6 @@ export default {
} }
} }
.invite-btn {
position: absolute;
right: 255rpx;
bottom: 24rpx;
width: 175rpx;
height: 42rpx;
image {
width: 100%;
height: 100%;
}
}
.member-benefits-btn { .member-benefits-btn {
position: absolute; position: absolute;
right: 50rpx; right: 50rpx;
@ -958,153 +702,5 @@ export default {
align-items: center; align-items: center;
justify-content: 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> </style>

View File

@ -1,404 +0,0 @@
<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>

View File

@ -1,393 +0,0 @@
<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>

View File

@ -1,9 +1,7 @@
<template> <template>
<view class="rules-page"> <view class="rules-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <NavHeader title="积分会员制度" />
<NavHeader title="积分会员制度" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
<scroll-view class="content-scroll" scroll-y="true"> <scroll-view class="content-scroll" scroll-y="true">
<!-- 会员等级与积分门槛 --> <!-- 会员等级与积分门槛 -->
<view class="section"> <view class="section">
@ -69,7 +67,6 @@
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
</view>
</view> </view>
</template> </template>
@ -80,7 +77,6 @@ export default {
components: { NavHeader }, components: { NavHeader },
data() { data() {
return { return {
statusBarHeight: 0,
levelRows: [ levelRows: [
{ level: "初级", points: "0", name: "筑巢会员" }, { level: "初级", points: "0", name: "筑巢会员" },
{ level: "中级", points: "1000", name: "安居会员" }, { level: "中级", points: "1000", name: "安居会员" },
@ -116,49 +112,24 @@ export default {
], ],
}; };
}, },
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
},
onLoad() {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
},
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.rules-page { .rules-page {
height: 100vh; height: 100vh;
background: radial-gradient(0% 0% at 0% 0%, #ffffff 0%, #e2e8f1 100%); background-color: #ffffff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; 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 { .content-scroll {
flex: 1; flex: 1;
height: 0; height: 0;
padding: 20rpx 24rpx 40rpx; padding: 20rpx 24rpx 40rpx;
box-sizing: border-box; box-sizing: border-box;
background: radial-gradient(0% 0% at 0% 0%, #ffffff 0%, #e2e8f1 100%); background: #ffffff;
} }
.section { .section {

View File

@ -1,15 +1,11 @@
<template> <template>
<view class="privacy-page"> <view class="privacy-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <NavHeader title="隐私政策" />
<NavHeader title="隐私政策" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
<scroll-view class="content" scroll-y="true"> <scroll-view class="content" scroll-y="true">
<view class="privacy-content"> <view class="privacy-content">
<text class="privacy-text">{{ privacyText }}</text> <text class="privacy-text">{{ privacyText }}</text>
</view> </view>
</scroll-view> </scroll-view>
</view>
</view> </view>
</template> </template>
@ -22,7 +18,6 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
privacyText: `隐私政策 privacyText: `隐私政策
本隐私政策以下简称"本政策"由广厦千万间司机公会以下简称"我们"制定旨在说明我们通过运营的微信小程序以下简称"小程序"向您以下简称"用户"提供餐饮维修保险健康等服务过程中对用户个人信息的收集使用存储传输共享披露及保护等相关事宜您在使用我们的服务时即视为您已充分阅读理解并同意本政策的全部内容包括我们对本政策的后续更新如您不同意本政策请勿使用我们的服务 本隐私政策以下简称"本政策"由广厦千万间司机公会以下简称"我们"制定旨在说明我们通过运营的微信小程序以下简称"小程序"向您以下简称"用户"提供餐饮维修保险健康等服务过程中对用户个人信息的收集使用存储传输共享披露及保护等相关事宜您在使用我们的服务时即视为您已充分阅读理解并同意本政策的全部内容包括我们对本政策的后续更新如您不同意本政策请勿使用我们的服务
@ -35,13 +30,13 @@ export default {
为向您提供高质量的服务我们仅收集为实现服务目的所必需的个人信息具体包括 为向您提供高质量的服务我们仅收集为实现服务目的所必需的个人信息具体包括
1.基本身份信息包括您的姓名身份证号联系电话等用于完成身份核验建立服务关系 1.基本身份信息包括您的姓名身份证号联系电话等用于完成身份核验建立服务关系
2.车辆相关信息包括您的车牌号车辆行驶证信息等用于为您精准匹配维修保险等服务 2.车辆相关信息包括您的车牌号车辆行驶证信息等用于为您精准匹配维修保险等服务
3.服务相关信息包括您的餐饮偏好维修需求保险配置需求健康状况仅收集服务必需的部分服务订单记录费用结算记录评价反馈等用于处理订单优化服务体验 3.服务相关信息包括您的餐饮偏好维修需求保险配置需求健康状况仅收集服务必需的部分服务订单记录费用支付记录评价反馈等用于处理订单优化服务体验
4.其他必要信息为保障服务安全履行法律法规义务所必需的其他信息 4.其他必要信息为保障服务安全履行法律法规义务所必需的其他信息
收集方式 收集方式
1.您主动提供您在小程序注册账号提交服务申请填写资料购买服务发表评价时主动向我们提供的个人信息 1.您主动提供您在小程序注册账号提交服务申请填写资料购买服务发表评价时主动向我们提供的个人信息
2.服务过程获取在为您提供服务的过程中我们通过小程序系统自动记录的相关信息如服务使用记录结算记录等 2.服务过程获取在为您提供服务的过程中我们通过小程序系统自动记录的相关信息如服务使用记录支付记录等
3.第三方协助获取在取得您明确授权的前提下我们从合法的第三方机构如保险机构结算机构合作维修商家等获取的必要信息用于完成服务对接 3.第三方协助获取在取得您明确授权的前提下我们从合法的第三方机构如保险机构支付机构合作维修商家等获取的必要信息用于完成服务对接
4.其他合法方式依据法律法规规定或有权机关要求合法收集的相关信息 4.其他合法方式依据法律法规规定或有权机关要求合法收集的相关信息
个人信息的使用目的 个人信息的使用目的
@ -73,7 +68,7 @@ export default {
信息共享 信息共享
我们不会随意向第三方共享您的个人信息除非符合以下情形 我们不会随意向第三方共享您的个人信息除非符合以下情形
1.经您明确同意或授权我们将在您授权的范围内向您指定的第三方共享必要的个人信息 1.经您明确同意或授权我们将在您授权的范围内向您指定的第三方共享必要的个人信息
2.服务提供必需为向您提供约定的服务需向合作的第三方机构如保险机构维修商家结算机构等共享必要的个人信息且该第三方已签署保密协议承诺严格保护您的个人信息 2.服务提供必需为向您提供约定的服务需向合作的第三方机构如保险机构维修商家支付机构等共享必要的个人信息且该第三方已签署保密协议承诺严格保护您的个人信息
3.法律法规要求依据法律法规的规定司法机关或行政机关的合法要求向相关部门披露或共享个人信息 3.法律法规要求依据法律法规的规定司法机关或行政机关的合法要求向相关部门披露或共享个人信息
4.保护合法权益为保护我们的合法权益服务秩序或社会公共利益在合理必要的范围内共享个人信息 4.保护合法权益为保护我们的合法权益服务秩序或社会公共利益在合理必要的范围内共享个人信息
5.匿名化处理经过匿名化处理的信息因其已无法识别您的身份共享此类信息无需经过您的同意 5.匿名化处理经过匿名化处理的信息因其已无法识别您的身份共享此类信息无需经过您的同意
@ -107,16 +102,7 @@ export default {
广厦千万间司机公会` 广厦千万间司机公会`
}; };
}, }
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
},
onLoad() {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
},
}; };
</script> </script>
@ -128,22 +114,6 @@ export default {
flex-direction: column; 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 { .content {
flex: 1; flex: 1;
padding: 20rpx; padding: 20rpx;

View File

@ -1,25 +1,14 @@
<template> <template>
<view class="real-name-auth-page"> <view class="real-name-auth-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 头部区域 -->
<NavHeader title="实名认证" /> <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" v-else> <scroll-view class="form-content" scroll-y="true">
<!-- 基本信息 --> <!-- 基本信息 -->
<view class="form-section"> <view class="form-section">
<view class="section-title">基本信息</view> <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="form-item">
<view class="input-wrapper"> <view class="input-wrapper">
@ -30,7 +19,6 @@
v-model="formData.realName" v-model="formData.realName"
placeholder="请输入真实姓名" placeholder="请输入真实姓名"
maxlength="20" maxlength="20"
:disabled="!formEditable"
/> />
</view> </view>
</view> </view>
@ -40,7 +28,6 @@
<view class="input-wrapper"> <view class="input-wrapper">
<text class="input-label">证件类型</text> <text class="input-label">证件类型</text>
<picker <picker
v-if="formEditable"
mode="selector" mode="selector"
:range="idTypeOptions" :range="idTypeOptions"
range-key="label" range-key="label"
@ -53,7 +40,6 @@
</text> </text>
</view> </view>
</picker> </picker>
<text v-else class="picker-text readonly">{{ idTypeLabel }}</text>
</view> </view>
</view> </view>
@ -67,7 +53,6 @@
v-model="formData.idNumber" v-model="formData.idNumber"
placeholder="请输入证件号码" placeholder="请输入证件号码"
maxlength="30" maxlength="30"
:disabled="!formEditable"
/> />
</view> </view>
</view> </view>
@ -81,7 +66,7 @@
<view class="form-item"> <view class="form-item">
<view class="upload-wrapper"> <view class="upload-wrapper">
<text class="upload-label">身份证正面</text> <text class="upload-label">身份证正面</text>
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('idFrontImg')"> <view class="upload-box" @click="chooseImage('idFrontImg')">
<image <image
v-if="formData.idFrontImg" v-if="formData.idFrontImg"
class="uploaded-image" class="uploaded-image"
@ -100,7 +85,7 @@
<view class="form-item"> <view class="form-item">
<view class="upload-wrapper"> <view class="upload-wrapper">
<text class="upload-label">身份证背面</text> <text class="upload-label">身份证背面</text>
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('idBackImg')"> <view class="upload-box" @click="chooseImage('idBackImg')">
<image <image
v-if="formData.idBackImg" v-if="formData.idBackImg"
class="uploaded-image" class="uploaded-image"
@ -124,7 +109,7 @@
<view class="form-item"> <view class="form-item">
<view class="upload-wrapper"> <view class="upload-wrapper">
<text class="upload-label">驾驶证正面</text> <text class="upload-label">驾驶证正面</text>
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('driverLicenseFrontImg')"> <view class="upload-box" @click="chooseImage('driverLicenseFrontImg')">
<image <image
v-if="formData.driverLicenseFrontImg" v-if="formData.driverLicenseFrontImg"
class="uploaded-image" class="uploaded-image"
@ -143,7 +128,7 @@
<view class="form-item"> <view class="form-item">
<view class="upload-wrapper"> <view class="upload-wrapper">
<text class="upload-label">驾驶证背面</text> <text class="upload-label">驾驶证背面</text>
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('driverLicenseBackImg')"> <view class="upload-box" @click="chooseImage('driverLicenseBackImg')">
<image <image
v-if="formData.driverLicenseBackImg" v-if="formData.driverLicenseBackImg"
class="uploaded-image" class="uploaded-image"
@ -167,7 +152,7 @@
<view class="form-item"> <view class="form-item">
<view class="upload-wrapper"> <view class="upload-wrapper">
<text class="upload-label">行驶证正面</text> <text class="upload-label">行驶证正面</text>
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('vehicleLicenseFrontImg')"> <view class="upload-box" @click="chooseImage('vehicleLicenseFrontImg')">
<image <image
v-if="formData.vehicleLicenseFrontImg" v-if="formData.vehicleLicenseFrontImg"
class="uploaded-image" class="uploaded-image"
@ -186,7 +171,7 @@
<view class="form-item"> <view class="form-item">
<view class="upload-wrapper"> <view class="upload-wrapper">
<text class="upload-label">行驶证背面</text> <text class="upload-label">行驶证背面</text>
<view class="upload-box" :class="{ disabled: !formEditable }" @click="formEditable && chooseImage('vehicleLicenseBackImg')"> <view class="upload-box" @click="chooseImage('vehicleLicenseBackImg')">
<image <image
v-if="formData.vehicleLicenseBackImg" v-if="formData.vehicleLicenseBackImg"
class="uploaded-image" class="uploaded-image"
@ -214,14 +199,13 @@
placeholder="请输入备注信息(选填)" placeholder="请输入备注信息(选填)"
maxlength="200" maxlength="200"
:auto-height="true" :auto-height="true"
:disabled="!formEditable"
></textarea> ></textarea>
</view> </view>
</view> </view>
</view> </view>
<!-- 协议同意区域仅可编辑时显示 --> <!-- 协议同意区域 -->
<view class="agreement-section" v-if="formEditable"> <view class="agreement-section">
<view class="agreement-checkbox" @click="toggleAgreement"> <view class="agreement-checkbox" @click="toggleAgreement">
<view class="checkbox-icon" :class="{ checked: agreedToTerms }"> <view class="checkbox-icon" :class="{ checked: agreedToTerms }">
<text v-if="agreedToTerms" class="checkmark"></text> <text v-if="agreedToTerms" class="checkmark"></text>
@ -235,8 +219,8 @@
</view> </view>
</view> </view>
<!-- 提交按钮仅可编辑时显示 --> <!-- 提交按钮 -->
<view class="submit-section" v-if="formEditable"> <view class="submit-section">
<button <button
class="submit-btn" class="submit-btn"
:class="{ disabled: loading || !agreedToTerms }" :class="{ disabled: loading || !agreedToTerms }"
@ -247,12 +231,11 @@
</button> </button>
</view> </view>
</scroll-view> </scroll-view>
</view>
</view> </view>
</template> </template>
<script> <script>
import { createRealNameInfo, getRealNameInfo } from '@/api/profile.js' import { createRealNameInfo } from '@/api/profile.js'
import NavHeader from "@/components/NavHeader/NavHeader.vue"; import NavHeader from "@/components/NavHeader/NavHeader.vue";
export default { export default {
@ -261,11 +244,8 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
loading: false, loading: false,
realNameLoading: true, // agreedToTerms: false, //
realNameStatus: null, // 0 / 1 2 3 退 4
agreedToTerms: false, //
idTypeIndex: 0, idTypeIndex: 0,
idTypeOptions: [ idTypeOptions: [
{ label: '身份证', value: 1 }, { label: '身份证', value: 1 },
@ -285,79 +265,9 @@ 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() { onLoad() {
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
this.loadRealNameInfo()
}, },
methods: { 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) { handleIdTypeChange(e) {
@ -520,34 +430,6 @@ export default {
background: #e2e8f1; 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 { .form-content {
height: calc(100vh - 120rpx); height: calc(100vh - 120rpx);
padding: 0 30rpx 40rpx; padding: 0 30rpx 40rpx;
@ -564,17 +446,6 @@ export default {
margin-bottom: 24rpx; margin-bottom: 24rpx;
padding-left: 10rpx; 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 { .form-item {
@ -616,10 +487,6 @@ export default {
&.placeholder { &.placeholder {
color: #999999; color: #999999;
} }
&.readonly {
color: #666666;
}
} }
.picker-arrow { .picker-arrow {
@ -679,11 +546,6 @@ export default {
color: #999999; color: #999999;
} }
} }
&.disabled {
pointer-events: none;
opacity: 0.85;
}
} }
} }

View File

@ -1,9 +1,8 @@
<template> <template>
<view class="service-records-page"> <view class="service-records-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 头部区域 -->
<NavHeader title="服务记录" /> <NavHeader title="服务记录" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
<!-- Tab 切换 --> <!-- Tab 切换 -->
<view class="tab-bar"> <view class="tab-bar">
<view <view
@ -11,21 +10,21 @@
:class="{ active: currentTab === 'pending_payment' }" :class="{ active: currentTab === 'pending_payment' }"
@click="switchTab('pending_payment')" @click="switchTab('pending_payment')"
> >
<text class="tab-text">结算</text> <text class="tab-text">支付</text>
</view> </view>
<view <view
class="tab-item" class="tab-item"
:class="{ active: currentTab === 'pending_verification' }" :class="{ active: currentTab === 'pending_verification' }"
@click="switchTab('pending_verification')" @click="switchTab('pending_verification')"
> >
<text class="tab-text">待核销</text> <text class="tab-text">已完成</text>
</view> </view>
<view <view
class="tab-item" class="tab-item"
:class="{ active: currentTab === 'completed' }" :class="{ active: currentTab === 'chargeback' }"
@click="switchTab('completed')" @click="switchTab('chargeback')"
> >
<text class="tab-text">完成</text> <text class="tab-text">退款</text>
</view> </view>
<view <view
class="tab-item" class="tab-item"
@ -78,13 +77,13 @@
<view <view
class="record-goods" class="record-goods"
v-for="(goods, gIndex) in getGoodsList(item)" v-for="(goods, gIndex) in getGoodsList(item)"
:key="goods.id || goods.voucherId || gIndex" :key="goods.id || goods.couponId || gIndex"
> >
<image <image
class="goods-image" class="goods-image"
:src=" :src="
goods.coverUrl || goods.coverUrl ||
goods.voucherCoverUrl || goods.couponCoverUrl ||
goods.picUrl || goods.picUrl ||
'/static/home/entry_icon.png' '/static/home/entry_icon.png'
" "
@ -92,7 +91,7 @@
></image> ></image>
<view class="goods-info"> <view class="goods-info">
<text class="goods-title">{{ <text class="goods-title">{{
goods.voucherName || goods.name goods.couponName || goods.name
}}</text> }}</text>
<!-- <text class="goods-subtitle"> <!-- <text class="goods-subtitle">
订单号{{ item.orderNumber }} 订单号{{ item.orderNumber }}
@ -120,7 +119,6 @@
src="/static/home/qr_code_icon.png" src="/static/home/qr_code_icon.png"
mode="aspectFill" mode="aspectFill"
class="qr-code-icon" class="qr-code-icon"
:class="{ gray: item.status === 4 }"
></image> ></image>
</view> </view>
</view> </view>
@ -144,7 +142,7 @@
取消订单 取消订单
</button> </button>
<button class="action-btn pay-btn" @click.stop="handlePay(item)"> <button class="action-btn pay-btn" @click.stop="handlePay(item)">
去结算 立即支付
</button> </button>
</view> </view>
<view class="record-actions" v-else-if="item.status === 1"> <view class="record-actions" v-else-if="item.status === 1">
@ -207,19 +205,18 @@
<view class="qr-modal-close" @click="closeQrModal">×</view> <view class="qr-modal-close" @click="closeQrModal">×</view>
</view> </view>
<view class="qr-modal-body"> <view class="qr-modal-body">
<view class="qr-code-box" v-if="qrModalData.voucherCode"> <view class="qr-code-box" v-if="qrModalData.couponCode">
<image <image
class="qr-code-image" class="qr-code-image"
:src="qrCodeImageUrl" :src="qrCodeImageUrl"
mode="aspectFit" mode="aspectFit"
></image> ></image>
<!-- <text class="qr-code-text">{{ qrModalData.voucherCode }}</text> --> <!-- <text class="qr-code-text">{{ qrModalData.couponCode }}</text> -->
</view> </view>
<text v-else class="qr-code-empty">暂无券码</text> <text v-else class="qr-code-empty">暂无券码</text>
</view> </view>
</view> </view>
</view> </view>
</view>
</view> </view>
</template> </template>
@ -233,7 +230,6 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
currentTab: "pending_payment", // tab currentTab: "pending_payment", // tab
refreshing: false, refreshing: false,
loading: false, loading: false,
@ -251,35 +247,31 @@ export default {
qrModalVisible: false, qrModalVisible: false,
qrModalData: { qrModalData: {
useStatus: 0, useStatus: 0,
voucherCode: "", couponCode: "",
}, },
}; };
}, },
computed: { computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
currentList() { currentList() {
return this.recordsMap[this.currentTab] || []; return this.recordsMap[this.currentTab] || [];
}, },
qrCodeImageUrl() { qrCodeImageUrl() {
const code = this.qrModalData.voucherCode || ""; const code = this.qrModalData.couponCode || "";
if (!code) return ""; if (!code) return "";
return `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(code)}`; return `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(code)}`;
}, },
}, },
onLoad(options) { onLoad(options) {
const systemInfo = uni.getSystemInfoSync(); // tab使 tab
this.statusBarHeight = systemInfo.statusBarHeight || 0;
if (options && options.tab) { if (options && options.tab) {
this.currentTab = options.tab; this.currentTab = options.tab;
} }
this.loadData(); this.loadData();
}, },
methods: { methods: {
// voucherPurchaseRespVOS // couponPurchaseRespVOS
getGoodsList(order) { getGoodsList(order) {
const list = (order && order.voucherPurchaseRespVOS) || []; const list = (order && order.couponPurchaseRespVOS) || [];
return Array.isArray(list) && list.length ? list : [order || {}]; return Array.isArray(list) && list.length ? list : [order || {}];
}, },
// //
@ -306,7 +298,7 @@ export default {
// tab status // tab status
getStatusValue() { getStatusValue() {
const map = { const map = {
pending_payment: 0, // pending_payment: 0, //
pending_verification: 1, // pending_verification: 1, //
chargeback: 3, // 退 chargeback: 3, // 退
cancelled: 4, // cancelled: 4, //
@ -324,7 +316,7 @@ export default {
// Tab // Tab
getTabLabel() { getTabLabel() {
const labels = { const labels = {
pending_payment: "待结算", pending_payment: "待支付",
pending_verification: "待核销", pending_verification: "待核销",
completed: "已完成", completed: "已完成",
cancelled: "已取消", cancelled: "已取消",
@ -334,7 +326,7 @@ export default {
// //
getStatusText(status) { getStatusText(status) {
return status === 0 return status === 0
? "待结算" ? "待支付"
: status === 1 : status === 1
? "已完成" ? "已完成"
: status === 3 : status === 3
@ -459,30 +451,30 @@ export default {
}, },
}); });
}, },
// handlePay // handlePay
async handlePay(item) { async handlePay(item) {
if (!item || !item.orderNumber) { if (!item || !item.orderNumber) {
uni.showToast({ title: "订单信息异常", icon: "none" }); uni.showToast({ title: "订单信息异常", icon: "none" });
return; return;
} }
const voucherArray = item.voucherPurchaseRespVOS || []; const couponArray = item.couponPurchaseRespVOS || [];
const voucherMap = new Map(); const couponMap = new Map();
voucherArray.forEach((goods) => { couponArray.forEach((goods) => {
const key = goods.voucherId; const key = goods.couponId;
if (!key) return; if (!key) return;
const currentList = voucherMap.get(key) || []; const currentList = couponMap.get(key) || [];
currentList.push(goods); currentList.push(goods);
voucherMap.set(key, currentList); couponMap.set(key, currentList);
}); });
let bodyStr = ""; let bodyStr = "";
voucherMap.forEach((voucherList) => { couponMap.forEach((couponList) => {
bodyStr = bodyStr =
bodyStr + bodyStr +
(voucherList[0] && voucherList[0].voucherName (couponList[0] && couponList[0].couponName
? voucherList[0].voucherName ? couponList[0].couponName
: "商品") + : "商品") +
"x" + "x" +
voucherList.length + couponList.length +
";"; ";";
}); });
@ -528,8 +520,8 @@ export default {
}, },
}); });
} catch (e) { } catch (e) {
console.error("获取签名失败:", e); console.error("获取支付签名失败:", e);
uni.showToast({ title: "结算准备失败,请稍后重试", icon: "none" }); uni.showToast({ title: "支付准备失败,请稍后重试", icon: "none" });
} }
}, },
// //
@ -546,15 +538,15 @@ export default {
icon: "none", icon: "none",
}); });
}, },
// + voucherCode // + couponCode
handleQrCode(item, goods) { handleQrCode(item, goods) {
const useStatus = goods?.useStatus ?? item?.useStatus ?? 0; const useStatus = goods?.useStatus ?? item?.useStatus ?? 0;
const voucherCode = const couponCode =
(goods && (goods.voucherCode || goods.voucherNo)) || (goods && (goods.couponCode || goods.couponNo)) ||
item?.voucherCode || item?.couponCode ||
item?.voucherNo || item?.couponNo ||
""; "";
this.qrModalData = { useStatus, voucherCode }; this.qrModalData = { useStatus, couponCode };
this.qrModalVisible = true; this.qrModalVisible = true;
}, },
getQrUseStatusText(useStatus) { getQrUseStatusText(useStatus) {
@ -585,22 +577,6 @@ export default {
flex-direction: column; 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 切换栏 */
.tab-bar { .tab-bar {
display: flex; display: flex;
@ -867,11 +843,6 @@ export default {
.qr-code-icon { .qr-code-icon {
width: 44rpx; width: 44rpx;
height: 44rpx; height: 44rpx;
&.gray {
filter: grayscale(100%);
opacity: 0.6;
}
} }
} }
} }

View File

@ -1,15 +1,11 @@
<template> <template>
<view class="agreement-page"> <view class="agreement-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <NavHeader title="用户服务协议" />
<NavHeader title="用户服务协议" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
<scroll-view class="content" scroll-y="true"> <scroll-view class="content" scroll-y="true">
<view class="agreement-content"> <view class="agreement-content">
<text class="agreement-text">{{ agreementText }}</text> <text class="agreement-text">{{ agreementText }}</text>
</view> </view>
</scroll-view> </scroll-view>
</view>
</view> </view>
</template> </template>
@ -22,7 +18,6 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
agreementText: `用户服务协议 agreementText: `用户服务协议
鉴于甲方运营微信小程序以下简称"小程序"为乙方提供餐饮维修保险健康等专属收费服务乙方自愿使用甲方提供的服务为明确双方权利义务根据中华人民共和国民法典中华人民共和国个人信息保护法等相关法律法规甲乙双方本着平等自愿公平诚实信用的原则达成如下协议以资共同遵守 鉴于甲方运营微信小程序以下简称"小程序"为乙方提供餐饮维修保险健康等专属收费服务乙方自愿使用甲方提供的服务为明确双方权利义务根据中华人民共和国民法典中华人民共和国个人信息保护法等相关法律法规甲乙双方本着平等自愿公平诚实信用的原则达成如下协议以资共同遵守
@ -35,11 +30,11 @@ export default {
4.健康服务包括但不限于司机专属健康体检套餐健康咨询职业病预防指导等收费服务 4.健康服务包括但不限于司机专属健康体检套餐健康咨询职业病预防指导等收费服务
1.2 甲方有权根据市场需求业务发展等情况调整服务内容及收费项目相关调整将通过小程序公告或短信提前通知乙方乙方继续使用服务的视为认可调整后的内容 1.2 甲方有权根据市场需求业务发展等情况调整服务内容及收费项目相关调整将通过小程序公告或短信提前通知乙方乙方继续使用服务的视为认可调整后的内容
服务费用与结算 服务费用与支付
2.1 收费标准本协议项下服务均为收费服务具体收费标准计费方式服务明细均在小程序对应服务页面明确公示乙方可自行查看并选择购买 2.1 收费标准本协议项下服务均为收费服务具体收费标准计费方式服务明细均在小程序对应服务页面明确公示乙方可自行查看并选择购买
2.2 价格调整甲方可根据市场行情服务成本等因素调整收费标准调整后将通过小程序公告提前7个自然日公示公示期满后生效 2.2 价格调整甲方可根据市场行情服务成本等因素调整收费标准调整后将通过小程序公告提前7个自然日公示公示期满后生效
2.3 结算方式乙方应通过小程序内指定渠道结算费用结算成功后视为订单确认甲方依订单约定提供服务 2.3 支付方式乙方应通过小程序内指定支付渠道如微信支付支付费用支付成功后视为订单确认甲方依订单约定提供服务
2.4 套餐续费套餐类周期类服务需一次性结算对应周期费用乙方应在服务期满前按小程序提示续费逾期未续费的服务自动终止 2.4 套餐续费套餐类周期类服务需一次性支付对应周期费用乙方应在服务期满前按小程序提示续费逾期未续费的服务自动终止
费用退还 费用退还
3.1 因甲方原因如无法提供约定服务服务存在重大瑕疵无法补救导致服务无法履行的甲方应根据乙方未使用的服务内容或时长退还相应费用 3.1 因甲方原因如无法提供约定服务服务存在重大瑕疵无法补救导致服务无法履行的甲方应根据乙方未使用的服务内容或时长退还相应费用
@ -50,14 +45,14 @@ export default {
4.1 乙方权利 4.1 乙方权利
1.要求甲方按协议及订单约定提供符合标准的服务 1.要求甲方按协议及订单约定提供符合标准的服务
2.监督服务质量对服务问题提出改进建议 2.监督服务质量对服务问题提出改进建议
3.查询更正本人的个人信息及服务订单结算记录 3.查询更正本人的个人信息及服务订单支付记录
4.符合退款条件时申请退还相应服务费用 4.符合退款条件时申请退还相应服务费用
5.按约定申请终止本协议 5.按约定申请终止本协议
4.2 乙方义务 4.2 乙方义务
1.向甲方提供真实准确完整的个人信息及相关资料不得提供虚假信息或隐瞒重要事实 1.向甲方提供真实准确完整的个人信息及相关资料不得提供虚假信息或隐瞒重要事实
2.遵守国家法律法规及甲方发布的服务规则不得利用服务从事违法违规损害他人合法权益的活动 2.遵守国家法律法规及甲方发布的服务规则不得利用服务从事违法违规损害他人合法权益的活动
3.妥善保管小程序账号密码等身份信息对账号下所有操作行为承担责任发现账号泄露被盗用应及时通知甲方 3.妥善保管小程序账号密码等身份信息对账号下所有操作行为承担责任发现账号泄露被盗用应及时通知甲方
4.按协议及小程序公示标准及时足额结算服务费用 4.按协议及小程序公示标准及时足额支付服务费用
5.配合甲方完成服务必需的信息核验服务对接等工作 5.配合甲方完成服务必需的信息核验服务对接等工作
甲方权利与义务 甲方权利与义务
@ -85,14 +80,14 @@ export default {
1.提供虚假信息伪造证明材料影响服务开展的 1.提供虚假信息伪造证明材料影响服务开展的
2.违反协议或甲方服务规则经通知后限期未改正的 2.违反协议或甲方服务规则经通知后限期未改正的
3.利用服务从事违法违规活动的 3.利用服务从事违法违规活动的
4.逾期结算费用超过15日经催告仍未结算 4.逾期支付费用超过15日经催告仍未支付
5.其他严重损害甲方合法权益的行为 5.其他严重损害甲方合法权益的行为
7.4 因不可抗力政策调整等不可归责于双方的原因导致协议无法履行的协议自动终止双方互不担责甲方退还乙方未使用部分的服务费用 7.4 因不可抗力政策调整等不可归责于双方的原因导致协议无法履行的协议自动终止双方互不担责甲方退还乙方未使用部分的服务费用
违约责任 违约责任
8.1 任何一方违反协议约定给对方造成损失的应承担全部赔偿责任包括直接损失维权产生的律师费诉讼费等合理费用 8.1 任何一方违反协议约定给对方造成损失的应承担全部赔偿责任包括直接损失维权产生的律师费诉讼费等合理费用
8.2 甲方未按约定提供服务的除退还相应费用外还应按乙方已结算对应服务费用的10%承担违约金违约金不足以弥补损失的补足差额 8.2 甲方未按约定提供服务的除退还相应费用外还应按乙方已支付对应服务费用的10%支付违约金违约金不足以弥补损失的补足差额
8.3 乙方未按时结算费用的每逾期一日按逾期金额的0.5%违约金逾期超过15日的甲方有权终止协议并要求乙方赔偿损失 8.3 乙方未按时支付费用的每逾期一日按逾期金额的0.5%支付违约金逾期超过15日的甲方有权终止协议并要求乙方赔偿损失
8.4 乙方利用服务从事违法违规活动或损害第三方权益的法律责任由乙方自行承担给甲方造成损失的乙方全额赔偿 8.4 乙方利用服务从事违法违规活动或损害第三方权益的法律责任由乙方自行承担给甲方造成损失的乙方全额赔偿
争议解决 争议解决
@ -103,16 +98,7 @@ export default {
10.2 甲方通过小程序公告短信发送的通知公示自发布或送达之日起生效视为已履行告知义务乙方应及时关注 10.2 甲方通过小程序公告短信发送的通知公示自发布或送达之日起生效视为已履行告知义务乙方应及时关注
10.3 本协议条款被认定为无效或不可执行的不影响其他条款的效力` 10.3 本协议条款被认定为无效或不可执行的不影响其他条款的效力`
}; };
}, }
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
},
onLoad() {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
},
}; };
</script> </script>
@ -124,22 +110,6 @@ export default {
flex-direction: column; 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 { .content {
flex: 1; flex: 1;
padding: 20rpx; padding: 20rpx;

View File

@ -1,9 +1,9 @@
<template> <template>
<view class="webview-page"> <view class="webview-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }"> <!-- 头部导航栏 -->
<NavHeader title="网页" /> <NavHeader title="网页" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }"> <!-- WebView 容器 -->
<view class="webview-container"> <view class="webview-container">
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP-WEIXIN -->
<web-view :src="webviewUrl"></web-view> <web-view :src="webviewUrl"></web-view>
@ -15,7 +15,6 @@
</view> </view>
<!-- #endif --> <!-- #endif -->
</view> </view>
</view>
</view> </view>
</template> </template>
@ -28,18 +27,10 @@ export default {
}, },
data() { data() {
return { return {
statusBarHeight: 0,
webviewUrl: '' webviewUrl: ''
} }
}, },
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
},
onLoad(options) { onLoad(options) {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
// URL // URL
if (options.url) { if (options.url) {
this.webviewUrl = decodeURIComponent(options.url); this.webviewUrl = decodeURIComponent(options.url);
@ -65,22 +56,6 @@ export default {
overflow: hidden; 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 { .webview-container {
flex: 1; flex: 1;
height: 0; // flex: 1 使 height: 0; // flex: 1 使

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB