页面优化

This commit is contained in:
18631081161 2026-03-14 23:38:46 +08:00
parent 81f0a4e10d
commit 6488aa837d
45 changed files with 2762 additions and 1413 deletions

View File

@ -106,6 +106,35 @@
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 页面顶图 -->
<el-tab-pane label="页面顶图" name="page_banners">
<el-form label-width="100px" style="max-width: 600px;">
<el-form-item v-for="item in pageBannerList" :key="item.key" :label="item.label">
<el-upload
action="/api/upload/image"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="(res) => pageBanners[item.key] = res.url"
accept="image/*"
>
<el-image
v-if="pageBanners[item.key]"
:src="pageBanners[item.key]"
style="width: 300px; height: 100px; cursor: pointer; border-radius: 8px;"
fit="cover"
/>
<el-button v-else size="small" type="primary">选择图片</el-button>
</el-upload>
<div v-if="pageBanners[item.key]" style="margin-top: 4px;">
<el-button size="small" text type="danger" @click="pageBanners[item.key] = ''">移除</el-button>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="savePageBanners">保存全部顶图</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</div>
</template>
@ -126,6 +155,24 @@ const commissionRules = ref([])
const configs = reactive({ qrcode: '', agreement: '', privacy: '', withdrawal_guide: '' })
const freezeDays = ref(1)
//
const pageBannerList = [
{ key: 'page_banner_pickup', label: '代取' },
{ key: 'page_banner_delivery', label: '代送' },
{ key: 'page_banner_help', label: '万能帮' },
{ key: 'page_banner_purchase', label: '代购' },
{ key: 'page_banner_food', label: '美食街' },
{ key: 'page_banner_order-hall', label: '接单页' }
]
const pageBanners = reactive({
page_banner_pickup: '',
page_banner_delivery: '',
page_banner_help: '',
page_banner_purchase: '',
page_banner_food: '',
'page_banner_order-hall': ''
})
function addRule() {
commissionRules.value.push({ minAmount: 0, maxAmount: 0, rateType: 0, rate: 0 })
}
@ -148,7 +195,7 @@ async function saveCommissionRules() {
async function fetchConfig(key) {
try {
const res = await request.get(`/config/${key}`)
const res = await request.get(`/admin/config/${key}`)
configs[key] = res.value || ''
} catch { /* 忽略 */ }
}
@ -180,6 +227,27 @@ async function saveFreezeDays() {
}
}
async function fetchPageBanners() {
for (const item of pageBannerList) {
try {
const res = await request.get(`/admin/config/${item.key}`)
pageBanners[item.key] = res.value || ''
} catch { /* 忽略 */ }
}
}
async function savePageBanners() {
saving.value = true
try {
for (const item of pageBannerList) {
await request.put(`/admin/config/${item.key}`, { value: pageBanners[item.key] })
}
ElMessage.success('页面顶图保存成功')
} finally {
saving.value = false
}
}
onMounted(async () => {
await fetchCommissionRules()
await Promise.all([
@ -187,7 +255,8 @@ onMounted(async () => {
fetchConfig('agreement'),
fetchConfig('privacy'),
fetchConfig('withdrawal_guide'),
fetchFreezeDays()
fetchFreezeDays(),
fetchPageBanners()
])
})
</script>

View File

@ -50,7 +50,7 @@
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"appid" : "wx626fd4a47944e3ea",
"setting" : {
"urlCheck" : false
},

View File

@ -9,157 +9,183 @@
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录"
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/order-hall/order-hall",
"style": {
"navigationBarTitleText": "订单大厅"
"navigationBarTitleText": "接单",
"navigationStyle": "custom"
}
},
{
"path": "pages/message/message",
"style": {
"navigationBarTitleText": "消息"
"navigationBarTitleText": "消息",
"navigationStyle": "custom"
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的"
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/pickup/pickup",
"style": {
"navigationBarTitleText": "代取"
"navigationBarTitleText": "代取",
"navigationStyle": "custom"
}
},
{
"path": "pages/delivery/delivery",
"style": {
"navigationBarTitleText": "代送"
"navigationBarTitleText": "代送",
"navigationStyle": "custom"
}
},
{
"path": "pages/help/help",
"style": {
"navigationBarTitleText": "万能帮"
"navigationBarTitleText": "万能帮",
"navigationStyle": "custom"
}
},
{
"path": "pages/purchase/purchase",
"style": {
"navigationBarTitleText": "代购"
"navigationBarTitleText": "代购",
"navigationStyle": "custom"
}
},
{
"path": "pages/food/food",
"style": {
"navigationBarTitleText": "美食街"
"navigationBarTitleText": "美食街",
"navigationStyle": "custom"
}
},
{
"path": "pages/food/shop-detail",
"style": {
"navigationBarTitleText": "门店详情"
"navigationBarTitleText": "门店详情",
"navigationStyle": "custom"
}
},
{
"path": "pages/food/food-order",
"style": {
"navigationBarTitleText": "美食街订单"
"navigationBarTitleText": "美食街订单",
"navigationStyle": "custom"
}
},
{
"path": "pages/food/food-order-detail",
"style": {
"navigationBarTitleText": "美食街订单详情"
"navigationBarTitleText": "美食街订单详情",
"navigationStyle": "custom"
}
},
{
"path": "pages/order/order-detail",
"style": {
"navigationBarTitleText": "订单详情"
"navigationBarTitleText": "订单详情",
"navigationStyle": "custom"
}
},
{
"path": "pages/order/my-orders",
"style": {
"navigationBarTitleText": "我的订单"
"navigationBarTitleText": "我的订单",
"navigationStyle": "custom"
}
},
{
"path": "pages/order/my-taken",
"style": {
"navigationBarTitleText": "我的接单"
"navigationBarTitleText": "我的接单",
"navigationStyle": "custom"
}
},
{
"path": "pages/order/complete-order",
"style": {
"navigationBarTitleText": "完成订单确认"
"navigationBarTitleText": "完成订单确认",
"navigationStyle": "custom"
}
},
{
"path": "pages/message/system-msg",
"style": {
"navigationBarTitleText": "系统消息"
"navigationBarTitleText": "系统消息",
"navigationStyle": "custom"
}
},
{
"path": "pages/message/order-notify",
"style": {
"navigationBarTitleText": "订单通知"
"navigationBarTitleText": "订单通知",
"navigationStyle": "custom"
}
},
{
"path": "pages/message/chat",
"style": {
"navigationBarTitleText": "聊天"
"navigationBarTitleText": "聊天",
"navigationStyle": "custom"
}
},
{
"path": "pages/mine/earnings",
"style": {
"navigationBarTitleText": "我的收益"
"navigationBarTitleText": "我的收益",
"navigationStyle": "custom"
}
},
{
"path": "pages/mine/earnings-record",
"style": {
"navigationBarTitleText": "收益记录"
"navigationBarTitleText": "收益记录",
"navigationStyle": "custom"
}
},
{
"path": "pages/runner/certification",
"style": {
"navigationBarTitleText": "跑腿认证"
"navigationBarTitleText": "跑腿认证",
"navigationStyle": "custom"
}
},
{
"path": "pages/config/agreement",
"style": {
"navigationBarTitleText": "用户协议"
"navigationBarTitleText": "用户协议",
"navigationStyle": "custom"
}
},
{
"path": "pages/config/privacy",
"style": {
"navigationBarTitleText": "隐私政策"
"navigationBarTitleText": "隐私政策",
"navigationStyle": "custom"
}
},
{
"path": "pages/config/qrcode",
"style": {
"navigationBarTitleText": "客服二维码"
"navigationBarTitleText": "客服二维码",
"navigationStyle": "custom"
}
},
{
"path": "pages/webview/webview",
"style": {
"navigationBarTitleText": ""
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
}
],

View File

@ -1,5 +1,16 @@
<template>
<view class="agreement-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">用户协议</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<rich-text v-if="content" :nodes="content" class="rich-content"></rich-text>
<view v-else class="empty-tip">
<text>暂无内容</text>
@ -13,13 +24,17 @@ import { getAgreement } from '../../utils/api'
export default {
data() {
return {
content: ''
content: '',
statusBarHeight: 0
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadContent()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载用户协议内容 */
async loadContent() {
try {
@ -34,6 +49,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.agreement-page {
min-height: 100vh;
background-color: #ffffff;

View File

@ -1,5 +1,16 @@
<template>
<view class="privacy-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">隐私政策</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<rich-text v-if="content" :nodes="content" class="rich-content"></rich-text>
<view v-else class="empty-tip">
<text>暂无内容</text>
@ -13,13 +24,17 @@ import { getPrivacy } from '../../utils/api'
export default {
data() {
return {
content: ''
content: '',
statusBarHeight: 0
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadContent()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载隐私政策内容 */
async loadContent() {
try {
@ -34,6 +49,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.privacy-page {
min-height: 100vh;
background-color: #ffffff;

View File

@ -1,5 +1,16 @@
<template>
<view class="qrcode-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">客服二维码</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<view v-if="qrcodeUrl" class="qrcode-card">
<text class="qrcode-title">客服二维码</text>
<image class="qrcode-image" :src="qrcodeUrl" mode="aspectFit" @click="previewImage"></image>
@ -17,13 +28,17 @@ import { getQrcode } from '../../utils/api'
export default {
data() {
return {
qrcodeUrl: ''
qrcodeUrl: '',
statusBarHeight: 0
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadQrcode()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载客服二维码 */
async loadQrcode() {
try {
@ -43,6 +58,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.qrcode-page {
min-height: 100vh;
background-color: #f5f5f5;

View File

@ -1,55 +1,91 @@
<template>
<view class="order-page">
<view class="form-section">
<!-- 代送物品 -->
<view class="form-item">
<text class="form-label">代送物品</text>
<input class="form-input" v-model="form.itemName" placeholder="请输入代送物品" />
</view>
<!-- 取货地点 -->
<view class="form-item">
<text class="form-label">取货地点</text>
<input class="form-input" v-model="form.pickupLocation" placeholder="请输入取货地点" />
</view>
<!-- 送达地点 -->
<view class="form-item">
<text class="form-label">送达地点</text>
<input class="form-input" v-model="form.deliveryLocation" placeholder="请输入送达地点" />
</view>
<!-- 备注信息 -->
<view class="form-item">
<text class="form-label">备注信息</text>
<textarea class="form-textarea" v-model="form.remark" placeholder="请输入备注信息(选填)" />
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<input class="form-input" v-model="form.phone" type="number" placeholder="请输入联系手机号" maxlength="11" />
</view>
<!-- 跑腿佣金 -->
<view class="form-item">
<text class="form-label">跑腿佣金</text>
<view class="commission-input">
<text class="commission-unit">¥</text>
<input class="form-input commission-field" v-model="form.commission" type="digit" placeholder="最低1.0元" />
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">代送</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 顶部大图 -->
<view class="top-banner" v-if="bannerUrl">
<image class="banner-img" :src="bannerUrl" mode="aspectFill"></image>
</view>
<!-- 表单区域 -->
<view class="form-section">
<!-- 1.代送物品 -->
<view class="form-group">
<text class="form-label">1.代送物品</text>
<view class="input-box">
<input v-model="form.itemName" placeholder="请输入要代送的物品名" placeholder-class="placeholder" />
</view>
</view>
<!-- 2.取货地点 -->
<view class="form-group">
<text class="form-label">2.取货地点</text>
<view class="input-box">
<input v-model="form.pickupLocation" placeholder="请输入取货地点" placeholder-class="placeholder" />
</view>
</view>
<!-- 3.送达地点 -->
<view class="form-group">
<text class="form-label">3.送达地点</text>
<view class="input-box">
<input v-model="form.deliveryLocation" placeholder="请输入送达地点" placeholder-class="placeholder" />
</view>
</view>
<!-- 4.备注信息 -->
<view class="form-group">
<text class="form-label">4.备注信息</text>
<view class="input-box textarea-box">
<textarea v-model="form.remark" placeholder="请输入备注信息,如注意事项等" placeholder-class="placeholder" :maxlength="200" />
</view>
</view>
<!-- 5.如何联系您 -->
<view class="form-group">
<text class="form-label">5.如何联系您</text>
<view class="input-box">
<input v-model="form.phone" type="number" placeholder="请输入手机号,对方接单后才能看到" placeholder-class="placeholder" maxlength="11" />
</view>
</view>
<!-- 6.跑腿佣金 -->
<view class="form-group">
<text class="form-label">6.跑腿佣金</text>
<view class="input-box">
<input v-model="form.commission" type="digit" placeholder="请输入跑腿佣金最低1.0元" placeholder-class="placeholder" />
</view>
<text class="form-tip">佣金先由平台保管接单方完成订单后才会收到佣金</text>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<text class="pay-amount">支付金额¥{{ payAmount }}</text>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">确定</button>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">
支付佣金 确定下单
</button>
</view>
</view>
</template>
<script>
import { createOrder } from '../../utils/api'
import { createOrder, getPageBanner } from '../../utils/api'
export default {
data() {
return {
statusBarHeight: 0,
bannerUrl: '',
form: {
itemName: '',
pickupLocation: '',
@ -61,97 +97,79 @@ export default {
submitting: false
}
},
computed: {
/** 支付金额 = 跑腿佣金(代送类型) */
payAmount() {
const c = parseFloat(this.form.commission)
return isNaN(c) ? '0.0' : c.toFixed(1)
}
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadBanner()
},
methods: {
/** 校验佣金 */
goBack() { uni.navigateBack() },
async loadBanner() {
try {
const res = await getPageBanner('delivery')
if (res?.value) this.bannerUrl = res.value
} catch (e) {}
},
validateCommission() {
const val = this.form.commission
if (!val) {
uni.showToast({ title: '请输入跑腿佣金', icon: 'none' })
return false
uni.showToast({ title: '请输入跑腿佣金', icon: 'none' }); return false
}
const num = parseFloat(val)
if (isNaN(num) || num < 1.0) {
uni.showToast({ title: '跑腿佣金不可低于1.0元', icon: 'none' })
return false
uni.showToast({ title: '跑腿佣金不可低于1.0元', icon: 'none' }); return false
}
if (val.includes('.') && val.split('.')[1].length > 1) {
uni.showToast({ title: '佣金最多支持小数点后1位', icon: 'none' })
return false
uni.showToast({ title: '佣金最多支持小数点后1位', icon: 'none' }); return false
}
return true
},
/** 校验表单 */
validateForm() {
if (!this.form.itemName.trim()) {
uni.showToast({ title: '请输入代送物品', icon: 'none' })
return false
uni.showToast({ title: '请输入代送物品', icon: 'none' }); return false
}
if (!this.form.pickupLocation.trim()) {
uni.showToast({ title: '请输入取货地点', icon: 'none' })
return false
uni.showToast({ title: '请输入取货地点', icon: 'none' }); return false
}
if (!this.form.deliveryLocation.trim()) {
uni.showToast({ title: '请输入送达地点', icon: 'none' })
return false
uni.showToast({ title: '请输入送达地点', icon: 'none' }); return false
}
if (!this.form.phone.trim()) {
uni.showToast({ title: '请输入手机号', icon: 'none' })
return false
uni.showToast({ title: '请输入手机号', icon: 'none' }); return false
}
return this.validateCommission()
},
/** 提交订单 */
async onSubmit() {
if (!this.validateForm()) return
const token = uni.getStorageSync('token')
if (!token) {
uni.navigateTo({ url: '/pages/login/login' }); return
}
this.submitting = true
try {
const commission = parseFloat(this.form.commission)
const orderData = {
const result = await createOrder({
orderType: 'Delivery',
itemName: this.form.itemName.trim(),
pickupLocation: this.form.pickupLocation.trim(),
deliveryLocation: this.form.deliveryLocation.trim(),
remark: this.form.remark.trim(),
phone: this.form.phone.trim(),
commission: commission,
commission,
totalAmount: commission
}
const result = await createOrder(orderData)
if (result.paymentParams) {
await this.wxPay(result.paymentParams)
}
})
if (result.paymentParams) await this.wxPay(result.paymentParams)
uni.showToast({ title: '下单成功', icon: 'success' })
setTimeout(() => { uni.navigateBack() }, 1500)
} catch (e) {
// request
} finally {
this.submitting = false
}
} catch (e) {} finally { this.submitting = false }
},
/** 调用微信支付 */
wxPay(params) {
return new Promise((resolve, reject) => {
uni.requestPayment({
...params,
success: resolve,
...params, success: resolve,
fail: (err) => {
if (err.errMsg !== 'requestPayment:fail cancel') {
if (err.errMsg !== 'requestPayment:fail cancel')
uni.showToast({ title: '支付失败', icon: 'none' })
}
reject(err)
}
})
@ -163,94 +181,124 @@ export default {
<style scoped>
.order-page {
padding: 24rpx;
padding-bottom: 200rpx;
padding-bottom: 40rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.form-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 20rpx 30rpx;
}
.form-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.form-item:last-child {
border-bottom: none;
}
.form-label {
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
display: block;
}
.form-input {
font-size: 28rpx;
color: #333333;
height: 72rpx;
}
.form-textarea {
font-size: 28rpx;
color: #333333;
width: 100%;
height: 160rpx;
}
.commission-input {
display: flex;
align-items: center;
}
.commission-unit {
font-size: 32rpx;
color: #e64340;
margin-right: 8rpx;
font-weight: bold;
}
.commission-field {
flex: 1;
}
.submit-section {
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
bottom: 0;
top: 0;
left: 0;
right: 0;
background-color: #ffffff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
padding: 0 20rpx;
}
.pay-amount {
font-size: 32rpx;
color: #e64340;
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.submit-btn {
width: 240rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #007AFF;
color: #ffffff;
/* 顶部大图 */
.top-banner {
padding: 20rpx 24rpx 0;
}
.banner-img {
width: 100%;
height: 330rpx;
border-radius: 20rpx;
}
/* 表单区域 */
.form-section {
padding: 10rpx 24rpx 0;
}
.form-group {
margin-bottom: 24rpx;
}
.form-label {
font-size: 30rpx;
border-radius: 40rpx;
color: #333333;
font-weight: bold;
display: block;
margin-bottom: 16rpx;
}
.input-box {
background-color: #ffffff;
border-radius: 16rpx;
padding: 0 24rpx;
height: 88rpx;
display: flex;
align-items: center;
}
.input-box input {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
}
.textarea-box {
height: 240rpx;
padding: 20rpx 24rpx;
align-items: flex-start;
}
.textarea-box textarea {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
}
.form-tip {
font-size: 24rpx;
color: #999999;
margin-top: 12rpx;
display: block;
text-align: center;
}
/* 提交按钮 */
.submit-section {
padding: 20rpx 24rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
}
.submit-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background: #FAD146;
color: #ffffff;
font-size: 32rpx;
font-weight: bold;
border-radius: 10rpx;
border: none;
text-align: center;
}
.submit-btn::after {
border: none;
}
.submit-btn[disabled] {
opacity: 0.6;
}

View File

@ -1,5 +1,16 @@
<template>
<view class="detail-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">美食街订单详情</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<view v-if="loading" class="loading-tip">加载中...</view>
<view v-else-if="order" class="detail-content">
@ -105,7 +116,8 @@ export default {
showCertModal: false,
certForm: { realName: '', phone: '' },
certSubmitting: false,
certStatus: null
certStatus: null,
statusBarHeight: 0
}
},
computed: {
@ -128,10 +140,13 @@ export default {
}
},
onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.orderId = options.id
this.loadDetail()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载订单详情 */
async loadDetail() {
this.loading = true
@ -210,6 +225,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.detail-page {
padding: 24rpx;
padding-bottom: 180rpx;

View File

@ -1,5 +1,16 @@
<template>
<view class="food-order-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">美食街订单</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 订单商品摘要 -->
<view class="order-summary">
<text class="section-title">订单商品</text>
@ -65,7 +76,8 @@ export default {
phone: '',
commission: ''
},
submitting: false
submitting: false,
statusBarHeight: 0
}
},
computed: {
@ -90,6 +102,8 @@ export default {
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
//
if (this.cartStore.totalCount === 0) {
uni.showToast({ title: '购物车为空', icon: 'none' })
@ -97,6 +111,7 @@ export default {
}
},
methods: {
goBack() { uni.navigateBack() },
/** 校验佣金 */
validateCommission() {
const val = this.form.commission
@ -196,6 +211,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.food-order-page {
padding: 24rpx;
padding-bottom: 200rpx;

View File

@ -1,5 +1,22 @@
<template>
<view class="food-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">美食街</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 顶部大图 -->
<view class="top-banner" v-if="bannerUrl">
<image class="banner-img" :src="bannerUrl" mode="aspectFill"></image>
</view>
<!-- 门店列表 -->
<view class="shop-list">
<view
@ -25,19 +42,31 @@
</template>
<script>
import { getShops } from '../../utils/api'
import { getShops, getPageBanner } from '../../utils/api'
export default {
data() {
return {
shops: [],
loading: true
loading: true,
statusBarHeight: 0,
bannerUrl: ''
}
},
onShow() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadBanner()
this.loadShops()
},
methods: {
goBack() { uni.navigateBack() },
async loadBanner() {
try {
const res = await getPageBanner('food')
if (res?.value) this.bannerUrl = res.value
} catch (e) {}
},
/** 加载门店列表 */
async loadShops() {
this.loading = true
@ -60,6 +89,53 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
/* 顶部大图 */
.top-banner {
padding: 0 24rpx 0;
margin-bottom: 20rpx;
}
.banner-img {
width: 100%;
height: 280rpx;
border-radius: 20rpx;
}
.food-page {
padding: 24rpx;
background-color: #f5f5f5;

View File

@ -1,5 +1,16 @@
<template>
<view class="shop-detail-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">门店详情</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 门店 Banner -->
<swiper
v-if="shop.banners && shop.banners.length > 0"
@ -111,7 +122,8 @@ export default {
return {
shop: {},
dishes: [],
showCartPopup: false
showCartPopup: false,
statusBarHeight: 0
}
},
computed: {
@ -146,11 +158,14 @@ export default {
}
},
onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
if (options.id) {
this.loadShopDetail(options.id)
}
},
methods: {
goBack() { uni.navigateBack() },
/** 加载门店详情 */
async loadShopDetail(id) {
try {
@ -200,6 +215,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.shop-detail-page {
padding-bottom: 140rpx;
background-color: #f5f5f5;

View File

@ -1,45 +1,71 @@
<template>
<view class="order-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">万能帮</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 顶部大图 -->
<view class="top-banner" v-if="bannerUrl">
<image class="banner-img" :src="bannerUrl" mode="aspectFill"></image>
</view>
<!-- 表单区域 -->
<view class="form-section">
<!-- 要做的事情 -->
<view class="form-item">
<text class="form-label">要做的事情</text>
<textarea class="form-textarea" v-model="form.itemName" placeholder="请描述您需要帮忙做的事情" />
<!-- 1.要做的事情 -->
<view class="form-group">
<text class="form-label">1.要做的事情</text>
<view class="input-box textarea-box">
<textarea v-model="form.itemName" placeholder="请描述您需要帮忙做的事情" placeholder-class="placeholder" :maxlength="500" />
</view>
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<input class="form-input" v-model="form.phone" type="number" placeholder="请输入联系手机号" maxlength="11" />
<!-- 2.如何联系您 -->
<view class="form-group">
<text class="form-label">2.如何联系您</text>
<view class="input-box">
<input v-model="form.phone" type="number" placeholder="请输入手机号,对方接单后才能看到" placeholder-class="placeholder" maxlength="11" />
</view>
</view>
<!-- 代购商品总金额 -->
<view class="form-item">
<text class="form-label">代购商品总金额</text>
<!-- 3.商品总金额 -->
<view class="form-group">
<text class="form-label">3.商品总金额</text>
<text class="form-tip">需包含商品总额和包装费等额外费用</text>
<view class="commission-input">
<text class="commission-unit">¥</text>
<input class="form-input commission-field" v-model="form.goodsAmount" type="digit" placeholder="请输入商品总金额" />
<view class="input-box">
<input v-model="form.goodsAmount" type="digit" placeholder="请输入商品总金额" placeholder-class="placeholder" />
</view>
</view>
<!-- 跑腿佣金 -->
<view class="form-item">
<text class="form-label">跑腿佣金</text>
<view class="commission-input">
<text class="commission-unit">¥</text>
<input class="form-input commission-field" v-model="form.commission" type="digit" placeholder="最低1.0元" />
<!-- 4.跑腿佣金 -->
<view class="form-group">
<text class="form-label">4.跑腿佣金</text>
<view class="input-box">
<input v-model="form.commission" type="digit" placeholder="请输入跑腿佣金,最低1.0元" placeholder-class="placeholder" />
</view>
<text class="form-tip">佣金先由平台保管接单方完成订单后才会收到佣金</text>
</view>
</view>
<!-- 提交按钮 -->
<!-- 支付金额 + 提交按钮 -->
<view class="submit-section">
<text class="pay-amount">支付金额¥{{ payAmount }}</text>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">确定</button>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">
支付佣金 确定下单
</button>
</view>
</view>
</template>
<script>
import { createOrder } from '../../utils/api'
import { createOrder, getPageBanner } from '../../utils/api'
export default {
data() {
@ -50,7 +76,9 @@ export default {
goodsAmount: '',
commission: ''
},
submitting: false
submitting: false,
statusBarHeight: 0,
bannerUrl: ''
}
},
computed: {
@ -61,7 +89,19 @@ export default {
return (goods + commission).toFixed(1)
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadBanner()
},
methods: {
goBack() { uni.navigateBack() },
async loadBanner() {
try {
const res = await getPageBanner('help')
if (res?.value) this.bannerUrl = res.value
} catch (e) {}
},
/** 校验佣金 */
validateCommission() {
const val = this.form.commission
@ -80,7 +120,6 @@ export default {
}
return true
},
/** 校验表单 */
validateForm() {
if (!this.form.itemName.trim()) {
@ -102,49 +141,42 @@ export default {
}
return this.validateCommission()
},
/** 提交订单 */
async onSubmit() {
if (!this.validateForm()) return
//
const token = uni.getStorageSync('token')
if (!token) {
uni.navigateTo({ url: '/pages/login/login' })
return
}
this.submitting = true
try {
const commission = parseFloat(this.form.commission)
const goodsAmount = parseFloat(this.form.goodsAmount)
const orderData = {
const result = await createOrder({
orderType: 'Help',
itemName: this.form.itemName.trim(),
phone: this.form.phone.trim(),
goodsAmount: goodsAmount,
commission: commission,
goodsAmount,
commission,
totalAmount: goodsAmount + commission
}
const result = await createOrder(orderData)
if (result.paymentParams) {
await this.wxPay(result.paymentParams)
}
})
if (result.paymentParams) await this.wxPay(result.paymentParams)
uni.showToast({ title: '下单成功', icon: 'success' })
setTimeout(() => { uni.navigateBack() }, 1500)
} catch (e) {
// request
} finally {
this.submitting = false
}
} catch (e) {} finally { this.submitting = false }
},
/** 调用微信支付 */
wxPay(params) {
return new Promise((resolve, reject) => {
uni.requestPayment({
...params,
success: resolve,
...params, success: resolve,
fail: (err) => {
if (err.errMsg !== 'requestPayment:fail cancel') {
if (err.errMsg !== 'requestPayment:fail cancel')
uni.showToast({ title: '支付失败', icon: 'none' })
}
reject(err)
}
})
@ -156,99 +188,137 @@ export default {
<style scoped>
.order-page {
padding: 24rpx;
padding-bottom: 200rpx;
padding-bottom: 40rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
/* 顶部大图 */
.top-banner {
padding: 20rpx 24rpx 0;
}
.banner-img {
width: 100%;
height: 280rpx;
border-radius: 20rpx;
}
/* 表单区域 */
.form-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 20rpx 30rpx;
padding: 10rpx 24rpx 0;
}
.form-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.form-item:last-child {
border-bottom: none;
.form-group {
margin-bottom: 24rpx;
}
.form-label {
font-size: 28rpx;
font-size: 30rpx;
color: #333333;
margin-bottom: 12rpx;
font-weight: bold;
display: block;
margin-bottom: 16rpx;
}
.input-box {
background-color: #ffffff;
border-radius: 16rpx;
padding: 0 24rpx;
height: 88rpx;
display: flex;
align-items: center;
}
.input-box input {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
}
.textarea-box {
height: 240rpx;
padding: 20rpx 24rpx;
align-items: flex-start;
}
.textarea-box textarea {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
}
.form-tip {
font-size: 24rpx;
color: #999999;
margin-bottom: 12rpx;
margin-top: 12rpx;
display: block;
text-align: center;
}
.form-input {
font-size: 28rpx;
color: #333333;
height: 72rpx;
}
.form-textarea {
font-size: 28rpx;
color: #333333;
width: 100%;
height: 200rpx;
}
.commission-input {
display: flex;
align-items: center;
}
.commission-unit {
font-size: 32rpx;
color: #e64340;
margin-right: 8rpx;
font-weight: bold;
}
.commission-field {
flex: 1;
}
/* 支付金额 + 提交按钮 */
.submit-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
padding: 20rpx 30rpx;
padding: 20rpx 24rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.pay-amount {
font-size: 32rpx;
color: #e64340;
font-weight: bold;
display: block;
text-align: center;
margin-bottom: 20rpx;
}
.submit-btn {
width: 240rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #007AFF;
width: 100%;
height: 96rpx;
line-height: 96rpx;
background: #FAD146;
color: #ffffff;
font-size: 30rpx;
border-radius: 40rpx;
font-size: 32rpx;
font-weight: bold;
border-radius: 10rpx;
border: none;
text-align: center;
}
.submit-btn[disabled] {

View File

@ -1,5 +1,16 @@
<template>
<view class="login-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<text class="back-arrow"></text>
</view>
<text class="navbar-title">登录</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- Logo 和应用名称 -->
<view class="login-header">
<image class="login-logo" src="/static/logo.png" mode="aspectFit"></image>
@ -11,12 +22,11 @@
<view class="login-actions">
<button
class="login-btn"
open-type="getPhoneNumber"
@getphonenumber="onGetPhoneNumber"
@click="onWxLogin"
:loading="loading"
:disabled="loading"
>
手机号快捷登录
微信一键登录
</button>
<text class="login-tip">{{ tipText }}</text>
</view>
@ -31,91 +41,128 @@
</view>
</template>
<script>
import { login } from '../../utils/api'
import { useUserStore } from '../../stores/user'
<script setup>
import { ref } from 'vue'
import { useUserStore } from '@/stores/user'
import { wxLogin } from '@/utils/api'
export default {
data() {
return {
loading: false,
tipText: ''
}
},
methods: {
/**
* 微信手机号授权回调
* */
async onGetPhoneNumber(e) {
//
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
this.tipText = '需要授权手机号才能使用服务'
return
}
const userStore = useUserStore()
const loading = ref(false)
const tipText = ref('使用微信账号快速登录')
const statusBarHeight = ref(0)
this.loading = true
this.tipText = ''
//
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight || 0
try {
// login code
const loginRes = await new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: resolve,
fail: reject
})
})
//
const result = await login({
code: loginRes.code,
encryptedData: e.detail.encryptedData,
iv: e.detail.iv
})
// token
const userStore = useUserStore()
userStore.setLoginInfo(result.token, result.userInfo || {})
//
uni.switchTab({ url: '/pages/index/index' })
} catch (err) {
//
this.tipText = '登录失败,请重试'
} finally {
this.loading = false
}
},
/** 跳转用户协议 */
goAgreement() {
uni.navigateTo({ url: '/pages/config/agreement' })
},
/** 跳转隐私政策 */
goPrivacy() {
uni.navigateTo({ url: '/pages/config/privacy' })
}
/** 返回上一页 */
function goBack() {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack()
} else {
uni.switchTab({ url: '/pages/index/index' })
}
}
/** 微信一键登录 */
async function onWxLogin() {
loading.value = true
tipText.value = '登录中...'
try {
// wx.login code
const loginRes = await new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: resolve,
fail: reject
})
})
if (!loginRes?.code) {
tipText.value = '获取微信授权失败,请重试'
return
}
// code token
console.log('[登录] 获取到 code:', loginRes.code)
const res = await wxLogin({ code: loginRes.code })
if (res.token && res.userInfo) {
userStore.setLoginInfo(res.token, res.userInfo)
uni.showToast({ title: '登录成功', icon: 'success' })
uni.reLaunch({ url: '/pages/index/index' })
} else {
tipText.value = '登录失败,请重试'
}
} catch (e) {
console.error('登录失败:', e)
tipText.value = '登录失败,请重试'
} finally {
loading.value = false
}
}
/** 跳转用户协议 */
function goAgreement() {
uni.navigateTo({ url: '/pages/config/agreement' })
}
/** 跳转隐私政策 */
function goPrivacy() {
uni.navigateTo({ url: '/pages/config/privacy' })
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 100vh;
justify-content: center;
background: linear-gradient(180deg, #FFF8E1 0%, #FFFFFF 40%);
padding: 0 60rpx;
background-color: #ffffff;
}
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-arrow {
font-size: 48rpx;
color: #333;
font-weight: bold;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.nav-placeholder {
width: 60rpx;
}
.login-header {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 200rpx;
margin-bottom: 120rpx;
}
.login-logo {
@ -127,58 +174,59 @@ export default {
.login-title {
font-size: 44rpx;
font-weight: bold;
color: #333333;
color: #333;
margin-bottom: 16rpx;
}
.login-subtitle {
font-size: 28rpx;
color: #999999;
color: #999;
}
.login-actions {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.login-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #007AFF;
color: #ffffff;
font-size: 32rpx;
border-radius: 44rpx;
height: 96rpx;
line-height: 96rpx;
background: linear-gradient(135deg, #FFB700, #FF9500);
color: #fff;
font-size: 34rpx;
font-weight: bold;
border-radius: 48rpx;
border: none;
letter-spacing: 4rpx;
}
.login-btn::after {
border: none;
}
.login-btn[disabled] {
opacity: 0.6;
}
.login-tip {
margin-top: 20rpx;
font-size: 26rpx;
color: #e64340;
min-height: 36rpx;
font-size: 24rpx;
color: #bbb;
margin-top: 24rpx;
}
.login-footer {
position: fixed;
bottom: 60rpx;
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 60rpx;
}
.footer-text {
font-size: 24rpx;
color: #999999;
font-size: 22rpx;
color: #ccc;
}
.footer-link {
font-size: 24rpx;
color: #007AFF;
font-size: 22rpx;
color: #FFB700;
}
</style>

View File

@ -1,5 +1,16 @@
<template>
<view class="chat-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">聊天</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 顶部订单信息栏 -->
<view class="order-bar" v-if="orderInfo.id" @click="goOrderDetail">
<text class="order-bar-text">
@ -214,7 +225,8 @@ export default {
showPriceChangePopup: false,
priceChangeType: '', // 'Commission' 'GoodsAmount'
newPriceInput: '',
submittingPriceChange: false
submittingPriceChange: false,
statusBarHeight: 0
}
},
computed: {
@ -255,12 +267,15 @@ export default {
}
},
onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.orderId = options.orderId
this.targetUserId = options.targetUserId
this.loadOrderInfo()
this.loadChatMessages()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载订单信息 */
async loadOrderInfo() {
if (!this.orderId) return
@ -516,6 +531,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.chat-page {
display: flex;
flex-direction: column;

View File

@ -1,5 +1,15 @@
<template>
<view class="message-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<text class="navbar-title">消息</text>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 消息内容 -->
<view class="message-content">
<!-- 系统消息入口 -->
<view class="entry-item" @click="goSystemMsg">
<view class="entry-icon system-icon">
@ -56,6 +66,7 @@
<view v-if="chatList.length === 0" class="empty-chat">
<text class="empty-text">暂无聊天记录</text>
</view>
</view>
</view>
</template>
@ -65,6 +76,7 @@ import { getUnreadCount } from '../../utils/api'
export default {
data() {
return {
statusBarHeight: 0,
unreadCount: {
systemUnread: 0,
orderNotificationUnread: 0,
@ -75,6 +87,8 @@ export default {
}
},
onShow() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadUnreadCount()
this.loadChatList()
this.updateTabBarBadge()
@ -149,6 +163,34 @@ export default {
<style scoped>
.message-page {
min-height: 100vh;
background-color: #f5f5f5;
}
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: linear-gradient(to right, #FFB700, #FFD59B);
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.message-content {
padding: 20rpx 24rpx;
}

View File

@ -1,5 +1,16 @@
<template>
<view class="order-notify-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">订单通知</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 分类标签 -->
<view class="tabs">
<view
@ -56,13 +67,17 @@ export default {
{ label: '已取消', value: 'cancelled' }
],
notifications: [],
loading: false
loading: false,
statusBarHeight: 0
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadNotifications()
},
methods: {
goBack() { uni.navigateBack() },
/** 切换分类标签 */
switchTab(value) {
this.currentTab = value
@ -117,6 +132,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.order-notify-page {
padding: 0 24rpx 20rpx;
}

View File

@ -1,5 +1,16 @@
<template>
<view class="system-msg-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">系统消息</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 系统消息列表 -->
<view
class="msg-item"
@ -59,13 +70,17 @@ export default {
messages: [],
loading: false,
showDetail: false,
detailData: {}
detailData: {},
statusBarHeight: 0
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadMessages()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载系统消息列表 */
async loadMessages() {
this.loading = true
@ -110,6 +125,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.system-msg-page {
padding: 20rpx 24rpx;
}

View File

@ -1,5 +1,16 @@
<template>
<view class="record-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">收益记录</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<view v-if="loading" class="loading-tip">加载中...</view>
<view v-else-if="records.length === 0" class="empty-tip">
<text>暂无收益记录</text>
@ -44,13 +55,17 @@ export default {
data() {
return {
records: [],
loading: false
loading: false,
statusBarHeight: 0
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadRecords()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载收益记录 */
async loadRecords() {
this.loading = true
@ -80,6 +95,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.record-page {
min-height: 100vh;
background-color: #f5f5f5;

View File

@ -1,5 +1,16 @@
<template>
<view class="earnings-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">我的收益</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 四种金额状态展示 -->
<view class="amount-section">
<view class="amount-grid">
@ -146,13 +157,17 @@ export default {
withdrawSubmitting: false,
//
showGuideModal: false,
guideContent: ''
guideContent: '',
statusBarHeight: 0
}
},
onShow() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadData()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载收益和提现数据 */
async loadData() {
try {
@ -276,6 +291,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.earnings-page {
min-height: 100vh;
background-color: #f5f5f5;

View File

@ -1,85 +1,95 @@
<template>
<view class="mine-page">
<!-- 用户信息区域 -->
<view class="user-header">
<!-- 顶部渐变导航栏 -->
<view class="header-bg">
<view :style="{ height: statusBarHeight + 'px' }"></view>
<view class="nav-bar">
<text class="nav-title">我的</text>
</view>
</view>
<!-- 用户信息卡片 -->
<view class="user-card" @click="onUserClick">
<image class="user-avatar" :src="userInfo.avatarUrl || '/static/logo.png'" mode="aspectFill"></image>
<text class="user-name">{{ userInfo.nickname || '用户' }}</text>
<text class="user-name">{{ isLoggedIn ? (userInfo.nickname || '用户') : '点击注册/登录' }}</text>
<image class="arrow-icon" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
<!-- 订单统计 -->
<view class="stats-section">
<view class="stats-row">
<view class="stats-block" @click="goMyOrders">
<text class="stats-title">我的下单</text>
<view class="stats-nums">
<view class="stats-item">
<text class="stats-num">{{ stats.orderCount }}</text>
<text class="stats-label">下单数</text>
</view>
<view class="stats-item">
<text class="stats-num">{{ stats.orderCompleted }}</text>
<text class="stats-label">完成数</text>
</view>
</view>
<!-- 我的订单 -->
<view class="card" @click="goMyOrders">
<view class="card-header">
<text class="card-title">我的订单</text>
<image class="arrow-icon" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
<view class="card-stats">
<view class="stat-item">
<text class="stat-label">进行中</text>
<text class="stat-num">{{ stats.orderOngoing }}</text>
</view>
<view class="stats-block" @click="goMyTaken">
<text class="stats-title">我的接单</text>
<view class="stats-nums">
<view class="stats-item">
<text class="stats-num">{{ stats.takenCount }}</text>
<text class="stats-label">接单数</text>
</view>
<view class="stats-item">
<text class="stats-num">{{ stats.takenCompleted }}</text>
<text class="stats-label">完成数</text>
</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-label">已完成</text>
<text class="stat-num">{{ stats.orderCompleted }}</text>
</view>
</view>
</view>
<!-- 功能入口 -->
<view class="menu-section">
<view class="menu-item" @click="contactService">
<text class="menu-icon">📞</text>
<text class="menu-text">联系客服</text>
<text class="menu-arrow"></text>
<!-- 我的接单 -->
<view class="card" @click="goMyTaken">
<view class="card-header">
<text class="card-title">我的接单</text>
<image class="arrow-icon" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
<view class="card-stats">
<view class="stat-item">
<text class="stat-label">进行中</text>
<text class="stat-num">{{ stats.takenOngoing }}</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-label">已完成</text>
<text class="stat-num">{{ stats.takenCompleted }}</text>
</view>
</view>
</view>
<!-- 菜单列表 -->
<view class="menu-list">
<view class="menu-item" @click="goQrcode">
<text class="menu-icon">📱</text>
<text class="menu-text">客服二维码</text>
<text class="menu-arrow"></text>
<image class="menu-icon" src="/static/ic_customer_service.png" mode="aspectFit"></image>
<text class="menu-text">联系客服</text>
<image class="menu-arrow" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
<view class="menu-item" @click="goCertification">
<text class="menu-icon">🏃</text>
<image class="menu-icon" src="/static/ic_certification.png" mode="aspectFit"></image>
<text class="menu-text">跑腿认证</text>
<text class="menu-arrow"></text>
<image class="menu-arrow" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
<view class="menu-item" @click="goEarnings">
<text class="menu-icon">💰</text>
<image class="menu-icon" src="/static/ic_revenue.png" mode="aspectFit"></image>
<text class="menu-text">我的收益</text>
<text class="menu-arrow"></text>
<image class="menu-arrow" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
</view>
<view class="menu-section">
<view class="menu-item" @click="goAgreement">
<text class="menu-icon">📄</text>
<image class="menu-icon" src="/static/ic_agreement1.png" mode="aspectFit"></image>
<text class="menu-text">用户协议</text>
<text class="menu-arrow"></text>
<image class="menu-arrow" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
<view class="menu-item" @click="goPrivacy">
<text class="menu-icon">🔒</text>
<text class="menu-text">隐私政策</text>
<text class="menu-arrow"></text>
<image class="menu-icon" src="/static/ic_agreement2.png" mode="aspectFit"></image>
<text class="menu-text">隐私协议</text>
<image class="menu-arrow" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
<view class="menu-item" @click="goRunnerAgreement">
<image class="menu-icon" src="/static/ic_agreement3.png" mode="aspectFit"></image>
<text class="menu-text">跑腿协议</text>
<image class="menu-arrow" src="/static/ic_arrow.png" mode="aspectFit"></image>
</view>
</view>
<!-- 退出登录 -->
<view class="logout-section">
<view class="logout-btn" @click="onLogout">
<text>退出登录</text>
</view>
<view v-if="isLoggedIn" class="logout-section" @click="onLogout">
<text class="logout-text">退出登录</text>
</view>
</view>
</template>
@ -92,18 +102,25 @@ export default {
data() {
return {
userInfo: {},
isLoggedIn: false,
statusBarHeight: 0,
stats: {
orderCount: 0,
orderOngoing: 0,
orderCompleted: 0,
takenCount: 0,
takenOngoing: 0,
takenCompleted: 0
}
}
},
onShow() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
const userStore = useUserStore()
this.userInfo = userStore.userInfo || {}
this.loadStats()
this.isLoggedIn = userStore.isLoggedIn
if (this.isLoggedIn) {
this.loadStats()
}
},
methods: {
/** 加载订单统计 */
@ -115,57 +132,31 @@ export default {
])
const orders = ordersRes?.items || ordersRes || []
const taken = takenRes?.items || takenRes || []
this.stats.orderCount = orders.length
this.stats.orderOngoing = orders.filter(o => o.status !== 'Completed' && o.status !== 'Cancelled').length
this.stats.orderCompleted = orders.filter(o => o.status === 'Completed').length
this.stats.takenCount = taken.length
this.stats.takenOngoing = taken.filter(o => o.status !== 'Completed' && o.status !== 'Cancelled').length
this.stats.takenCompleted = taken.filter(o => o.status === 'Completed').length
} catch (e) {
//
}
},
/** 跳转我的订单 */
goMyOrders() {
uni.navigateTo({ url: '/pages/order/my-orders' })
/** 点击用户区域 */
onUserClick() {
if (!this.isLoggedIn) {
uni.navigateTo({ url: '/pages/login/login' })
}
},
/** 跳转我的接单 */
goMyTaken() {
uni.navigateTo({ url: '/pages/order/my-taken' })
},
/** 联系客服 */
contactService() {
//
// uni-app button open-type="contact" navigateTo
uni.showToast({ title: '请使用客服按钮', icon: 'none' })
},
/** 客服二维码 */
goQrcode() {
uni.navigateTo({ url: '/pages/config/qrcode' })
},
/** 跑腿认证 */
goCertification() {
uni.navigateTo({ url: '/pages/runner/certification' })
},
/** 我的收益 */
goEarnings() {
uni.navigateTo({ url: '/pages/mine/earnings' })
},
/** 用户协议 */
goAgreement() {
goMyOrders() { uni.navigateTo({ url: '/pages/order/my-orders' }) },
goMyTaken() { uni.navigateTo({ url: '/pages/order/my-taken' }) },
goQrcode() { uni.navigateTo({ url: '/pages/config/qrcode' }) },
goCertification() { uni.navigateTo({ url: '/pages/runner/certification' }) },
goEarnings() { uni.navigateTo({ url: '/pages/mine/earnings' }) },
goAgreement() { uni.navigateTo({ url: '/pages/config/agreement' }) },
goPrivacy() { uni.navigateTo({ url: '/pages/config/privacy' }) },
goRunnerAgreement() {
//
uni.navigateTo({ url: '/pages/config/agreement' })
},
/** 隐私政策 */
goPrivacy() {
uni.navigateTo({ url: '/pages/config/privacy' })
},
/** 退出登录 */
onLogout() {
uni.showModal({
@ -187,91 +178,127 @@ export default {
.mine-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 140rpx;
}
/* 用户信息头部 */
.user-header {
/* 顶部渐变背景 */
.header-bg {
background: linear-gradient(180deg, #FFB700 0%, #FFD59B 100%);
}
.nav-bar {
height: 88rpx;
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 0 40rpx;
background-color: #007AFF;
justify-content: center;
}
.user-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
border: 4rpx solid #ffffff;
margin-bottom: 16rpx;
}
.user-name {
font-size: 32rpx;
color: #ffffff;
font-weight: 500;
}
/* 订单统计 */
.stats-section {
margin: -20rpx 24rpx 24rpx;
}
.stats-row {
display: flex;
gap: 20rpx;
}
.stats-block {
flex: 1;
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
}
.stats-title {
font-size: 28rpx;
color: #333333;
font-weight: 500;
display: block;
margin-bottom: 16rpx;
}
.stats-nums {
display: flex;
justify-content: space-around;
}
.stats-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stats-num {
font-size: 36rpx;
color: #007AFF;
.nav-title {
font-size: 34rpx;
color: #333;
font-weight: bold;
}
.stats-label {
font-size: 22rpx;
color: #999999;
margin-top: 4rpx;
/* 用户信息卡片 */
.user-card {
display: flex;
align-items: center;
margin: 20rpx 30rpx 0;
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
}
/* 菜单区域 */
.menu-section {
margin: 0 24rpx 24rpx;
background-color: #ffffff;
border-radius: 16rpx;
overflow: hidden;
.user-avatar {
width: 90rpx;
height: 90rpx;
border-radius: 50%;
margin-right: 24rpx;
background-color: #f0f0f0;
}
.user-name {
flex: 1;
font-size: 32rpx;
color: #333;
font-weight: 500;
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
}
/* 卡片 */
.card {
margin: 20rpx 30rpx 0;
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
}
.card-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 30rpx;
}
.card-title {
font-size: 32rpx;
color: #FFB700;
font-weight: bold;
}
.card-header .arrow-icon {
position: absolute;
right: 0;
}
.card-stats {
display: flex;
align-items: center;
justify-content: center;
}
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.stat-label {
font-size: 26rpx;
color: #999;
margin-bottom: 12rpx;
}
.stat-num {
font-size: 48rpx;
color: #333;
font-weight: bold;
}
.stat-divider {
width: 2rpx;
height: 60rpx;
background-color: #e0e0e0;
}
/* 菜单列表 */
.menu-list {
margin: 20rpx 30rpx 0;
background: #fff;
border-radius: 20rpx;
padding: 0 30rpx;
}
.menu-item {
display: flex;
align-items: center;
padding: 28rpx 30rpx;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
@ -280,34 +307,31 @@ export default {
}
.menu-icon {
font-size: 36rpx;
margin-right: 20rpx;
width: 44rpx;
height: 44rpx;
margin-right: 24rpx;
}
.menu-text {
flex: 1;
font-size: 28rpx;
color: #333333;
font-size: 30rpx;
color: #333;
}
.menu-arrow {
font-size: 32rpx;
color: #cccccc;
width: 28rpx;
height: 28rpx;
}
/* 退出登录 */
.logout-section {
margin: 40rpx 24rpx;
}
.logout-btn {
background-color: #ffffff;
border-radius: 16rpx;
margin: 40rpx 30rpx 120rpx;
background: #fff;
border-radius: 20rpx;
padding: 28rpx 0;
text-align: center;
}
.logout-btn text {
.logout-text {
font-size: 30rpx;
color: #e64340;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,16 @@
<template>
<view class="complete-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">完成订单确认</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 订单信息展示 -->
<view class="info-section">
<view class="info-title">
@ -91,16 +102,20 @@ export default {
proofImageUrl: '',
submitting: false,
//
isOwnerConfirm: false
isOwnerConfirm: false,
statusBarHeight: 0
}
},
onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.orderId = options.id
// mode=confirm
this.isOwnerConfirm = options.mode === 'confirm'
this.loadOrderInfo()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载订单信息 */
async loadOrderInfo() {
if (!this.orderId) return
@ -225,6 +240,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.complete-page {
padding: 30rpx;
min-height: 100vh;

View File

@ -1,5 +1,16 @@
<template>
<view class="my-orders-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">我的订单</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 状态筛选标签 -->
<scroll-view class="tab-bar" scroll-x>
<view
@ -179,7 +190,8 @@ export default {
showReviewModal: false,
reviewingOrder: null,
reviewForm: { rating: 5, content: '' },
reviewSubmitting: false
reviewSubmitting: false,
statusBarHeight: 0
}
},
computed: {
@ -193,9 +205,12 @@ export default {
}
},
onShow() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadOrders()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载我的订单 */
async loadOrders() {
this.loading = true
@ -314,6 +329,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.my-orders-page {
display: flex;
flex-direction: column;

View File

@ -1,5 +1,16 @@
<template>
<view class="my-taken-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">我的接单</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 状态筛选标签 -->
<scroll-view class="tab-bar" scroll-x>
<view
@ -120,7 +131,8 @@ export default {
currentStatus: '',
currentType: '',
orders: [],
loading: false
loading: false,
statusBarHeight: 0
}
},
computed: {
@ -133,9 +145,12 @@ export default {
}
},
onShow() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadOrders()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载我的接单 */
async loadOrders() {
this.loading = true
@ -193,6 +208,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.my-taken-page {
display: flex;
flex-direction: column;

View File

@ -1,5 +1,16 @@
<template>
<view class="detail-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">订单详情</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 页面标题 -->
<view class="page-title">
<text>我的{{ getTypeLabel(order.orderType) }}订单详情</text>
@ -209,7 +220,8 @@ export default {
//
showReviewModal: false,
reviewForm: { rating: 5, content: '' },
reviewSubmitting: false
reviewSubmitting: false,
statusBarHeight: 0
}
},
computed: {
@ -231,10 +243,13 @@ export default {
}
},
onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.orderId = options.id
this.loadDetail()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载订单详情 */
async loadDetail() {
if (!this.orderId) return
@ -346,6 +361,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.detail-page {
min-height: 100vh;
background-color: #f5f5f5;

View File

@ -1,55 +1,91 @@
<template>
<view class="order-page">
<view class="form-section">
<!-- 代取物品 -->
<view class="form-item">
<text class="form-label">代取物品</text>
<input class="form-input" v-model="form.itemName" placeholder="请输入代取物品" />
</view>
<!-- 代取地点 -->
<view class="form-item">
<text class="form-label">代取地点</text>
<input class="form-input" v-model="form.pickupLocation" placeholder="请输入代取地点" />
</view>
<!-- 送达地点 -->
<view class="form-item">
<text class="form-label">送达地点</text>
<input class="form-input" v-model="form.deliveryLocation" placeholder="请输入送达地点" />
</view>
<!-- 备注信息 -->
<view class="form-item">
<text class="form-label">备注信息</text>
<textarea class="form-textarea" v-model="form.remark" placeholder="请输入备注信息(选填)" />
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<input class="form-input" v-model="form.phone" type="number" placeholder="请输入联系手机号" maxlength="11" />
</view>
<!-- 跑腿佣金 -->
<view class="form-item">
<text class="form-label">跑腿佣金</text>
<view class="commission-input">
<text class="commission-unit">¥</text>
<input class="form-input commission-field" v-model="form.commission" type="digit" placeholder="最低1.0元" />
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">代取</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 顶部大图 -->
<view class="top-banner" v-if="bannerUrl">
<image class="banner-img" :src="bannerUrl" mode="aspectFill"></image>
</view>
<!-- 表单区域 -->
<view class="form-section">
<!-- 1.代取物品 -->
<view class="form-group">
<text class="form-label">1.代取物品</text>
<view class="input-box">
<input v-model="form.itemName" placeholder="请输入要代取的物品名" placeholder-class="placeholder" />
</view>
</view>
<!-- 2.取货地点 -->
<view class="form-group">
<text class="form-label">2.取货地点</text>
<view class="input-box">
<input v-model="form.pickupLocation" placeholder="请输入代取地点" placeholder-class="placeholder" />
</view>
</view>
<!-- 3.送达地点 -->
<view class="form-group">
<text class="form-label">3.送达地点</text>
<view class="input-box">
<input v-model="form.deliveryLocation" placeholder="请输入送达地点" placeholder-class="placeholder" />
</view>
</view>
<!-- 4.备注信息 -->
<view class="form-group">
<text class="form-label">4.备注信息</text>
<view class="input-box textarea-box">
<textarea v-model="form.remark" placeholder="请输入备注信息,如注意事项、订单号、取货码等" placeholder-class="placeholder" :maxlength="200" />
</view>
</view>
<!-- 5.如何联系您 -->
<view class="form-group">
<text class="form-label">5.如何联系您</text>
<view class="input-box">
<input v-model="form.phone" type="number" placeholder="请输入手机号,对方接单后才能看到" placeholder-class="placeholder" maxlength="11" />
</view>
</view>
<!-- 6.跑腿佣金 -->
<view class="form-group">
<text class="form-label">6.跑腿佣金</text>
<view class="input-box">
<input v-model="form.commission" type="digit" placeholder="请输入跑腿佣金最低1.0元" placeholder-class="placeholder" />
</view>
<text class="form-tip">佣金先由平台保管接单方完成订单后才会收到佣金</text>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<text class="pay-amount">支付金额¥{{ payAmount }}</text>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">确定</button>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">
支付佣金 确定下单
</button>
</view>
</view>
</template>
<script>
import { createOrder } from '../../utils/api'
import { createOrder, getPageBanner } from '../../utils/api'
export default {
data() {
return {
statusBarHeight: 0,
bannerUrl: '',
form: {
itemName: '',
pickupLocation: '',
@ -61,15 +97,19 @@ export default {
submitting: false
}
},
computed: {
/** 支付金额 = 跑腿佣金(代取类型) */
payAmount() {
const c = parseFloat(this.form.commission)
return isNaN(c) ? '0.0' : c.toFixed(1)
}
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadBanner()
},
methods: {
/** 校验佣金 */
goBack() { uni.navigateBack() },
async loadBanner() {
try {
const res = await getPageBanner('pickup')
if (res?.value) this.bannerUrl = res.value
} catch (e) {}
},
validateCommission() {
const val = this.form.commission
if (!val) {
@ -81,82 +121,62 @@ export default {
uni.showToast({ title: '跑腿佣金不可低于1.0元', icon: 'none' })
return false
}
// 1
if (val.includes('.') && val.split('.')[1].length > 1) {
uni.showToast({ title: '佣金最多支持小数点后1位', icon: 'none' })
return false
}
return true
},
/** 校验表单 */
validateForm() {
if (!this.form.itemName.trim()) {
uni.showToast({ title: '请输入代取物品', icon: 'none' })
return false
uni.showToast({ title: '请输入代取物品', icon: 'none' }); return false
}
if (!this.form.pickupLocation.trim()) {
uni.showToast({ title: '请输入代取地点', icon: 'none' })
return false
uni.showToast({ title: '请输入代取地点', icon: 'none' }); return false
}
if (!this.form.deliveryLocation.trim()) {
uni.showToast({ title: '请输入送达地点', icon: 'none' })
return false
uni.showToast({ title: '请输入送达地点', icon: 'none' }); return false
}
if (!this.form.phone.trim()) {
uni.showToast({ title: '请输入手机号', icon: 'none' })
return false
uni.showToast({ title: '请输入手机号', icon: 'none' }); return false
}
return this.validateCommission()
},
/** 提交订单 */
async onSubmit() {
if (!this.validateForm()) return
//
const token = uni.getStorageSync('token')
if (!token) {
uni.navigateTo({ url: '/pages/login/login' })
return
}
this.submitting = true
try {
//
const commission = parseFloat(this.form.commission)
const orderData = {
const result = await createOrder({
orderType: 'Pickup',
itemName: this.form.itemName.trim(),
pickupLocation: this.form.pickupLocation.trim(),
deliveryLocation: this.form.deliveryLocation.trim(),
remark: this.form.remark.trim(),
phone: this.form.phone.trim(),
commission: commission,
commission,
totalAmount: commission
}
const result = await createOrder(orderData)
//
if (result.paymentParams) {
await this.wxPay(result.paymentParams)
}
})
if (result.paymentParams) await this.wxPay(result.paymentParams)
uni.showToast({ title: '下单成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
// request
} finally {
this.submitting = false
}
setTimeout(() => { uni.navigateBack() }, 1500)
} catch (e) {} finally { this.submitting = false }
},
/** 调用微信支付 */
wxPay(params) {
return new Promise((resolve, reject) => {
uni.requestPayment({
...params,
success: resolve,
...params, success: resolve,
fail: (err) => {
if (err.errMsg !== 'requestPayment:fail cancel') {
if (err.errMsg !== 'requestPayment:fail cancel')
uni.showToast({ title: '支付失败', icon: 'none' })
}
reject(err)
}
})
@ -168,92 +188,128 @@ export default {
<style scoped>
.order-page {
padding: 24rpx;
padding-bottom: 200rpx;
padding-bottom: 40rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.form-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 20rpx 30rpx;
}
.form-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.form-item:last-child {
border-bottom: none;
}
.form-label {
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
display: block;
}
.form-input {
font-size: 28rpx;
color: #333333;
height: 72rpx;
}
.form-textarea {
font-size: 28rpx;
color: #333333;
width: 100%;
height: 160rpx;
}
.commission-input {
display: flex;
align-items: center;
}
.commission-unit {
font-size: 32rpx;
color: #e64340;
margin-right: 8rpx;
font-weight: bold;
}
.commission-field {
flex: 1;
}
.submit-section {
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
bottom: 0;
top: 0;
left: 0;
right: 0;
background-color: #ffffff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.pay-amount {
font-size: 32rpx;
color: #e64340;
/* 顶部大图 */
.top-banner {
padding: 20rpx 24rpx 0;
}
.banner-img {
width: 100%;
height: 330rpx;
border-radius: 20rpx;
}
/* 表单区域 */
.form-section {
padding: 10rpx 24rpx 0;
}
.form-group {
margin-bottom: 24rpx;
}
.form-label {
font-size: 30rpx;
color: #333333;
font-weight: bold;
display: block;
margin-bottom: 16rpx;
}
.input-box {
background-color: #ffffff;
border-radius: 16rpx;
padding: 0 24rpx;
height: 88rpx;
display: flex;
align-items: center;
}
.input-box input {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
}
.textarea-box {
height: 240rpx;
padding: 20rpx 24rpx;
align-items: flex-start;
}
.textarea-box textarea {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
}
.form-tip {
font-size: 24rpx;
color: #999999;
margin-top: 12rpx;
display: block;
text-align: center;
}
/* 提交按钮 */
.submit-section {
padding: 20rpx 24rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
}
.submit-btn {
width: 240rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #007AFF;
width: 100%;
height: 96rpx;
line-height: 96rpx;
background: #FAD146;
color: #ffffff;
font-size: 30rpx;
border-radius: 40rpx;
font-size: 32rpx;
font-weight: bold;
border-radius: 10rpx;
border: none;
text-align: center;
}
.submit-btn[disabled] {

View File

@ -1,59 +1,82 @@
<template>
<view class="order-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">代购</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 顶部大图 -->
<view class="top-banner" v-if="bannerUrl">
<image class="banner-img" :src="bannerUrl" mode="aspectFill"></image>
</view>
<!-- 表单区域 -->
<view class="form-section">
<!-- 代购物品 -->
<view class="form-item">
<text class="form-label">代购物品</text>
<input class="form-input" v-model="form.itemName" placeholder="请输入代购物品" />
</view>
<!-- 买货地点 -->
<view class="form-item">
<text class="form-label">买货地点</text>
<input class="form-input" v-model="form.pickupLocation" placeholder="请输入买货地点" />
</view>
<!-- 送达地点 -->
<view class="form-item">
<text class="form-label">送达地点</text>
<input class="form-input" v-model="form.deliveryLocation" placeholder="请输入送达地点" />
</view>
<!-- 备注信息 -->
<view class="form-item">
<text class="form-label">备注信息</text>
<textarea class="form-textarea" v-model="form.remark" placeholder="请输入备注信息(选填)" />
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<input class="form-input" v-model="form.phone" type="number" placeholder="请输入联系手机号" maxlength="11" />
</view>
<!-- 代购商品总金额 -->
<view class="form-item">
<text class="form-label">代购商品总金额</text>
<view class="commission-input">
<text class="commission-unit">¥</text>
<input class="form-input commission-field" v-model="form.goodsAmount" type="digit" placeholder="请输入商品总金额" />
<view class="form-group">
<text class="form-label">1.代购物品</text>
<view class="input-box">
<input v-model="form.itemName" placeholder="请输入代购物品名" placeholder-class="placeholder" />
</view>
</view>
<!-- 跑腿佣金 -->
<view class="form-item">
<text class="form-label">跑腿佣金</text>
<view class="commission-input">
<text class="commission-unit">¥</text>
<input class="form-input commission-field" v-model="form.commission" type="digit" placeholder="最低1.0元" />
<view class="form-group">
<text class="form-label">2.买货地点</text>
<view class="input-box">
<input v-model="form.pickupLocation" placeholder="请输入买货地点" placeholder-class="placeholder" />
</view>
</view>
<view class="form-group">
<text class="form-label">3.送达地点</text>
<view class="input-box">
<input v-model="form.deliveryLocation" placeholder="请输入送达地点" placeholder-class="placeholder" />
</view>
</view>
<view class="form-group">
<text class="form-label">4.备注信息</text>
<view class="input-box textarea-box">
<textarea v-model="form.remark" placeholder="请输入备注信息,如规格、数量、品牌等" placeholder-class="placeholder" :maxlength="200" />
</view>
</view>
<view class="form-group">
<text class="form-label">5.如何联系您</text>
<view class="input-box">
<input v-model="form.phone" type="number" placeholder="请输入手机号,对方接单后才能看到" placeholder-class="placeholder" maxlength="11" />
</view>
</view>
<view class="form-group">
<text class="form-label">6.商品总金额</text>
<view class="input-box">
<input v-model="form.goodsAmount" type="digit" placeholder="请输入商品总金额" placeholder-class="placeholder" />
</view>
<text class="form-tip">需包含商品总额和包装费等额外费用</text>
</view>
<view class="form-group">
<text class="form-label">7.跑腿佣金</text>
<view class="input-box">
<input v-model="form.commission" type="digit" placeholder="请输入跑腿佣金最低1.0元" placeholder-class="placeholder" />
</view>
<text class="form-tip">佣金先由平台保管接单方完成订单后才会收到佣金</text>
</view>
</view>
<!-- 提交按钮 -->
<!-- 支付金额 + 提交按钮 -->
<view class="submit-section">
<text class="pay-amount">支付金额¥{{ payAmount }}</text>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">确定</button>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">
支付佣金 确定下单
</button>
</view>
</view>
</template>
<script>
import { createOrder } from '../../utils/api'
import { createOrder, getPageBanner } from '../../utils/api'
export default {
data() {
@ -67,7 +90,9 @@ export default {
goodsAmount: '',
commission: ''
},
submitting: false
submitting: false,
statusBarHeight: 0,
bannerUrl: ''
}
},
computed: {
@ -78,101 +103,67 @@ export default {
return (goods + commission).toFixed(1)
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadBanner()
},
methods: {
/** 校验佣金 */
goBack() { uni.navigateBack() },
async loadBanner() {
try {
const res = await getPageBanner('purchase')
if (res?.value) this.bannerUrl = res.value
} catch (e) {}
},
validateCommission() {
const val = this.form.commission
if (!val) {
uni.showToast({ title: '请输入跑腿佣金', icon: 'none' })
return false
}
if (!val) { uni.showToast({ title: '请输入跑腿佣金', icon: 'none' }); return false }
const num = parseFloat(val)
if (isNaN(num) || num < 1.0) {
uni.showToast({ title: '跑腿佣金不可低于1.0元', icon: 'none' })
return false
}
if (val.includes('.') && val.split('.')[1].length > 1) {
uni.showToast({ title: '佣金最多支持小数点后1位', icon: 'none' })
return false
}
if (isNaN(num) || num < 1.0) { uni.showToast({ title: '跑腿佣金不可低于1.0元', icon: 'none' }); return false }
if (val.includes('.') && val.split('.')[1].length > 1) { uni.showToast({ title: '佣金最多支持小数点后1位', icon: 'none' }); return false }
return true
},
/** 校验表单 */
validateForm() {
if (!this.form.itemName.trim()) {
uni.showToast({ title: '请输入代购物品', icon: 'none' })
return false
}
if (!this.form.pickupLocation.trim()) {
uni.showToast({ title: '请输入买货地点', icon: 'none' })
return false
}
if (!this.form.deliveryLocation.trim()) {
uni.showToast({ title: '请输入送达地点', icon: 'none' })
return false
}
if (!this.form.phone.trim()) {
uni.showToast({ title: '请输入手机号', icon: 'none' })
return false
}
if (!this.form.goodsAmount) {
uni.showToast({ title: '请输入商品总金额', icon: 'none' })
return false
}
if (!this.form.itemName.trim()) { uni.showToast({ title: '请输入代购物品', icon: 'none' }); return false }
if (!this.form.pickupLocation.trim()) { uni.showToast({ title: '请输入买货地点', icon: 'none' }); return false }
if (!this.form.deliveryLocation.trim()) { uni.showToast({ title: '请输入送达地点', icon: 'none' }); return false }
if (!this.form.phone.trim()) { uni.showToast({ title: '请输入手机号', icon: 'none' }); return false }
if (!this.form.goodsAmount) { uni.showToast({ title: '请输入商品总金额', icon: 'none' }); return false }
const goodsNum = parseFloat(this.form.goodsAmount)
if (isNaN(goodsNum) || goodsNum <= 0) {
uni.showToast({ title: '请输入正确的商品总金额', icon: 'none' })
return false
}
if (isNaN(goodsNum) || goodsNum <= 0) { uni.showToast({ title: '请输入正确的商品总金额', icon: 'none' }); return false }
return this.validateCommission()
},
/** 提交订单 */
async onSubmit() {
if (!this.validateForm()) return
const token = uni.getStorageSync('token')
if (!token) { uni.navigateTo({ url: '/pages/login/login' }); return }
this.submitting = true
try {
const commission = parseFloat(this.form.commission)
const goodsAmount = parseFloat(this.form.goodsAmount)
const orderData = {
const result = await createOrder({
orderType: 'Purchase',
itemName: this.form.itemName.trim(),
pickupLocation: this.form.pickupLocation.trim(),
deliveryLocation: this.form.deliveryLocation.trim(),
remark: this.form.remark.trim(),
phone: this.form.phone.trim(),
goodsAmount: goodsAmount,
commission: commission,
goodsAmount, commission,
totalAmount: goodsAmount + commission
}
const result = await createOrder(orderData)
if (result.paymentParams) {
await this.wxPay(result.paymentParams)
}
})
if (result.paymentParams) await this.wxPay(result.paymentParams)
uni.showToast({ title: '下单成功', icon: 'success' })
setTimeout(() => { uni.navigateBack() }, 1500)
} catch (e) {
// request
} finally {
this.submitting = false
}
} catch (e) {} finally { this.submitting = false }
},
/** 调用微信支付 */
wxPay(params) {
return new Promise((resolve, reject) => {
uni.requestPayment({
...params,
success: resolve,
...params, success: resolve,
fail: (err) => {
if (err.errMsg !== 'requestPayment:fail cancel') {
if (err.errMsg !== 'requestPayment:fail cancel')
uni.showToast({ title: '支付失败', icon: 'none' })
}
reject(err)
}
})
@ -184,95 +175,37 @@ export default {
<style scoped>
.order-page {
padding: 24rpx;
padding-bottom: 200rpx;
padding-bottom: 40rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.form-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 20rpx 30rpx;
.custom-navbar {
position: fixed; top: 0; left: 0; width: 100%; z-index: 999; background: #FFB700;
}
.form-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
.navbar-content {
height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 20rpx;
}
.form-item:last-child {
border-bottom: none;
.nav-back { width: 60rpx; height: 60rpx; display: flex; align-items: center; justify-content: center; }
.back-icon { width: 40rpx; height: 40rpx; }
.navbar-title { font-size: 34rpx; font-weight: bold; color: #363636; }
.nav-placeholder { width: 60rpx; }
.top-banner { padding: 20rpx 24rpx 0; }
.banner-img { width: 100%; height: 280rpx; border-radius: 20rpx; }
.form-section { padding: 10rpx 24rpx 0; }
.form-group { margin-bottom: 24rpx; }
.form-label { font-size: 30rpx; color: #333333; font-weight: bold; display: block; margin-bottom: 16rpx; }
.input-box {
background-color: #ffffff; border-radius: 16rpx; padding: 0 24rpx; height: 88rpx; display: flex; align-items: center;
}
.form-label {
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
display: block;
}
.form-input {
font-size: 28rpx;
color: #333333;
height: 72rpx;
}
.form-textarea {
font-size: 28rpx;
color: #333333;
width: 100%;
height: 160rpx;
}
.commission-input {
display: flex;
align-items: center;
}
.commission-unit {
font-size: 32rpx;
color: #e64340;
margin-right: 8rpx;
font-weight: bold;
}
.commission-field {
flex: 1;
}
.submit-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.pay-amount {
font-size: 32rpx;
color: #e64340;
font-weight: bold;
}
.input-box input { width: 100%; height: 100%; font-size: 28rpx; color: #333; }
.textarea-box { height: 240rpx; padding: 20rpx 24rpx; align-items: flex-start; }
.textarea-box textarea { width: 100%; height: 100%; font-size: 28rpx; color: #333; }
.form-tip { font-size: 24rpx; color: #999999; margin-top: 12rpx; display: block; text-align: center; }
.submit-section { padding: 20rpx 24rpx; padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); }
.pay-amount { font-size: 32rpx; color: #e64340; font-weight: bold; display: block; text-align: center; margin-bottom: 20rpx; }
.submit-btn {
width: 240rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #007AFF;
color: #ffffff;
font-size: 30rpx;
border-radius: 40rpx;
border: none;
}
.submit-btn[disabled] {
opacity: 0.6;
width: 100%; height: 96rpx; line-height: 96rpx; background: #FAD146; color: #ffffff;
font-size: 32rpx; font-weight: bold; border-radius: 10rpx; border: none; text-align: center;
}
.submit-btn[disabled] { opacity: 0.6; }
</style>

View File

@ -1,5 +1,16 @@
<template>
<view class="cert-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">跑腿认证</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 已认证状态 -->
<view v-if="certStatus === 'Approved'" class="status-card success">
<text class="status-icon"></text>
@ -44,13 +55,17 @@ export default {
return {
certStatus: null,
form: { realName: '', phone: '' },
submitting: false
submitting: false,
statusBarHeight: 0
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadStatus()
},
methods: {
goBack() { uni.navigateBack() },
/** 加载认证状态 */
async loadStatus() {
try {
@ -91,6 +106,41 @@ export default {
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.cert-page {
min-height: 100vh;
background-color: #f5f5f5;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
miniapp/static/ic_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

BIN
miniapp/static/ic_back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -34,7 +34,7 @@ export const useUserStore = defineStore('user', () => {
token.value = ''
userInfo.value = {}
clearAuth()
uni.reLaunch({ url: '/pages/login/login' })
uni.reLaunch({ url: '/pages/index/index' })
}
/**

View File

@ -14,6 +14,14 @@ export function login(data) {
return request({ url: '/api/auth/login', method: 'POST', data })
}
/**
* 微信快捷登录仅需 code
* @param {Object} data - { code }
*/
export function wxLogin(data) {
return request({ url: '/api/auth/wx-login', method: 'POST', data })
}
// ==================== Banner ====================
/** 获取已启用的 Banner 列表 */
@ -188,3 +196,8 @@ export function getPrivacy() {
export function getWithdrawalGuide() {
return request({ url: '/api/config/withdrawal-guide' })
}
/** 获取页面顶图配置 */
export function getPageBanner(page) {
return request({ url: `/api/config/page-banner/${page}` })
}

View File

@ -2,6 +2,16 @@ using System.ComponentModel.DataAnnotations;
namespace CampusErrand.Models.Dtos;
/// <summary>
/// 微信快捷登录请求(仅需 code
/// </summary>
public class WxLoginRequest
{
/// <summary>微信 wx.login() 返回的 code</summary>
[Required(ErrorMessage = "code 不能为空")]
public string Code { get; set; } = string.Empty;
}
/// <summary>
/// 微信登录请求
/// </summary>

View File

@ -151,6 +151,56 @@ app.MapPost("/api/auth/login", async (
});
});
// 微信快捷登录(仅需 code通过 openid 注册/登录)
app.MapPost("/api/auth/wx-login", async (
WxLoginRequest request,
IWeChatService weChatService,
JwtService jwtService,
AppDbContext db) =>
{
// 1. 调用微信 code2Session 获取 openid
Console.WriteLine($"[wx-login] 收到 code: {request.Code}");
var sessionResult = await weChatService.Code2SessionAsync(request.Code);
if (!sessionResult.Success)
{
return Results.BadRequest(new { code = 400, message = sessionResult.ErrorMessage });
}
// 2. 根据 openid 查找或创建用户
var openId = sessionResult.OpenId!;
var user = await db.Users.FirstOrDefaultAsync(u => u.OpenId == openId);
if (user == null)
{
// 自动注册,手机号暂时留空
var randomSuffix = Random.Shared.Next(1000, 9999).ToString();
user = new User
{
OpenId = openId,
Phone = "",
Nickname = $"微信用户{randomSuffix}",
CreatedAt = DateTime.UtcNow
};
db.Users.Add(user);
await db.SaveChangesAsync();
}
// 3. 生成 JWT 返回
var token = jwtService.GenerateToken(user.Id, user.Role.ToString());
return Results.Ok(new LoginResponse
{
Token = token,
UserInfo = new UserInfo
{
Id = user.Id,
Phone = user.Phone,
Nickname = user.Nickname,
AvatarUrl = user.AvatarUrl,
Role = user.Role.ToString()
}
});
});
// 受保护的测试端点(用于验证认证和授权)
app.MapGet("/api/protected", () => Results.Ok("ok"))
.RequireAuthorization();
@ -2511,6 +2561,19 @@ app.MapPost("/api/upload/image", async (IFormFile file, IConfiguration config) =
// ========== 系统配置接口 ==========
// 获取页面顶图配置
app.MapGet("/api/config/page-banner/{page}", async (string page, AppDbContext db) =>
{
var key = $"page_banner_{page}";
var config = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == key);
return Results.Ok(new ConfigResponse
{
Key = key,
Value = config?.Value ?? "",
UpdatedAt = config?.UpdatedAt ?? DateTime.MinValue
});
});
// 获取客服二维码
app.MapGet("/api/config/qrcode", async (AppDbContext db) =>
{
@ -2577,7 +2640,9 @@ app.MapPut("/api/admin/config/{key}", async (string key, UpdateConfigRequest req
// 允许的配置键白名单
var allowedKeys = new HashSet<string>
{
"qrcode", "agreement", "privacy", "withdrawal_guide", "freeze_days"
"qrcode", "agreement", "privacy", "withdrawal_guide", "freeze_days",
"page_banner_pickup", "page_banner_delivery", "page_banner_help", "page_banner_purchase", "page_banner_food",
"page_banner_order-hall"
};
if (!allowedKeys.Contains(key))

View File

@ -38,10 +38,12 @@ public class WeChatService : IWeChatService
}
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine($"[WeChat] code2session 响应: {json}");
var result = JsonSerializer.Deserialize<WeChatCode2SessionResponse>(json);
if (result == null || result.ErrCode != 0)
{
Console.WriteLine($"[WeChat] code2session 失败: errcode={result?.ErrCode}, errmsg={result?.ErrMsg}");
return new WeChatSessionResult
{
Success = false,

View File

@ -19,8 +19,8 @@
"ExpireMinutes": 10080
},
"WeChat": {
"AppId": "your_wechat_appid",
"AppSecret": "your_wechat_appsecret"
"AppId": "wx626fd4a47944e3ea",
"AppSecret": "352b4f8238e43df92d5131ba31e6f33d"
},
"Upload": {
"MaxFileSizeBytes": 5242880,