feat: 新增多角色系统(派单/分润/推广/区域)
- 后端:RlzDispatch/Profit/Promotion/Region 控制器、服务、Mapper、领域模型 - 权限:MethodSecurityConfig + PermissionInterceptor - 管理后台:派单/分润/推广/区域 页面与 API - Android 陪护端:派单/运营/退款 Fragment 及布局 - 小程序:推广员子包、派单/运营/退款/检索子包、自定义 tabBar - 文档:多角色系统方案、角色权限矩阵、用户操作手册等 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
36
coupon/subPackages/pages-promoter/commission/commission.js
Normal file
36
coupon/subPackages/pages-promoter/commission/commission.js
Normal file
@@ -0,0 +1,36 @@
|
||||
var { request } = require('../../../utils/request');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
records: [],
|
||||
loading: true
|
||||
},
|
||||
|
||||
onShow() { this.loadRecords(); },
|
||||
|
||||
onPullDownRefresh() { this.loadRecords().finally(function() { wx.stopPullDownRefresh(); }); },
|
||||
|
||||
async loadRecords() {
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
var res = await request({ url: '/system/promotion/myCommission', method: 'GET', loading: false });
|
||||
if (res.code === 200) {
|
||||
this.setData({ records: res.data || [], loading: false });
|
||||
}
|
||||
} catch (e) { this.setData({ loading: false }); }
|
||||
},
|
||||
|
||||
goWithdraw() {
|
||||
wx.navigateTo({ url: '/shopping/pages/withdrawal/withdrawal' });
|
||||
},
|
||||
|
||||
getTypeLabel(t) {
|
||||
var map = { 'promotion_l1': '一级推广', 'promotion_l2': '二级推广', 'promotion_l3': '三级推广' };
|
||||
return map[t] || t || '推广佣金';
|
||||
},
|
||||
|
||||
getStatusLabel(s) {
|
||||
var map = { '0': '待结算', '1': '已结算', '7': '已退回' };
|
||||
return map[s] || s || '待结算';
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
27
coupon/subPackages/pages-promoter/commission/commission.wxml
Normal file
27
coupon/subPackages/pages-promoter/commission/commission.wxml
Normal file
@@ -0,0 +1,27 @@
|
||||
<view class="container">
|
||||
<view class="summary-bar">
|
||||
<text class="summary-label">推广佣金明细</text>
|
||||
<text class="withdraw-btn" bindtap="goWithdraw">提现</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading">加载中...</view>
|
||||
|
||||
<block wx:for="{{records}}" wx:key="id">
|
||||
<view class="record-card">
|
||||
<view class="record-row">
|
||||
<view class="record-type">
|
||||
<text class="type-tag">{{item.profitType === 'promotion_l1' ? '一级' : item.profitType === 'promotion_l2' ? '二级' : item.profitType === 'promotion_l3' ? '三级' : '推广'}}</text>
|
||||
<text class="order-id">订单 #{{item.orderId}}</text>
|
||||
</view>
|
||||
<text class="record-amount">¥{{item.amount || '0'}}</text>
|
||||
</view>
|
||||
<view class="record-row sub">
|
||||
<text class="record-rate">比例 {{item.rate || '0'}}%</text>
|
||||
<text class="record-status {{item.status === '1' ? 'settled' : item.status === '7' ? 'reversed' : 'pending'}}">{{item.status === '1' ? '已结算' : item.status === '7' ? '已退回' : '待结算'}}</text>
|
||||
</view>
|
||||
<view wx:if="{{item.createTime}}" class="record-time">{{item.createTime}}</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view wx:if="{{!loading && records.length === 0}}" class="empty">暂无佣金记录</view>
|
||||
</view>
|
||||
20
coupon/subPackages/pages-promoter/commission/commission.wxss
Normal file
20
coupon/subPackages/pages-promoter/commission/commission.wxss
Normal file
@@ -0,0 +1,20 @@
|
||||
.container { padding: 0 0 40rpx 0; background: #F5F5F5; min-height: 100vh; }
|
||||
.summary-bar { display: flex; justify-content: space-between; align-items: center; padding: 24rpx 32rpx; background: #fff; margin-bottom: 4rpx; }
|
||||
.summary-label { font-size: 32rpx; font-weight: 700; color: #333; }
|
||||
.withdraw-btn { font-size: 26rpx; color: #fff; background: #FF8F1F; padding: 10rpx 24rpx; border-radius: 32rpx; }
|
||||
|
||||
.loading { text-align: center; padding: 80rpx; color: #999; }
|
||||
.record-card { background: #fff; margin: 12rpx 0 0 0; padding: 28rpx 32rpx; }
|
||||
.record-row { display: flex; justify-content: space-between; align-items: center; }
|
||||
.record-row.sub { margin-top: 12rpx; }
|
||||
.record-type { display: flex; align-items: center; }
|
||||
.type-tag { font-size: 22rpx; background: #FFF3E0; color: #FF9800; padding: 4rpx 12rpx; border-radius: 8rpx; }
|
||||
.order-id { font-size: 24rpx; color: #999; margin-left: 12rpx; }
|
||||
.record-amount { font-size: 34rpx; font-weight: 700; color: #333; }
|
||||
.record-rate { font-size: 24rpx; color: #999; }
|
||||
.record-status { font-size: 22rpx; padding: 4rpx 12rpx; border-radius: 8rpx; }
|
||||
.record-status.settled { background: #E8F5E9; color: #4CAF50; }
|
||||
.record-status.pending { background: #FFF3E0; color: #FF9800; }
|
||||
.record-status.reversed { background: #FFEBEE; color: #F44336; }
|
||||
.record-time { font-size: 22rpx; color: #C0C4CC; margin-top: 12rpx; }
|
||||
.empty { text-align: center; padding: 120rpx; color: #999; font-size: 28rpx; }
|
||||
27
coupon/subPackages/pages-promoter/dashboard/dashboard.js
Normal file
27
coupon/subPackages/pages-promoter/dashboard/dashboard.js
Normal file
@@ -0,0 +1,27 @@
|
||||
var app = getApp();
|
||||
var { request } = require('../../../utils/request');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
stats: { inviteCount: 0, teamCount: 0, totalCommission: '0', settledCommission: '0', pendingCommission: '0', orderCount: 0, promotionCode: '' },
|
||||
loading: true
|
||||
},
|
||||
|
||||
onShow() { this.loadStats(); },
|
||||
|
||||
onPullDownRefresh() { this.loadStats().finally(function() { wx.stopPullDownRefresh(); }); },
|
||||
|
||||
async loadStats() {
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
var res = await request({ url: '/system/promotion/myStats', method: 'GET', loading: false });
|
||||
if (res.code === 200 && res.data) {
|
||||
this.setData({ stats: res.data, loading: false });
|
||||
}
|
||||
} catch (e) { this.setData({ loading: false }); }
|
||||
},
|
||||
|
||||
goTeam() { wx.navigateTo({ url: '/subPackages/pages-promoter/team/team' }); },
|
||||
goCommission() { wx.navigateTo({ url: '/subPackages/pages-promoter/commission/commission' }); },
|
||||
goShare() { wx.navigateTo({ url: '/subPackages/pages-promoter/share/share' }); }
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
51
coupon/subPackages/pages-promoter/dashboard/dashboard.wxml
Normal file
51
coupon/subPackages/pages-promoter/dashboard/dashboard.wxml
Normal file
@@ -0,0 +1,51 @@
|
||||
<view class="container">
|
||||
<view class="top-banner">
|
||||
<view class="banner-title">推广数据看板</view>
|
||||
<view class="banner-code">邀请码:{{stats.promotionCode || '暂无'}}</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-grid">
|
||||
<view class="stat-card">
|
||||
<view class="stat-num">{{stats.inviteCount || 0}}</view>
|
||||
<view class="stat-label">直接邀请</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-num">{{stats.teamCount || 0}}</view>
|
||||
<view class="stat-label">团队总人数</view>
|
||||
</view>
|
||||
<view class="stat-card" bindtap="goCommission">
|
||||
<view class="stat-num gold">¥{{stats.totalCommission || '0'}}</view>
|
||||
<view class="stat-label">佣金总额</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-num">{{stats.orderCount || 0}}</view>
|
||||
<view class="stat-label">推广订单</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="income-summary">
|
||||
<view class="income-row">
|
||||
<text class="income-label">已结算佣金</text>
|
||||
<text class="income-val green">¥{{stats.settledCommission || '0'}}</text>
|
||||
</view>
|
||||
<view class="income-row">
|
||||
<text class="income-label">待结算佣金</text>
|
||||
<text class="income-val orange">¥{{stats.pendingCommission || '0'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-grid">
|
||||
<view class="action-item" bindtap="goTeam">
|
||||
<view class="action-icon"><text>👥</text></view>
|
||||
<text class="action-text">我的团队</text>
|
||||
</view>
|
||||
<view class="action-item" bindtap="goCommission">
|
||||
<view class="action-icon"><text>💰</text></view>
|
||||
<text class="action-text">佣金明细</text>
|
||||
</view>
|
||||
<view class="action-item" bindtap="goShare">
|
||||
<view class="action-icon"><text>📤</text></view>
|
||||
<text class="action-text">分享推广</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
24
coupon/subPackages/pages-promoter/dashboard/dashboard.wxss
Normal file
24
coupon/subPackages/pages-promoter/dashboard/dashboard.wxss
Normal file
@@ -0,0 +1,24 @@
|
||||
.container { padding: 0 0 40rpx 0; background: #F5F5F5; min-height: 100vh; }
|
||||
.top-banner { background: linear-gradient(135deg, #FF6A6A, #FF8F1F); color: #fff; padding: 48rpx 32rpx 40rpx; }
|
||||
.banner-title { font-size: 36rpx; font-weight: 800; }
|
||||
.banner-code { font-size: 24rpx; margin-top: 8rpx; opacity: 0.85; }
|
||||
|
||||
.stats-grid { display: flex; flex-wrap: wrap; margin: -20rpx 24rpx 0; }
|
||||
.stat-card { width: 44%; margin: 8rpx 3%; background: #fff; border-radius: 16rpx; padding: 28rpx 24rpx; text-align: center; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
|
||||
.stat-num { font-size: 40rpx; font-weight: 800; color: #333; }
|
||||
.stat-num.gold { color: #FF8F1F; }
|
||||
.stat-label { font-size: 22rpx; color: #999; margin-top: 8rpx; }
|
||||
|
||||
.income-summary { margin: 24rpx 32rpx; background: #fff; border-radius: 16rpx; padding: 24rpx 32rpx; }
|
||||
.income-row { display: flex; justify-content: space-between; padding: 16rpx 0; border-bottom: 1rpx solid #F5F5F5; }
|
||||
.income-row:last-child { border-bottom: none; }
|
||||
.income-label { font-size: 28rpx; color: #333; }
|
||||
.income-val { font-size: 28rpx; font-weight: 700; }
|
||||
.income-val.green { color: #67C23A; }
|
||||
.income-val.orange { color: #E6A23C; }
|
||||
|
||||
.action-grid { display: flex; margin: 24rpx 32rpx; }
|
||||
.action-item { flex: 1; background: #fff; border-radius: 16rpx; padding: 32rpx 0; text-align: center; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
|
||||
.action-item + .action-item { margin-left: 16rpx; }
|
||||
.action-icon { font-size: 44rpx; }
|
||||
.action-text { font-size: 24rpx; color: #333; margin-top: 12rpx; display: block; }
|
||||
54
coupon/subPackages/pages-promoter/share/share.js
Normal file
54
coupon/subPackages/pages-promoter/share/share.js
Normal file
@@ -0,0 +1,54 @@
|
||||
var { request } = require('../../../utils/request');
|
||||
var app = getApp();
|
||||
|
||||
Page({
|
||||
data: {
|
||||
promotionCode: '',
|
||||
loading: false
|
||||
},
|
||||
|
||||
onShow() { this.loadCode(); },
|
||||
|
||||
async loadCode() {
|
||||
try {
|
||||
var res = await request({ url: '/system/promotion/myStats', method: 'GET', loading: false });
|
||||
if (res.code === 200 && res.data) {
|
||||
this.setData({ promotionCode: res.data.promotionCode || '' });
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
// 生成/刷新邀请码
|
||||
async generateCode() {
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
var res = await request({ url: '/system/promotion/generateCode', method: 'POST', loading: false });
|
||||
if (res.code === 200) {
|
||||
this.setData({ promotionCode: res.data || '' });
|
||||
wx.showToast({ title: '邀请码已生成', icon: 'success' });
|
||||
}
|
||||
} catch (e) {}
|
||||
this.setData({ loading: false });
|
||||
},
|
||||
|
||||
// 复制邀请码
|
||||
copyCode() {
|
||||
if (!this.data.promotionCode) {
|
||||
wx.showToast({ title: '请先生成邀请码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
wx.setClipboardData({
|
||||
data: this.data.promotionCode,
|
||||
success: function() { wx.showToast({ title: '邀请码已复制', icon: 'success' }); }
|
||||
});
|
||||
},
|
||||
|
||||
// 分享消息卡片
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '加入瑞莱健康陪诊平台,一起赚佣金!',
|
||||
path: '/pages/index/index?promotionCode=' + this.data.promotionCode,
|
||||
imageUrl: '/pages/image/home/home-banner.png'
|
||||
};
|
||||
}
|
||||
});
|
||||
1
coupon/subPackages/pages-promoter/share/share.json
Normal file
1
coupon/subPackages/pages-promoter/share/share.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
45
coupon/subPackages/pages-promoter/share/share.wxml
Normal file
45
coupon/subPackages/pages-promoter/share/share.wxml
Normal file
@@ -0,0 +1,45 @@
|
||||
<view class="container">
|
||||
<view class="share-banner">
|
||||
<view class="banner-text">邀请好友加入推广</view>
|
||||
<view class="banner-sub">好友下单后您可获得佣金</view>
|
||||
</view>
|
||||
|
||||
<view class="code-section">
|
||||
<view class="code-label">我的邀请码</view>
|
||||
<view class="code-box">
|
||||
<text class="code-value">{{promotionCode || '暂未生成'}}</text>
|
||||
</view>
|
||||
<view class="code-actions">
|
||||
<button class="gen-btn {{loading ? 'disabled' : ''}}" bindtap="generateCode" disabled="{{loading}}">
|
||||
{{promotionCode ? '刷新邀请码' : (loading ? '生成中...' : '生成邀请码')}}
|
||||
</button>
|
||||
<button wx:if="{{promotionCode}}" class="copy-btn" bindtap="copyCode">复制邀请码</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="share-section">
|
||||
<view class="section-title">分享方式</view>
|
||||
<view class="share-tips">
|
||||
<view class="tip-item">
|
||||
<text class="tip-icon">1</text>
|
||||
<text class="tip-text">点击右上角「...」发送给朋友</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<text class="tip-icon">2</text>
|
||||
<text class="tip-text">好友通过邀请码注册即可绑定推广关系</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<text class="tip-icon">3</text>
|
||||
<text class="tip-text">好友下单后您获得三级推广佣金</text>
|
||||
</view>
|
||||
</view>
|
||||
<button class="share-btn" open-type="share">分享给好友</button>
|
||||
</view>
|
||||
|
||||
<view class="commission-rules">
|
||||
<view class="section-title">推广佣金规则</view>
|
||||
<view class="rule-row"><text class="rule-lv">一级推广</text><text class="rule-pct">订单金额的 5%</text></view>
|
||||
<view class="rule-row"><text class="rule-lv">二级推广</text><text class="rule-pct">订单金额的 3%</text></view>
|
||||
<view class="rule-row"><text class="rule-lv">三级推广</text><text class="rule-pct">订单金额的 2%</text></view>
|
||||
</view>
|
||||
</view>
|
||||
27
coupon/subPackages/pages-promoter/share/share.wxss
Normal file
27
coupon/subPackages/pages-promoter/share/share.wxss
Normal file
@@ -0,0 +1,27 @@
|
||||
.container { padding: 0 0 60rpx 0; background: #F5F5F5; min-height: 100vh; }
|
||||
.share-banner { background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; padding: 48rpx 32rpx; text-align: center; }
|
||||
.banner-text { font-size: 36rpx; font-weight: 800; }
|
||||
.banner-sub { font-size: 24rpx; margin-top: 12rpx; opacity: 0.85; }
|
||||
|
||||
.code-section { margin: 24rpx 32rpx; background: #fff; border-radius: 16rpx; padding: 32rpx; text-align: center; }
|
||||
.code-label { font-size: 28rpx; color: #666; margin-bottom: 16rpx; }
|
||||
.code-box { background: #F5F5F5; border-radius: 12rpx; padding: 24rpx; margin-bottom: 24rpx; }
|
||||
.code-value { font-size: 48rpx; font-weight: 800; color: #333; letter-spacing: 8rpx; }
|
||||
.code-actions { display: flex; justify-content: center; gap: 16rpx; }
|
||||
.gen-btn { font-size: 26rpx; padding: 14rpx 28rpx; border-radius: 32rpx; background: #FF8F1F; color: #fff; border: none; line-height: 1.4; }
|
||||
.gen-btn.disabled { background: #ccc; }
|
||||
.copy-btn { font-size: 26rpx; padding: 14rpx 28rpx; border-radius: 32rpx; background: #fff; color: #FF8F1F; border: 1rpx solid #FF8F1F; line-height: 1.4; }
|
||||
|
||||
.share-section { margin: 0 32rpx 24rpx; background: #fff; border-radius: 16rpx; padding: 32rpx; }
|
||||
.section-title { font-size: 30rpx; font-weight: 700; color: #333; margin-bottom: 20rpx; }
|
||||
.share-tips { margin-bottom: 28rpx; }
|
||||
.tip-item { display: flex; align-items: center; margin-bottom: 16rpx; }
|
||||
.tip-icon { width: 36rpx; height: 36rpx; background: #FF8F1F; color: #fff; border-radius: 50%; text-align: center; line-height: 36rpx; font-size: 22rpx; margin-right: 12rpx; }
|
||||
.tip-text { font-size: 26rpx; color: #666; }
|
||||
.share-btn { width: 100%; background: #07C160; color: #fff; border-radius: 44rpx; font-size: 30rpx; padding: 20rpx 0; border: none; }
|
||||
|
||||
.commission-rules { margin: 0 32rpx; background: #fff; border-radius: 16rpx; padding: 32rpx; }
|
||||
.rule-row { display: flex; justify-content: space-between; padding: 16rpx 0; border-bottom: 1rpx solid #F5F5F5; }
|
||||
.rule-row:last-child { border-bottom: none; }
|
||||
.rule-lv { font-size: 28rpx; color: #333; }
|
||||
.rule-pct { font-size: 28rpx; color: #FF8F1F; font-weight: 600; }
|
||||
22
coupon/subPackages/pages-promoter/team/team.js
Normal file
22
coupon/subPackages/pages-promoter/team/team.js
Normal file
@@ -0,0 +1,22 @@
|
||||
var { request } = require('../../../utils/request');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
members: [],
|
||||
loading: true
|
||||
},
|
||||
|
||||
onShow() { this.loadTeam(); },
|
||||
|
||||
onPullDownRefresh() { this.loadTeam().finally(function() { wx.stopPullDownRefresh(); }); },
|
||||
|
||||
async loadTeam() {
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
var res = await request({ url: '/system/promotion/team', method: 'GET', loading: false });
|
||||
if (res.code === 200) {
|
||||
this.setData({ members: res.data || [], loading: false });
|
||||
}
|
||||
} catch (e) { this.setData({ loading: false }); }
|
||||
}
|
||||
});
|
||||
1
coupon/subPackages/pages-promoter/team/team.json
Normal file
1
coupon/subPackages/pages-promoter/team/team.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
33
coupon/subPackages/pages-promoter/team/team.wxml
Normal file
33
coupon/subPackages/pages-promoter/team/team.wxml
Normal file
@@ -0,0 +1,33 @@
|
||||
<view class="container">
|
||||
<view class="header"><text class="title">我的团队</text><text class="count">共{{members.length}}人</text></view>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading">加载中...</view>
|
||||
|
||||
<block wx:for="{{members}}" wx:key="userId">
|
||||
<view class="member-card">
|
||||
<view class="member-info">
|
||||
<view class="member-name">{{item.nickName || '用户' + item.userId}}</view>
|
||||
<view class="member-phone">{{item.phonenumber}}</view>
|
||||
</view>
|
||||
<view class="member-stats">
|
||||
<view class="member-stat">
|
||||
<text class="ms-num">{{item.inviteCount || 0}}</text>
|
||||
<text class="ms-label">邀请</text>
|
||||
</view>
|
||||
<view class="member-stat">
|
||||
<text class="ms-num gold">¥{{item.commission || '0'}}</text>
|
||||
<text class="ms-label">佣金贡献</text>
|
||||
</view>
|
||||
<view class="member-stat">
|
||||
<text class="ms-label">L{{item.level}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{item.joinTime}}" class="member-time">加入于 {{item.joinTime}}</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view wx:if="{{!loading && members.length === 0}}" class="empty">
|
||||
<text>暂无团队成员</text>
|
||||
<text class="empty-hint">分享您的邀请码,邀请好友加入推广</text>
|
||||
</view>
|
||||
</view>
|
||||
22
coupon/subPackages/pages-promoter/team/team.wxss
Normal file
22
coupon/subPackages/pages-promoter/team/team.wxss
Normal file
@@ -0,0 +1,22 @@
|
||||
.container { padding: 24rpx 32rpx; background: #F5F5F5; min-height: 100vh; }
|
||||
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx; }
|
||||
.title { font-size: 32rpx; font-weight: 700; color: #333; }
|
||||
.count { font-size: 24rpx; color: #999; }
|
||||
|
||||
.loading { text-align: center; padding: 80rpx; color: #999; font-size: 28rpx; }
|
||||
|
||||
.member-card { background: #fff; border-radius: 16rpx; padding: 28rpx 32rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04); }
|
||||
.member-info { display: flex; align-items: center; }
|
||||
.member-name { font-size: 30rpx; font-weight: 600; color: #333; }
|
||||
.member-phone { font-size: 24rpx; color: #999; margin-left: 16rpx; }
|
||||
|
||||
.member-stats { display: flex; margin-top: 20rpx; padding-top: 20rpx; border-top: 1rpx solid #F5F5F5; }
|
||||
.member-stat { flex: 1; text-align: center; }
|
||||
.ms-num { font-size: 32rpx; font-weight: 700; color: #333; display: block; }
|
||||
.ms-num.gold { color: #FF8F1F; }
|
||||
.ms-label { font-size: 22rpx; color: #999; }
|
||||
|
||||
.member-time { font-size: 22rpx; color: #C0C4CC; margin-top: 12rpx; }
|
||||
|
||||
.empty { text-align: center; padding: 120rpx 40rpx; color: #999; font-size: 28rpx; }
|
||||
.empty-hint { display: block; font-size: 24rpx; color: #C0C4CC; margin-top: 16rpx; }
|
||||
91
coupon/subPackages/pages-work/dispatch/dispatch.js
Normal file
91
coupon/subPackages/pages-work/dispatch/dispatch.js
Normal file
@@ -0,0 +1,91 @@
|
||||
var { request } = require('../../../utils/request');
|
||||
var app = getApp();
|
||||
|
||||
Page({
|
||||
data: {
|
||||
orders: [],
|
||||
caregivers: [],
|
||||
orderLoading: false,
|
||||
selectedOrder: null,
|
||||
selectedCaregiverId: null,
|
||||
showDialog: false,
|
||||
history: [],
|
||||
historyLoading: false
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadPending();
|
||||
this.loadCaregivers();
|
||||
this.loadHistory();
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
Promise.all([this.loadPending(), this.loadCaregivers(), this.loadHistory()])
|
||||
.finally(function() { wx.stopPullDownRefresh(); });
|
||||
},
|
||||
|
||||
async loadPending() {
|
||||
this.setData({ orderLoading: true });
|
||||
try {
|
||||
var res = await request({ url: '/system/dispatch/pending', method: 'GET', loading: false });
|
||||
if (res.code === 200) {
|
||||
this.setData({ orders: res.data || [], orderLoading: false });
|
||||
}
|
||||
} catch (e) { this.setData({ orderLoading: false }); }
|
||||
},
|
||||
|
||||
async loadCaregivers() {
|
||||
try {
|
||||
var res = await request({ url: '/system/dispatch/caregivers', method: 'GET', loading: false });
|
||||
if (res.code === 200) {
|
||||
this.setData({ caregivers: res.data || [] });
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
async loadHistory() {
|
||||
this.setData({ historyLoading: true });
|
||||
try {
|
||||
var res = await request({ url: '/system/dispatch/history', method: 'GET', loading: false });
|
||||
if (res.code === 200) {
|
||||
this.setData({ history: res.data || [], historyLoading: false });
|
||||
}
|
||||
} catch (e) { this.setData({ historyLoading: false }); }
|
||||
},
|
||||
|
||||
showAssignDialog(e) {
|
||||
var order = e.currentTarget.dataset.item;
|
||||
this.setData({ selectedOrder: order, selectedCaregiverId: null, showDialog: true });
|
||||
},
|
||||
|
||||
async doAssign() {
|
||||
if (!this.data.selectedCaregiverId || !this.data.selectedOrder) return;
|
||||
try {
|
||||
var res = await request({
|
||||
url: '/system/dispatch/assign/' + this.data.selectedOrder.orderId + '/' + this.data.selectedCaregiverId,
|
||||
method: 'POST',
|
||||
loading: true,
|
||||
loadingText: '派单中...'
|
||||
});
|
||||
if (res.code === 200) {
|
||||
wx.showToast({ title: '派单成功', icon: 'success' });
|
||||
this.setData({ showDialog: false });
|
||||
this.loadPending();
|
||||
this.loadCaregivers();
|
||||
this.loadHistory();
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
selectCaregiver(e) {
|
||||
this.setData({ selectedCaregiverId: e.currentTarget.dataset.id });
|
||||
},
|
||||
|
||||
closeDialog() {
|
||||
this.setData({ showDialog: false, selectedOrder: null, selectedCaregiverId: null });
|
||||
},
|
||||
|
||||
getStatusLabel(s) {
|
||||
return app.getStatusLabel(s);
|
||||
}
|
||||
});
|
||||
1
coupon/subPackages/pages-work/dispatch/dispatch.json
Normal file
1
coupon/subPackages/pages-work/dispatch/dispatch.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
54
coupon/subPackages/pages-work/dispatch/dispatch.wxml
Normal file
54
coupon/subPackages/pages-work/dispatch/dispatch.wxml
Normal file
@@ -0,0 +1,54 @@
|
||||
<view class="container">
|
||||
<view class="section-title">待派单订单</view>
|
||||
|
||||
<view wx:if="{{orderLoading}}" class="loading">加载中...</view>
|
||||
|
||||
<block wx:for="{{orders}}" wx:key="orderId">
|
||||
<view class="order-card">
|
||||
<view class="order-info">
|
||||
<text class="order-no">#{{item.yuliu1 || item.orderId}}</text>
|
||||
<text class="order-hospital">医院{{item.hospitalId}}</text>
|
||||
<text class="order-amount">¥{{item.yuguMoney}}</text>
|
||||
</view>
|
||||
<view class="order-meta">
|
||||
<text class="order-time">{{item.createTime}}</text>
|
||||
<text class="order-status">{{item.status === '0' ? '待接单' : item.status}}</text>
|
||||
</view>
|
||||
<view class="order-action">
|
||||
<button class="assign-btn" bindtap="showAssignDialog" data-item="{{item}}">派单</button>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view wx:if="{{!orderLoading && orders.length === 0}}" class="empty">暂无待派单订单</view>
|
||||
|
||||
<!-- 陪诊师选择弹窗 -->
|
||||
<view wx:if="{{showDialog}}" class="dialog-mask" bindtap="closeDialog">
|
||||
<view class="dialog-box" catchtap="">
|
||||
<view class="dialog-title">选择陪诊师</view>
|
||||
<scroll-view scroll-y class="caregiver-list">
|
||||
<view wx:for="{{caregivers}}" wx:key="userId" class="caregiver-item {{selectedCaregiverId === item.userId ? 'selected' : ''}}" data-id="{{item.userId}}" bindtap="selectCaregiver">
|
||||
<text class="cg-name">{{item.nickName || '未命名'}}</text>
|
||||
<text class="cg-phone">{{item.phonenumber}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="dialog-buttons">
|
||||
<button class="btn-cancel" bindtap="closeDialog">取消</button>
|
||||
<button class="btn-confirm" bindtap="doAssign" disabled="{{!selectedCaregiverId}}">确认派单</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 派单历史 -->
|
||||
<view class="section-title">我的派单记录</view>
|
||||
<view wx:if="{{historyLoading}}" class="loading">加载中...</view>
|
||||
<block wx:for="{{history}}" wx:key="orderId">
|
||||
<view class="history-item">
|
||||
<text class="h-order">#{{item.yuliu1 || item.orderId}}</text>
|
||||
<text class="h-caregiver">陪诊师{{item.bId}}</text>
|
||||
<text class="h-amount">¥{{item.yuguMoney}}</text>
|
||||
<text class="h-status">{{item.status === '1' ? '已接单' : item.status === '4' ? '已完成' : item.status}}</text>
|
||||
</view>
|
||||
</block>
|
||||
<view wx:if="{{!historyLoading && history.length === 0}}" class="empty">暂无记录</view>
|
||||
</view>
|
||||
35
coupon/subPackages/pages-work/dispatch/dispatch.wxss
Normal file
35
coupon/subPackages/pages-work/dispatch/dispatch.wxss
Normal file
@@ -0,0 +1,35 @@
|
||||
.container { padding: 24rpx 32rpx; background: #F5F5F5; min-height: 100vh; }
|
||||
.section-title { font-size: 30rpx; font-weight: 700; color: #333; margin: 24rpx 0 16rpx; }
|
||||
.loading { text-align: center; padding: 60rpx; color: #999; }
|
||||
.empty { text-align: center; padding: 80rpx; color: #999; font-size: 28rpx; }
|
||||
|
||||
.order-card { background: #fff; border-radius: 16rpx; padding: 24rpx 28rpx; margin-bottom: 12rpx; }
|
||||
.order-info { display: flex; justify-content: space-between; align-items: center; }
|
||||
.order-no { font-size: 28rpx; font-weight: 600; color: #333; }
|
||||
.order-hospital { font-size: 24rpx; color: #666; }
|
||||
.order-amount { font-size: 30rpx; font-weight: 700; color: #E6A23C; }
|
||||
.order-meta { display: flex; justify-content: space-between; margin-top: 12rpx; }
|
||||
.order-time { font-size: 22rpx; color: #999; }
|
||||
.order-status { font-size: 22rpx; color: #FF9800; }
|
||||
.order-action { text-align: right; margin-top: 16rpx; }
|
||||
.assign-btn { display: inline-block; font-size: 24rpx; padding: 8rpx 24rpx; background: #1890FF; color: #fff; border-radius: 32rpx; border: none; line-height: 1.4; }
|
||||
|
||||
/* 弹窗 */
|
||||
.dialog-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 100; display: flex; justify-content: center; align-items: center; }
|
||||
.dialog-box { width: 600rpx; max-height: 80vh; background: #fff; border-radius: 16rpx; overflow: hidden; }
|
||||
.dialog-title { font-size: 32rpx; font-weight: 700; text-align: center; padding: 32rpx 0 16rpx; }
|
||||
.caregiver-list { max-height: 500rpx; padding: 0 24rpx; }
|
||||
.caregiver-item { display: flex; justify-content: space-between; padding: 20rpx 16rpx; border-bottom: 1rpx solid #F5F5F5; }
|
||||
.caregiver-item.selected { background: #E6F7FF; border-radius: 8rpx; }
|
||||
.cg-name { font-size: 28rpx; color: #333; }
|
||||
.cg-phone { font-size: 24rpx; color: #999; }
|
||||
.dialog-buttons { display: flex; padding: 20rpx 24rpx; gap: 16rpx; }
|
||||
.btn-cancel { flex: 1; font-size: 28rpx; padding: 16rpx 0; border-radius: 8rpx; background: #F5F5F5; color: #666; border: none; }
|
||||
.btn-confirm { flex: 1; font-size: 28rpx; padding: 16rpx 0; border-radius: 8rpx; background: #1890FF; color: #fff; border: none; }
|
||||
|
||||
/* 历史 */
|
||||
.history-item { display: flex; justify-content: space-between; align-items: center; background: #fff; padding: 20rpx 28rpx; margin-bottom: 8rpx; border-radius: 8rpx; }
|
||||
.h-order { font-size: 26rpx; color: #333; }
|
||||
.h-caregiver { font-size: 24rpx; color: #999; }
|
||||
.h-amount { font-size: 26rpx; color: #E6A23C; }
|
||||
.h-status { font-size: 22rpx; padding: 4rpx 12rpx; border-radius: 8rpx; background: #E8F5E9; color: #4CAF50; }
|
||||
44
coupon/subPackages/pages-work/ops/ops.js
Normal file
44
coupon/subPackages/pages-work/ops/ops.js
Normal file
@@ -0,0 +1,44 @@
|
||||
var { request } = require('../../../utils/request');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
totalIncome: '--',
|
||||
totalRefund: '--',
|
||||
totalOrders: '--',
|
||||
netIncome: '--',
|
||||
trend: [],
|
||||
loading: true
|
||||
},
|
||||
|
||||
onShow() { this.loadStats(); },
|
||||
|
||||
onPullDownRefresh() { this.loadStats().finally(function() { wx.stopPullDownRefresh(); }); },
|
||||
|
||||
async loadStats() {
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
// Get current month date range
|
||||
var now = new Date();
|
||||
var endDate = now.toISOString().split('T')[0];
|
||||
var startDate = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().split('T')[0];
|
||||
|
||||
var res = await request({
|
||||
url: '/system/view/incomeStats',
|
||||
method: 'GET',
|
||||
data: { startDate: startDate, endDate: endDate, groupBy: 'day' },
|
||||
loading: false
|
||||
});
|
||||
if (res.code === 200 && res.data) {
|
||||
var d = res.data;
|
||||
this.setData({
|
||||
totalIncome: d.totalIncome || '0.00',
|
||||
totalRefund: d.totalRefund || '0.00',
|
||||
totalOrders: d.totalOrders || 0,
|
||||
netIncome: ((parseFloat(d.totalIncome) || 0) - (parseFloat(d.totalRefund) || 0)).toFixed(2),
|
||||
trend: d.trend || []
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
});
|
||||
1
coupon/subPackages/pages-work/ops/ops.json
Normal file
1
coupon/subPackages/pages-work/ops/ops.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
42
coupon/subPackages/pages-work/ops/ops.wxml
Normal file
42
coupon/subPackages/pages-work/ops/ops.wxml
Normal file
@@ -0,0 +1,42 @@
|
||||
<view class="container">
|
||||
<view class="cg-header">
|
||||
<text class="cg-header-title">运营看板</text>
|
||||
<text class="cg-header-sub">本月数据概览</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading">加载中...</view>
|
||||
|
||||
<view wx:else class="stats-grid">
|
||||
<view class="stat-card">
|
||||
<view class="stat-num">¥{{totalIncome}}</view>
|
||||
<view class="stat-label">总收入</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-num red">¥{{totalRefund}}</view>
|
||||
<view class="stat-label">退款总额</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-num gold">¥{{netIncome}}</view>
|
||||
<view class="stat-label">净收入</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-num">{{totalOrders}}</view>
|
||||
<view class="stat-label">总订单数</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!loading && trend.length > 0}}" class="trend-section">
|
||||
<view class="section-title">每日趋势</view>
|
||||
<view class="trend-list">
|
||||
<block wx:for="{{trend}}" wx:key="date">
|
||||
<view class="trend-item">
|
||||
<text class="trend-date">{{item.date}}</text>
|
||||
<text class="trend-income">¥{{item.income || '0'}}</text>
|
||||
<text class="trend-orders">{{item.orderCount || 0}}单</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!loading}}" class="hint">详细数据请登录管理后台查看</view>
|
||||
</view>
|
||||
27
coupon/subPackages/pages-work/ops/ops.wxss
Normal file
27
coupon/subPackages/pages-work/ops/ops.wxss
Normal file
@@ -0,0 +1,27 @@
|
||||
.container { padding: 0 0 40rpx 0; background: #F5F5F5; min-height: 100vh; }
|
||||
|
||||
/* === header === */
|
||||
.cg-header { background: #fff; padding: 32rpx 40rpx; margin-bottom: 16rpx; }
|
||||
.cg-header-title { font-size: 36rpx; font-weight: bold; color: #333; display: block; }
|
||||
.cg-header-sub { font-size: 24rpx; color: #999; margin-top: 8rpx; }
|
||||
|
||||
/* === stats grid === */
|
||||
.stats-grid { display: flex; flex-wrap: wrap; margin: 0 24rpx; }
|
||||
.stat-card { width: 44%; margin: 8rpx 3%; background: #fff; border-radius: 16rpx; padding: 28rpx 24rpx; text-align: center; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
|
||||
.stat-num { font-size: 40rpx; font-weight: 800; color: #333; }
|
||||
.stat-num.gold { color: #FF8F1F; }
|
||||
.stat-num.red { color: #F56C6C; }
|
||||
.stat-label { font-size: 22rpx; color: #999; margin-top: 8rpx; }
|
||||
|
||||
/* === trend === */
|
||||
.trend-section { margin: 24rpx 32rpx; }
|
||||
.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 12rpx; }
|
||||
.trend-list { background: #fff; border-radius: 16rpx; padding: 8rpx 0; }
|
||||
.trend-item { display: flex; justify-content: space-between; padding: 16rpx 32rpx; border-bottom: 1rpx solid #F5F5F5; }
|
||||
.trend-item:last-child { border-bottom: none; }
|
||||
.trend-date { font-size: 24rpx; color: #999; }
|
||||
.trend-income { font-size: 26rpx; font-weight: 600; color: #67C23A; }
|
||||
.trend-orders { font-size: 24rpx; color: #999; }
|
||||
|
||||
.loading { text-align: center; padding: 40rpx; color: #999; }
|
||||
.hint { text-align: center; margin-top: 24rpx; font-size: 24rpx; color: #C0C4CC; }
|
||||
73
coupon/subPackages/pages-work/refund/refund.js
Normal file
73
coupon/subPackages/pages-work/refund/refund.js
Normal file
@@ -0,0 +1,73 @@
|
||||
var { request } = require('../../../utils/request');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
orders: [],
|
||||
loading: false,
|
||||
reason: '',
|
||||
showReject: false,
|
||||
currentOrderId: null
|
||||
},
|
||||
|
||||
onShow() { this.loadOrders(); },
|
||||
|
||||
onPullDownRefresh() { this.loadOrders().finally(function() { wx.stopPullDownRefresh(); }); },
|
||||
|
||||
async loadOrders() {
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
var res = await request({
|
||||
url: '/system/view/list',
|
||||
method: 'GET',
|
||||
loading: false,
|
||||
data: { pageNum: 1, pageSize: 50, status: '5' }
|
||||
});
|
||||
if (res.code === 200) {
|
||||
this.setData({ orders: (res.rows || res.data || []), loading: false });
|
||||
}
|
||||
} catch (e) { this.setData({ loading: false }); }
|
||||
},
|
||||
|
||||
showRejectDialog(e) {
|
||||
var id = e.currentTarget.dataset.id;
|
||||
this.setData({ showReject: true, currentOrderId: id, reason: '' });
|
||||
},
|
||||
|
||||
async doApprove(e) {
|
||||
var orderId = e.currentTarget.dataset.id;
|
||||
await this.submitReview(orderId, true, '');
|
||||
},
|
||||
|
||||
async submitRefuse() {
|
||||
if (!this.data.reason.trim()) {
|
||||
wx.showToast({ title: '请输入拒绝原因', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
await this.submitReview(this.data.currentOrderId, false, this.data.reason);
|
||||
},
|
||||
|
||||
onReasonInput(e) {
|
||||
this.setData({ reason: e.detail.value });
|
||||
},
|
||||
|
||||
closeReject() {
|
||||
this.setData({ showReject: false, currentOrderId: null, reason: '' });
|
||||
},
|
||||
|
||||
async submitReview(orderId, approved, reason) {
|
||||
try {
|
||||
var res = await request({
|
||||
url: '/system/view/refundApprove/' + orderId,
|
||||
method: 'PUT',
|
||||
data: { approved: approved, reason: reason },
|
||||
loading: true,
|
||||
loadingText: approved ? '通过中...' : '拒绝中...'
|
||||
});
|
||||
if (res.code === 200) {
|
||||
wx.showToast({ title: approved ? '已通过' : '已拒绝', icon: 'success' });
|
||||
this.setData({ showReject: false });
|
||||
this.loadOrders();
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
1
coupon/subPackages/pages-work/refund/refund.json
Normal file
1
coupon/subPackages/pages-work/refund/refund.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
37
coupon/subPackages/pages-work/refund/refund.wxml
Normal file
37
coupon/subPackages/pages-work/refund/refund.wxml
Normal file
@@ -0,0 +1,37 @@
|
||||
<view class="container">
|
||||
<view class="section-title">退款审核</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading">加载中...</view>
|
||||
|
||||
<block wx:for="{{orders}}" wx:key="orderId">
|
||||
<view class="refund-card">
|
||||
<view class="refund-row">
|
||||
<text class="r-order">订单 #{{item.yuliu1 || item.orderId}}</text>
|
||||
<text class="r-amount">¥{{item.yuguMoney || item.orderMoney}}</text>
|
||||
</view>
|
||||
<view class="refund-row sub">
|
||||
<text class="r-meta">患者ID: {{item.cId}}</text>
|
||||
<text class="r-status">申请退款</text>
|
||||
</view>
|
||||
<view wx:if="{{item.remark}}" class="refund-reason">原因: {{item.remark}}</view>
|
||||
<view class="refund-actions">
|
||||
<button class="btn-approve" data-id="{{item.orderId}}" bindtap="doApprove">通过</button>
|
||||
<button class="btn-reject" data-id="{{item.orderId}}" bindtap="showRejectDialog">拒绝</button>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view wx:if="{{!loading && orders.length === 0}}" class="empty">暂无退款申请</view>
|
||||
|
||||
<!-- 拒绝原因弹窗 -->
|
||||
<view wx:if="{{showReject}}" class="dialog-mask" bindtap="closeReject">
|
||||
<view class="dialog-box" catchtap="">
|
||||
<view class="dialog-title">拒绝原因</view>
|
||||
<textarea class="reason-input" placeholder="请输入拒绝退款的原因" value="{{reason}}" bindinput="onReasonInput" maxlength="200"></textarea>
|
||||
<view class="dialog-buttons">
|
||||
<button class="btn-cancel" bindtap="closeReject">取消</button>
|
||||
<button class="btn-confirm" bindtap="submitRefuse">确认拒绝</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
24
coupon/subPackages/pages-work/refund/refund.wxss
Normal file
24
coupon/subPackages/pages-work/refund/refund.wxss
Normal file
@@ -0,0 +1,24 @@
|
||||
.container { padding: 24rpx 32rpx; background: #F5F5F5; min-height: 100vh; }
|
||||
.section-title { font-size: 30rpx; font-weight: 700; color: #333; margin-bottom: 16rpx; }
|
||||
.loading { text-align: center; padding: 80rpx; color: #999; }
|
||||
.empty { text-align: center; padding: 120rpx; color: #999; font-size: 28rpx; }
|
||||
|
||||
.refund-card { background: #fff; border-radius: 16rpx; padding: 24rpx 28rpx; margin-bottom: 12rpx; }
|
||||
.refund-row { display: flex; justify-content: space-between; align-items: center; }
|
||||
.refund-row.sub { margin-top: 10rpx; }
|
||||
.r-order { font-size: 28rpx; font-weight: 600; color: #333; }
|
||||
.r-amount { font-size: 30rpx; font-weight: 700; color: #E6A23C; }
|
||||
.r-meta { font-size: 24rpx; color: #666; }
|
||||
.r-status { font-size: 22rpx; padding: 4rpx 12rpx; border-radius: 8rpx; background: #FFF3E0; color: #FF9800; }
|
||||
.refund-reason { font-size: 24rpx; color: #999; margin-top: 12rpx; padding-top: 12rpx; border-top: 1rpx solid #F5F5F5; }
|
||||
.refund-actions { display: flex; justify-content: flex-end; gap: 16rpx; margin-top: 16rpx; }
|
||||
.btn-approve { font-size: 24rpx; padding: 10rpx 28rpx; background: #4CAF50; color: #fff; border-radius: 32rpx; border: none; line-height: 1.4; }
|
||||
.btn-reject { font-size: 24rpx; padding: 10rpx 28rpx; background: #F56C6C; color: #fff; border-radius: 32rpx; border: none; line-height: 1.4; }
|
||||
|
||||
.dialog-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 100; display: flex; justify-content: center; align-items: center; }
|
||||
.dialog-box { width: 600rpx; background: #fff; border-radius: 16rpx; padding: 32rpx; }
|
||||
.dialog-title { font-size: 32rpx; font-weight: 700; text-align: center; margin-bottom: 24rpx; }
|
||||
.reason-input { width: 100%; height: 160rpx; background: #F5F5F5; border-radius: 12rpx; padding: 16rpx; font-size: 26rpx; box-sizing: border-box; }
|
||||
.dialog-buttons { display: flex; gap: 16rpx; margin-top: 24rpx; }
|
||||
.btn-cancel { flex: 1; font-size: 28rpx; padding: 16rpx 0; border-radius: 8rpx; background: #F5F5F5; color: #666; border: none; }
|
||||
.btn-confirm { flex: 1; font-size: 28rpx; padding: 16rpx 0; border-radius: 8rpx; background: #F56C6C; color: #fff; border: none; }
|
||||
81
coupon/subPackages/pages-work/search/search.js
Normal file
81
coupon/subPackages/pages-work/search/search.js
Normal file
@@ -0,0 +1,81 @@
|
||||
var { request } = require('../../../utils/request');
|
||||
var app = getApp();
|
||||
|
||||
Page({
|
||||
data: {
|
||||
orders: [],
|
||||
keyword: '',
|
||||
statusFilter: '',
|
||||
pageNum: 1,
|
||||
hasMore: true,
|
||||
loading: false,
|
||||
firstLoad: true,
|
||||
statusOptions: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '待接单', value: '0' },
|
||||
{ label: '已接单', value: '1' },
|
||||
{ label: '服务中', value: '2' },
|
||||
{ label: '待支付', value: '3' },
|
||||
{ label: '已完成', value: '4' },
|
||||
{ label: '申请退款', value: '5' },
|
||||
{ label: '退款中', value: '6' },
|
||||
{ label: '已退款', value: '7' },
|
||||
{ label: '已结算', value: '8' }
|
||||
]
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (this.data.firstLoad) {
|
||||
this.setData({ firstLoad: false });
|
||||
this.searchOrders();
|
||||
}
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (this.data.hasMore && !this.data.loading) this.searchOrders();
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.setData({ pageNum: 1, orders: [] });
|
||||
this.searchOrders().finally(function() { wx.stopPullDownRefresh(); });
|
||||
},
|
||||
|
||||
onSearchInput(e) { this.setData({ keyword: e.detail.value }); },
|
||||
|
||||
onStatusTap(e) {
|
||||
this.setData({ statusFilter: e.currentTarget.dataset.status, pageNum: 1, orders: [] });
|
||||
this.searchOrders();
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.setData({ pageNum: 1, orders: [] });
|
||||
this.searchOrders();
|
||||
},
|
||||
|
||||
async searchOrders() {
|
||||
if (this.data.loading) return;
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
var params = { pageNum: this.data.pageNum, pageSize: 20 };
|
||||
if (this.data.keyword) params.keyword = this.data.keyword;
|
||||
if (this.data.statusFilter) params.status = this.data.statusFilter;
|
||||
var res = await request({ url: '/system/view/list', method: 'GET', data: params, loading: false });
|
||||
if (res.code === 200) {
|
||||
var list = res.rows || res.data || [];
|
||||
if (this.data.pageNum === 1) {
|
||||
this.setData({ orders: list, hasMore: list.length >= 20, pageNum: 2 });
|
||||
} else {
|
||||
this.setData({ orders: this.data.orders.concat(list), hasMore: list.length >= 20, pageNum: this.data.pageNum + 1 });
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
this.setData({ loading: false });
|
||||
},
|
||||
|
||||
getStatusLabel(s) { return app.getStatusLabel(s); },
|
||||
|
||||
goDetail(e) {
|
||||
var item = JSON.stringify(e.currentTarget.dataset.item);
|
||||
wx.navigateTo({ url: '/pages/orderdetail/orderdetail?item=' + item });
|
||||
}
|
||||
});
|
||||
1
coupon/subPackages/pages-work/search/search.json
Normal file
1
coupon/subPackages/pages-work/search/search.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
35
coupon/subPackages/pages-work/search/search.wxml
Normal file
35
coupon/subPackages/pages-work/search/search.wxml
Normal file
@@ -0,0 +1,35 @@
|
||||
<view class="container">
|
||||
<view class="search-bar">
|
||||
<input class="search-input" placeholder="搜索订单号/患者" value="{{keyword}}" bindinput="onSearchInput" confirm-type="search" bindconfirm="doSearch" />
|
||||
<button class="search-btn" bindtap="doSearch">搜索</button>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-x class="status-bar">
|
||||
<view wx:for="{{statusOptions}}" wx:key="value" class="status-item {{statusFilter === item.value ? 'active' : ''}}" data-status="{{item.value}}" bindtap="onStatusTap">
|
||||
{{item.label}}
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<block wx:for="{{orders}}" wx:key="orderId">
|
||||
<view class="order-card" bindtap="goDetail" data-item="{{item}}">
|
||||
<view class="o-row">
|
||||
<text class="o-no">{{item.yuliu1 || '#' + item.orderId}}</text>
|
||||
<text class="o-amount">¥{{item.yuguMoney || item.orderMoney}}</text>
|
||||
</view>
|
||||
<view class="o-row sub">
|
||||
<text class="o-meta">患者ID: {{item.cId}} | 陪诊师ID: {{item.bId || '--'}}</text>
|
||||
</view>
|
||||
<view class="o-row sub">
|
||||
<text class="o-time">{{item.createTime}}</text>
|
||||
<text class="o-status">{{item.status === '-2' ? '已取消' : item.status === '-1' ? '拒绝接单' : item.status === '0' ? '待接单' : item.status === '1' ? '已接单' : item.status === '2' ? '服务中' : item.status === '3' ? '待支付' : item.status === '4' ? '已完成' : item.status === '5' ? '申请退款' : item.status === '6' ? '退款中' : item.status === '7' ? '已退款' : item.status === '8' ? '已结算' : item.status}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view wx:if="{{!loading && orders.length === 0}}" class="empty">
|
||||
<text>未找到订单</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading">加载中...</view>
|
||||
<view wx:if="{{!hasMore && orders.length > 0}}" class="end">— 已加载全部 —</view>
|
||||
</view>
|
||||
21
coupon/subPackages/pages-work/search/search.wxss
Normal file
21
coupon/subPackages/pages-work/search/search.wxss
Normal file
@@ -0,0 +1,21 @@
|
||||
.container { padding: 0; background: #F5F5F5; min-height: 100vh; }
|
||||
.search-bar { display: flex; padding: 16rpx 24rpx; background: #fff; }
|
||||
.search-input { flex: 1; height: 68rpx; background: #F5F5F5; border-radius: 34rpx; padding: 0 28rpx; font-size: 26rpx; }
|
||||
.search-btn { width: 120rpx; height: 68rpx; line-height: 68rpx; text-align: center; font-size: 26rpx; background: #1890FF; color: #fff; border-radius: 34rpx; border: none; margin-left: 12rpx; padding: 0; }
|
||||
|
||||
.status-bar { white-space: nowrap; padding: 16rpx 24rpx; background: #fff; border-top: 1rpx solid #F5F5F5; }
|
||||
.status-item { display: inline-block; font-size: 24rpx; padding: 10rpx 20rpx; margin-right: 12rpx; border-radius: 24rpx; background: #F5F5F5; color: #666; }
|
||||
.status-item.active { background: #1890FF; color: #fff; }
|
||||
|
||||
.order-card { background: #fff; margin: 12rpx 0 0 0; padding: 24rpx 32rpx; }
|
||||
.o-row { display: flex; justify-content: space-between; align-items: center; }
|
||||
.o-row.sub { margin-top: 8rpx; }
|
||||
.o-no { font-size: 28rpx; font-weight: 600; color: #333; }
|
||||
.o-amount { font-size: 30rpx; font-weight: 700; color: #E6A23C; }
|
||||
.o-meta { font-size: 24rpx; color: #999; }
|
||||
.o-time { font-size: 22rpx; color: #C0C4CC; }
|
||||
.o-status { font-size: 22rpx; padding: 4rpx 12rpx; border-radius: 8rpx; background: #E8F5E9; color: #4CAF50; }
|
||||
|
||||
.loading { text-align: center; padding: 40rpx; color: #999; }
|
||||
.empty { text-align: center; padding: 120rpx 0; color: #999; font-size: 28rpx; }
|
||||
.end { text-align: center; padding: 32rpx; color: #C0C4CC; font-size: 24rpx; }
|
||||
Reference in New Issue
Block a user