diff --git a/admin/src/layout/AdminLayout.vue b/admin/src/layout/AdminLayout.vue index 34bbeb6..ff2bc81 100644 --- a/admin/src/layout/AdminLayout.vue +++ b/admin/src/layout/AdminLayout.vue @@ -38,6 +38,10 @@ + + + + @@ -95,7 +99,7 @@ import { ref } from 'vue' import { useRouter } from 'vue-router' import { ElMessageBox } from 'element-plus' -import { Monitor, Fold, Expand, ArrowDown, Picture, Grid, Shop, Stamp, User, Star, Bell, List, Setting, Money } from '@element-plus/icons-vue' +import { Monitor, Fold, Expand, ArrowDown, Picture, Grid, Shop, Stamp, User, UserFilled, Star, Bell, List, Setting, Money } from '@element-plus/icons-vue' const router = useRouter() const isCollapse = ref(false) diff --git a/admin/src/router/index.js b/admin/src/router/index.js index 6ec54a1..a1c7f18 100644 --- a/admin/src/router/index.js +++ b/admin/src/router/index.js @@ -54,6 +54,12 @@ const routes = [ component: () => import('../views/Runners.vue'), meta: { title: '跑腿管理' } }, + { + path: 'users', + name: 'Users', + component: () => import('../views/Users.vue'), + meta: { title: '用户管理' } + }, { path: 'reviews', name: 'Reviews', diff --git a/admin/src/views/Config.vue b/admin/src/views/Config.vue index 2d02eec..378ca29 100644 --- a/admin/src/views/Config.vue +++ b/admin/src/views/Config.vue @@ -5,6 +5,22 @@ + + +
+ 根据跑腿佣金金额匹配对应区间,计算平台抽成。最高金额留空或为0表示无上限。
+ · 百分比类型:抽成值填百分比数值,如填 10 表示抽成 10%(1元佣金抽0.1元)
+ · 固定金额类型:抽成值填固定金额,如填 2 表示固定抽 2元(不超过佣金本身)
+ · 跑腿实得 = 跑腿佣金 - 平台抽成 +
+
添加区间
diff --git a/admin/src/views/Users.vue b/admin/src/views/Users.vue new file mode 100644 index 0000000..e315eee --- /dev/null +++ b/admin/src/views/Users.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/miniapp/pages/delivery/delivery.vue b/miniapp/pages/delivery/delivery.vue index a1d8ef2..9a7cdbd 100644 --- a/miniapp/pages/delivery/delivery.vue +++ b/miniapp/pages/delivery/delivery.vue @@ -101,8 +101,20 @@ export default { const sysInfo = uni.getSystemInfoSync() this.statusBarHeight = sysInfo.statusBarHeight || 0 this.loadBanner() + this.restoreFormData() }, methods: { + /** 恢复登录前保存的表单数据 */ + restoreFormData() { + const saved = uni.getStorageSync('loginFormData') + if (saved) { + try { + const data = JSON.parse(saved) + Object.assign(this.form, data) + } catch (e) {} + uni.removeStorageSync('loginFormData') + } + }, goBack() { uni.navigateBack() }, async loadBanner() { try { @@ -137,12 +149,18 @@ export default { if (!this.form.phone.trim()) { uni.showToast({ title: '请输入手机号', icon: 'none' }); return false } + if (!/^1\d{10}$/.test(this.form.phone.trim())) { + uni.showToast({ title: '请输入正确的11位手机号', icon: 'none' }); return false + } return this.validateCommission() }, async onSubmit() { if (!this.validateForm()) return const token = uni.getStorageSync('token') if (!token) { + // 保存表单数据,登录后恢复 + uni.setStorageSync('loginFormData', JSON.stringify(this.form)) + uni.setStorageSync('loginRedirect', '/pages/delivery/delivery') uni.navigateTo({ url: '/pages/login/login' }); return } this.submitting = true diff --git a/miniapp/pages/food/food-order.vue b/miniapp/pages/food/food-order.vue index 54b850e..de1935d 100644 --- a/miniapp/pages/food/food-order.vue +++ b/miniapp/pages/food/food-order.vue @@ -142,6 +142,10 @@ export default { uni.showToast({ title: '请输入手机号', icon: 'none' }) return false } + if (!/^1\d{10}$/.test(this.form.phone.trim())) { + uni.showToast({ title: '请输入正确的11位手机号', icon: 'none' }) + return false + } return this.validateCommission() }, diff --git a/miniapp/pages/help/help.vue b/miniapp/pages/help/help.vue index 22ac64b..e57b76b 100644 --- a/miniapp/pages/help/help.vue +++ b/miniapp/pages/help/help.vue @@ -99,8 +99,20 @@ const sysInfo = uni.getSystemInfoSync() this.statusBarHeight = sysInfo.statusBarHeight || 0 this.loadBanner() + this.restoreFormData() }, methods: { + /** 恢复登录前保存的表单数据 */ + restoreFormData() { + const saved = uni.getStorageSync('loginFormData') + if (saved) { + try { + const data = JSON.parse(saved) + Object.assign(this.form, data) + } catch (e) {} + uni.removeStorageSync('loginFormData') + } + }, goBack() { uni.navigateBack() }, @@ -153,6 +165,13 @@ }) return false } + if (!/^1\d{10}$/.test(this.form.phone.trim())) { + uni.showToast({ + title: '请输入正确的11位手机号', + icon: 'none' + }) + return false + } if (!this.form.goodsAmount) { uni.showToast({ title: '请输入商品总金额', @@ -177,6 +196,9 @@ // 未登录跳转登录页 const token = uni.getStorageSync('token') if (!token) { + // 保存表单数据,登录后恢复 + uni.setStorageSync('loginFormData', JSON.stringify(this.form)) + uni.setStorageSync('loginRedirect', '/pages/help/help') uni.navigateTo({ url: '/pages/login/login' }) diff --git a/miniapp/pages/login/login.vue b/miniapp/pages/login/login.vue index bb38ec2..e703869 100644 --- a/miniapp/pages/login/login.vue +++ b/miniapp/pages/login/login.vue @@ -88,7 +88,14 @@ async function onWxLogin() { if (res.token && res.userInfo) { userStore.setLoginInfo(res.token, res.userInfo) uni.showToast({ title: '登录成功', icon: 'success' }) - uni.reLaunch({ url: '/pages/index/index' }) + // 检查是否有登录前的回跳页面 + const redirect = uni.getStorageSync('loginRedirect') + if (redirect) { + uni.removeStorageSync('loginRedirect') + uni.navigateTo({ url: redirect }) + } else { + uni.reLaunch({ url: '/pages/index/index' }) + } } else { tipText.value = '登录失败,请重试' } diff --git a/miniapp/pages/message/chat.vue b/miniapp/pages/message/chat.vue index e1023aa..bf57566 100644 --- a/miniapp/pages/message/chat.vue +++ b/miniapp/pages/message/chat.vue @@ -11,146 +11,159 @@ - - - - - {{ formatOrderType(orderInfo.orderType) }}订单 #{{ orderInfo.orderNo }} - - 查看详情 › - - - - 📞 拨打电话 + + + + + + + 订单类型: + {{ formatOrderType(orderInfo.orderType) }} + + + {{ getItemLabel(orderInfo.orderType) }}: + {{ orderInfo.itemName }} + + + 取货地址: + {{ orderInfo.pickupLocation }} + + + 送达地址: + {{ orderInfo.deliveryLocation }} + + + 备注信息: + {{ orderInfo.remark || '无' }} + - - 💬 联系客服 + + + 跑腿费: + {{ orderInfo.commission }}元 + + + 查看详情 + - + + + + 聊天安全提示:请友好和谐沟通。请友好和谐沟通。请友好和谐沟通。请友好和谐沟通。 + 加载中... - - - - {{ msg.content }} + + + + {{ msg.timeLabel }} + + + + + + {{ msg.content }} + - - - {{ msg.isSelf ? '您' : '对方' }}发起了{{ msg.changeTypeLabel }}改价申请 - - - - 原价 - ¥{{ msg.originalPrice }} + + + + {{ msg.isSelf ? '您' : '对方' }}发起了{{ msg.changeTypeLabel }}改价申请 - - 新价 - ¥{{ msg.newPrice }} + + + 原价 + ¥{{ msg.originalPrice }} + + + 新价 + ¥{{ msg.newPrice }} + + + {{ msg.difference > 0 ? '需补缴' : '将退款' }} + + ¥{{ Math.abs(msg.difference).toFixed(2) }} + + - - {{ msg.difference > 0 ? '需补缴' : '将退款' }} - - ¥{{ Math.abs(msg.difference).toFixed(2) }} + + + 同意 + + + 拒绝 + + + + 等待对方确认中 + + + + {{ msg.status === 'Accepted' ? '已同意' : '已拒绝' }} - - - 同意 - - - 拒绝 - - - - 等待对方确认中 - - - - {{ msg.status === 'Accepted' ? '已同意' : '已拒绝' }} - - - - + - - - - - + + + + + 拨打电话 + + + 联系客服 - - - - - 🖼️ - 发送图片 + + + + + + 发送 - - 💰 - 更改跑腿价格 + + - - 🏷️ - 更改商品价格 + + + + + + 发送图片 - - - 完成订单 + + + 更改商品价格 + + + + 更改跑腿价格 + + + + 完成订单 @@ -165,31 +178,19 @@ 新价格 - + - - 需补缴 ¥{{ priceDifference.toFixed(2) }}(将跳转支付) - - - 将退款 ¥{{ Math.abs(priceDifference).toFixed(2) }} - - - 对方需补缴 ¥{{ priceDifference.toFixed(2) }} - + 需补缴 + ¥{{ priceDifference.toFixed(2) }} + 将退款 + ¥{{ Math.abs(priceDifference).toFixed(2) }} + 对方需补缴 + ¥{{ priceDifference.toFixed(2) }} - - 取消 - - - 发起修改申请 - + 取消 + 发起修改申请 @@ -197,661 +198,927 @@ + .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; + height: 100vh; + background-color: #f5f5f5; + } + + /* 订单信息卡片 */ + .order-card { + margin: 16rpx 24rpx 0; + background: #fff; + border-radius: 16rpx; + padding: 24rpx; + flex-shrink: 0; + } + + .order-card-body { + display: flex; + } + + .order-card-left { + flex: 1; + min-width: 0; + } + + .order-info-row { + display: flex; + margin-bottom: 6rpx; + line-height: 1.6; + } + + .order-info-label { + font-size: 26rpx; + color: #666; + flex-shrink: 0; + } + + .order-info-val { + font-size: 26rpx; + color: #333; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .order-card-right { + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: space-between; + margin-left: 20rpx; + flex-shrink: 0; + } + + .order-commission { + display: flex; + align-items: baseline; + } + + .commission-label { + font-size: 24rpx; + color: #FF6600; + } + + .commission-value { + font-size: 36rpx; + color: #FF6600; + font-weight: bold; + } + + .order-detail-btn { + background: linear-gradient(135deg, #FFB700, #FF9500); + padding: 12rpx 28rpx; + border-radius: 8rpx; + } + + .order-detail-btn text { + font-size: 24rpx; + color: #fff; + font-weight: 500; + } + + /* 聊天安全提示 */ + .chat-safety-tip { + text-align: center; + padding: 16rpx 40rpx; + flex-shrink: 0; + } + + .chat-safety-tip text { + font-size: 22rpx; + color: #ccc; + line-height: 1.6; + } + + /* 聊天记录 */ + .chat-body { + flex: 1; + padding: 16rpx 0; + overflow-y: auto; + } + + .loading-history { + text-align: center; + padding: 16rpx 0; + font-size: 24rpx; + color: #999; + } + + .msg-time { + text-align: center; + margin: 16rpx 0; + } + + .msg-time text { + font-size: 22rpx; + color: #bbb; + } + + /* 消息行 */ + .msg-row { + display: flex; + align-items: flex-start; + margin-bottom: 24rpx; + padding: 0 8rpx; + } + + .msg-row.msg-self { + flex-direction: row-reverse; + } + + .msg-avatar { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + flex-shrink: 0; + } + + .msg-bubble { + max-width: 60%; + margin: 0 16rpx; + } + + .msg-text { + background-color: #fff; + padding: 20rpx 24rpx; + border-radius: 20rpx; + font-size: 28rpx; + color: #333; + word-break: break-all; + display: block; + line-height: 1.5; + } + + .msg-self .msg-text { + background-color: #FFB700; + color: #fff; + } + + .msg-image { + max-width: 100%; + border-radius: 12rpx; + } + + /* 系统消息居中 */ + .msg-system-wrap { + display: flex; + justify-content: center; + margin-bottom: 20rpx; + } + + .msg-system { + background-color: #f0f0f0; + padding: 10rpx 20rpx; + border-radius: 8rpx; + } + + .msg-system text { + font-size: 24rpx; + color: #999; + } + + /* 快捷操作栏 */ + .quick-actions { + display: flex; + gap: 20rpx; + padding: 12rpx 24rpx; + flex-shrink: 0; + background: #f5f5f5; + } + + .quick-btn { + padding: 10rpx 24rpx; + border: 1rpx solid #ddd; + border-radius: 8rpx; + background: #fff; + } + + .quick-btn text { + font-size: 22rpx; + color: #333; + } + + /* 底部输入区域 */ + .chat-bottom { + background: #fff; + border-top: 1rpx solid #eee; + flex-shrink: 0; + padding-bottom: env(safe-area-inset-bottom); + } + + .footer-input-row { + display: flex; + align-items: center; + padding: 16rpx 20rpx; + gap: 12rpx; + } + + .chat-input { + flex: 1; + height: 72rpx; + background-color: #f5f5f5; + border-radius: 36rpx; + padding: 0 24rpx; + font-size: 28rpx; + } + + .send-btn { + background: linear-gradient(135deg, #FFB700, #FF9500); + padding: 0 32rpx; + height: 72rpx; + line-height: 72rpx; + border-radius: 36rpx; + flex-shrink: 0; + } + + .send-btn text { + font-size: 28rpx; + color: #fff; + font-weight: 500; + } + + .plus-btn { + width: 64rpx; + height: 64rpx; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: transform 0.2s; + } + + .plus-btn.active { + transform: rotate(45deg); + } + + .plus-icon { + width: 48rpx; + height: 48rpx; + } + + /* 内嵌更多面板 */ + .more-panel { + display: flex; + flex-wrap: wrap; + padding: 20rpx 10rpx 10rpx; + border-top: 1rpx solid #f0f0f0; + } + + .panel-item { + width: 25%; + display: flex; + flex-direction: column; + align-items: center; + padding: 16rpx 0; + } + + .panel-icon-wrap { + width: 96rpx; + height: 96rpx; + background: #f5f5f5; + border-radius: 20rpx; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 10rpx; + } + + .panel-icon-img { + width: 48rpx; + height: 48rpx; + } + + .panel-label { + font-size: 22rpx; + color: #666; + } + + /* 改价消息卡片 */ + .price-change-card { + background: #fff; + border-radius: 16rpx; + padding: 24rpx; + width: 80%; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); + } + + .pc-header { + margin-bottom: 16rpx; + } + + .pc-title { + font-size: 26rpx; + color: #333; + font-weight: 500; + } + + .pc-body { + margin-bottom: 16rpx; + } + + .pc-row { + display: flex; + justify-content: space-between; + padding: 8rpx 0; + } + + .pc-label { + font-size: 24rpx; + color: #999; + } + + .pc-value { + font-size: 26rpx; + color: #333; + } + + .pc-new-price { + color: #FFB700; + font-weight: 500; + } + + .pc-pay { + color: #ff6600; + } + + .pc-refund { + color: #52c41a; + } + + .pc-actions { + display: flex; + gap: 16rpx; + } + + .pc-btn { + flex: 1; + padding: 16rpx 0; + border-radius: 8rpx; + text-align: center; + font-size: 26rpx; + } + + .pc-accept { + background: #FFB700; + color: #fff; + } + + .pc-reject { + background: #f5f5f5; + color: #666; + } + + .pc-waiting { + text-align: center; + padding: 12rpx 0; + } + + .pc-waiting text { + font-size: 24rpx; + color: #ff9900; + } + + .pc-result { + text-align: center; + padding: 12rpx 0; + } + + .pc-accepted { + font-size: 24rpx; + color: #52c41a; + } + + .pc-rejected { + font-size: 24rpx; + color: #ff4d4f; + } + + /* 改价弹窗 */ + .popup-mask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 200; + display: flex; + align-items: center; + justify-content: center; + } + + .popup-content { + width: 80%; + background: #fff; + border-radius: 20rpx; + padding: 40rpx; + } + + .popup-title { + font-size: 32rpx; + color: #333; + font-weight: 500; + display: block; + text-align: center; + margin-bottom: 30rpx; + } + + .popup-field { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20rpx 0; + border-bottom: 1rpx solid #f0f0f0; + } + + .popup-label { + font-size: 28rpx; + color: #666; + } + + .popup-current-price { + font-size: 28rpx; + color: #333; + } + + .popup-input { + width: 50%; + height: 64rpx; + border: 1rpx solid #ddd; + border-radius: 8rpx; + padding: 0 16rpx; + font-size: 28rpx; + text-align: right; + } + + .popup-diff { + padding: 16rpx 0; + text-align: center; + } + + .popup-diff text { + font-size: 24rpx; + } + + .popup-actions { + display: flex; + gap: 20rpx; + margin-top: 30rpx; + } + + .popup-btn { + flex: 1; + padding: 20rpx 0; + border-radius: 12rpx; + text-align: center; + font-size: 28rpx; + } + + .popup-cancel { + background: #f5f5f5; + color: #666; + } + + .popup-confirm { + background: #FAD146; + color: #fff; + } + \ No newline at end of file diff --git a/miniapp/pages/mine/mine.vue b/miniapp/pages/mine/mine.vue index f8cb51a..e627592 100644 --- a/miniapp/pages/mine/mine.vue +++ b/miniapp/pages/mine/mine.vue @@ -11,7 +11,10 @@ - {{ isLoggedIn ? (userInfo.nickname || '用户') : '点击注册/登录' }} + @@ -216,13 +219,24 @@ export default { background-color: #f0f0f0; } -.user-name { +.user-info { flex: 1; + display: flex; + flex-direction: column; +} + +.user-name { font-size: 32rpx; color: #333; font-weight: 500; } +.user-uid { + font-size: 24rpx; + color: #999; + margin-top: 6rpx; +} + .arrow-icon { width: 32rpx; height: 32rpx; diff --git a/miniapp/pages/order-hall/order-hall.vue b/miniapp/pages/order-hall/order-hall.vue index 5960b9c..03a6613 100644 --- a/miniapp/pages/order-hall/order-hall.vue +++ b/miniapp/pages/order-hall/order-hall.vue @@ -296,6 +296,12 @@ }, /** 点击接单 */ async onAcceptClick(order) { + // 未登录跳转登录页 + const token = uni.getStorageSync('token') + if (!token) { + uni.navigateTo({ url: '/pages/login/login' }) + return + } try { if (this.certStatus === null) { const res = await getCertificationStatus() diff --git a/miniapp/pages/order/order-detail.vue b/miniapp/pages/order/order-detail.vue index 8ee98db..2166e23 100644 --- a/miniapp/pages/order/order-detail.vue +++ b/miniapp/pages/order/order-detail.vue @@ -50,6 +50,11 @@ {{ order.remark }} + + 联系方式 + {{ order.phone }} + + 商品金额 ¥{{ order.goodsAmount }} @@ -60,6 +65,16 @@ ¥{{ order.commission }} + + 平台抽成 + -¥{{ order.platformFee }} + + + + 跑腿实得 + ¥{{ order.netEarning }} + + 支付总额 ¥{{ order.totalAmount }} @@ -536,6 +551,11 @@ export default { font-weight: bold; } +.info-value.highlight { + color: #52c41a; + font-weight: bold; +} + /* 完成凭证 */ .proof-image { width: 100%; diff --git a/miniapp/pages/pickup/pickup.vue b/miniapp/pages/pickup/pickup.vue index 75efed6..e8aa739 100644 --- a/miniapp/pages/pickup/pickup.vue +++ b/miniapp/pages/pickup/pickup.vue @@ -101,8 +101,20 @@ export default { const sysInfo = uni.getSystemInfoSync() this.statusBarHeight = sysInfo.statusBarHeight || 0 this.loadBanner() + this.restoreFormData() }, methods: { + /** 恢复登录前保存的表单数据 */ + restoreFormData() { + const saved = uni.getStorageSync('loginFormData') + if (saved) { + try { + const data = JSON.parse(saved) + Object.assign(this.form, data) + } catch (e) {} + uni.removeStorageSync('loginFormData') + } + }, goBack() { uni.navigateBack() }, async loadBanner() { try { @@ -140,6 +152,9 @@ export default { if (!this.form.phone.trim()) { uni.showToast({ title: '请输入手机号', icon: 'none' }); return false } + if (!/^1\d{10}$/.test(this.form.phone.trim())) { + uni.showToast({ title: '请输入正确的11位手机号', icon: 'none' }); return false + } return this.validateCommission() }, async onSubmit() { @@ -148,6 +163,9 @@ export default { // 未登录跳转登录页 const token = uni.getStorageSync('token') if (!token) { + // 保存表单数据,登录后恢复 + uni.setStorageSync('loginFormData', JSON.stringify(this.form)) + uni.setStorageSync('loginRedirect', '/pages/pickup/pickup') uni.navigateTo({ url: '/pages/login/login' }) return } diff --git a/miniapp/pages/purchase/purchase.vue b/miniapp/pages/purchase/purchase.vue index bd4f997..762a80d 100644 --- a/miniapp/pages/purchase/purchase.vue +++ b/miniapp/pages/purchase/purchase.vue @@ -114,8 +114,20 @@ const sysInfo = uni.getSystemInfoSync() this.statusBarHeight = sysInfo.statusBarHeight || 0 this.loadBanner() + this.restoreFormData() }, methods: { + /** 恢复登录前保存的表单数据 */ + restoreFormData() { + const saved = uni.getStorageSync('loginFormData') + if (saved) { + try { + const data = JSON.parse(saved) + Object.assign(this.form, data) + } catch (e) {} + uni.removeStorageSync('loginFormData') + } + }, goBack() { uni.navigateBack() }, @@ -180,6 +192,13 @@ }); return false } + if (!/^1\d{10}$/.test(this.form.phone.trim())) { + uni.showToast({ + title: '请输入正确的11位手机号', + icon: 'none' + }); + return false + } if (!this.form.goodsAmount) { uni.showToast({ title: '请输入商品总金额', @@ -201,6 +220,9 @@ if (!this.validateForm()) return const token = uni.getStorageSync('token') if (!token) { + // 保存表单数据,登录后恢复 + uni.setStorageSync('loginFormData', JSON.stringify(this.form)) + uni.setStorageSync('loginRedirect', '/pages/purchase/purchase') uni.navigateTo({ url: '/pages/login/login' }); diff --git a/miniapp/static/ic_commodity.png b/miniapp/static/ic_commodity.png new file mode 100644 index 0000000..3bdc5a5 Binary files /dev/null and b/miniapp/static/ic_commodity.png differ diff --git a/miniapp/static/ic_complete.png b/miniapp/static/ic_complete.png new file mode 100644 index 0000000..550adc5 Binary files /dev/null and b/miniapp/static/ic_complete.png differ diff --git a/miniapp/static/ic_errand.png b/miniapp/static/ic_errand.png new file mode 100644 index 0000000..535816a Binary files /dev/null and b/miniapp/static/ic_errand.png differ diff --git a/miniapp/static/ic_picture.png b/miniapp/static/ic_picture.png new file mode 100644 index 0000000..7a2f8d6 Binary files /dev/null and b/miniapp/static/ic_picture.png differ diff --git a/miniapp/static/logo.png b/miniapp/static/logo.png index b5771e2..cafbb83 100644 Binary files a/miniapp/static/logo.png and b/miniapp/static/logo.png differ diff --git a/miniapp/utils/api.js b/miniapp/utils/api.js index 45b449f..cb509e5 100644 --- a/miniapp/utils/api.js +++ b/miniapp/utils/api.js @@ -88,6 +88,11 @@ export function getOrderDetail(id) { return request({ url: `/api/orders/${id}` }) } +/** 根据聊天对方用户ID查找关联订单 */ +export function getOrderByChatUser(targetUserId) { + return request({ url: `/api/orders/by-chat-user/${targetUserId}` }) +} + // ==================== 美食街 ==================== /** 获取门店列表 */ diff --git a/miniapp/utils/request.js b/miniapp/utils/request.js index 18cfdb5..19c6591 100644 --- a/miniapp/utils/request.js +++ b/miniapp/utils/request.js @@ -7,6 +7,7 @@ // API 基础地址,按环境切换 const BASE_URL = 'http://localhost:5099' +// const BASE_URL = 'http://api.zwz.shhmkjgs.cn' /** * 获取本地存储的 token diff --git a/server/Models/Dtos/OrderDtos.cs b/server/Models/Dtos/OrderDtos.cs index f67f4b6..647529c 100644 --- a/server/Models/Dtos/OrderDtos.cs +++ b/server/Models/Dtos/OrderDtos.cs @@ -85,6 +85,10 @@ public class OrderResponse public int? RunnerUid { get; set; } /// 跑腿手机号(认证时填写的手机号,仅单主可见) public string? RunnerPhone { get; set; } + /// 平台抽成(订单完成后可见) + public decimal? PlatformFee { get; set; } + /// 跑腿实得佣金(订单完成后可见) + public decimal? NetEarning { get; set; } public List? FoodItems { get; set; } } diff --git a/server/Program.cs b/server/Program.cs index a6dbb9c..0762037 100644 --- a/server/Program.cs +++ b/server/Program.cs @@ -671,6 +671,31 @@ app.MapPost("/api/orders", async (CreateOrderRequest request, HttpContext httpCo }); }).RequireAuthorization(); +// 根据对方用户ID查找最近的关联订单(用于聊天页显示订单卡片) +app.MapGet("/api/orders/by-chat-user/{targetUserId}", async (int targetUserId, HttpContext httpContext, AppDbContext db) => +{ + var userIdClaim = httpContext.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier); + if (userIdClaim == null) return Results.Unauthorized(); + var currentUserId = int.Parse(userIdClaim.Value); + + // 查找当前用户与目标用户之间最近的订单(当前用户是单主对方是跑腿,或反过来) + var order = await db.Orders + .Where(o => + (o.OwnerId == currentUserId && o.RunnerId == targetUserId) || + (o.OwnerId == targetUserId && o.RunnerId == currentUserId)) + .OrderByDescending(o => o.CreatedAt) + .FirstOrDefaultAsync(); + + if (order == null) + return Results.Ok(new { found = false }); + + return Results.Ok(new + { + found = true, + orderId = order.Id + }); +}).RequireAuthorization(); + // 获取订单详情(含手机号隐藏逻辑和按状态显示字段) app.MapGet("/api/orders/{id}", async (int id, HttpContext httpContext, AppDbContext db) => { @@ -765,6 +790,27 @@ app.MapGet("/api/orders/{id}", async (int id, HttpContext httpContext, AppDbCont RunnerPhone = runnerPhone }; + // 查询佣金抽成信息(有 Earning 记录用实际值,否则实时计算预估值) + if (order.RunnerId.HasValue) + { + var earning = await db.Earnings + .Where(e => e.OrderId == order.Id) + .FirstOrDefaultAsync(); + if (earning != null) + { + response.PlatformFee = earning.PlatformFee; + response.NetEarning = earning.NetEarning; + } + else + { + // 未完成订单:实时计算预估抽成 + var rules = await db.CommissionRules.OrderBy(r => r.MinAmount).ToListAsync(); + var fee = CalculatePlatformFee(order.Commission, rules); + response.PlatformFee = fee; + response.NetEarning = order.Commission - fee; + } + } + // 美食街订单附带菜品详情 if (order.OrderType == OrderType.Food && order.FoodOrderItems.Count > 0) { @@ -864,9 +910,7 @@ app.MapGet("/api/orders/hall", async ( }).ToList(); return Results.Ok(result); -}).RequireAuthorization(); - -// 接取订单 +}).AllowAnonymous(); app.MapPost("/api/orders/{id}/accept", async (int id, HttpContext httpContext, AppDbContext db) => { // 获取当前用户 @@ -2488,6 +2532,59 @@ app.MapPost("/api/admin/notifications", async (CreateNotificationRequest request }); }).RequireAuthorization("AdminOnly"); +// ========== 用户管理接口 ========== + +// 管理端获取用户列表 +app.MapGet("/api/admin/users", async (string? keyword, AppDbContext db) => +{ + var query = db.Users.AsQueryable(); + + // 关键词搜索(昵称、手机号、ID) + if (!string.IsNullOrWhiteSpace(keyword)) + { + var kw = keyword.Trim(); + if (int.TryParse(kw, out var uid)) + { + query = query.Where(u => u.Id == uid || u.Nickname.Contains(kw) || u.Phone.Contains(kw)); + } + else + { + query = query.Where(u => u.Nickname.Contains(kw) || u.Phone.Contains(kw)); + } + } + + var users = await query.OrderByDescending(u => u.CreatedAt) + .Select(u => new + { + u.Id, + u.Nickname, + u.AvatarUrl, + u.Phone, + Role = u.Role.ToString(), + u.RunnerScore, + u.IsBanned, + u.CreatedAt, + // 查询该用户的订单数 + OrderCount = db.Orders.Count(o => o.OwnerId == u.Id) + }) + .ToListAsync(); + + return Results.Ok(users); +}).RequireAuthorization("AdminOnly"); + +// 管理端封禁/解封用户 +app.MapPut("/api/admin/users/{id}/ban", async (int id, BanRunnerRequest request, AppDbContext db) => +{ + var user = await db.Users.FindAsync(id); + if (user == null) + return Results.NotFound(new { code = 404, message = "用户不存在" }); + + user.IsBanned = request.IsBanned; + await db.SaveChangesAsync(); + + return Results.Ok(new { user.Id, user.IsBanned }); +}).RequireAuthorization("AdminOnly"); + // ========== 跑腿管理接口 ========== // 管理端获取跑腿列表 @@ -2884,6 +2981,20 @@ using (var scope = app.Services.CreateScope()) { db.Database.Migrate(); } + + // 初始化默认佣金规则(如果为空) + if (!db.CommissionRules.Any()) + { + db.CommissionRules.Add(new CommissionRule + { + MinAmount = 0m, + MaxAmount = null, + RateType = CommissionRateType.Percentage, + Rate = 10m // 默认抽成 10% + }); + db.SaveChanges(); + Console.WriteLine("[初始化] 已创建默认佣金规则:10% 抽成"); + } } // 注册后台定时任务:每10分钟执行一次自动确认和解冻