backup: 紫色UI换肤前完整备份(当前橙色逍遥梦主题)
保留改造前全部页面样式与功能代码,便于回滚。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,47 +1,398 @@
|
||||
/** 客服列表 - UI 预览,mock 数据 */
|
||||
/** 客服列表 — 派单量 / 剩余额度 / 封禁解封 */
|
||||
|
||||
import request from '../../utils/request.js';
|
||||
|
||||
import { STAFF_API, isStaffMode } from '../../utils/staff-api.js';
|
||||
|
||||
|
||||
|
||||
const app = getApp();
|
||||
|
||||
|
||||
|
||||
function avatarUrl(path) {
|
||||
|
||||
if (!path) return '/images/default-avatar.png';
|
||||
|
||||
if (path.startsWith('http')) return path;
|
||||
|
||||
const oss = app.globalData.ossImageUrl || '';
|
||||
|
||||
const p = path.startsWith('/') ? path.slice(1) : path;
|
||||
|
||||
return oss ? oss + p : '/images/default-avatar.png';
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Page({
|
||||
|
||||
data: {
|
||||
|
||||
statusBar: 20,
|
||||
list: [
|
||||
{
|
||||
id: 1,
|
||||
nickname: '客服总管-阿星',
|
||||
avatar: '/images/default-avatar.png',
|
||||
uid: '1008601',
|
||||
isChief: true,
|
||||
isFinance: false,
|
||||
orderCount: 128,
|
||||
settleCount: 96,
|
||||
settleAmount: '12800.00',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
nickname: '财务-小李',
|
||||
avatar: '/images/default-avatar.png',
|
||||
uid: '1008602',
|
||||
isChief: false,
|
||||
isFinance: true,
|
||||
orderCount: 56,
|
||||
settleCount: 52,
|
||||
settleAmount: '6800.00',
|
||||
},
|
||||
],
|
||||
|
||||
list: [],
|
||||
|
||||
isOwner: false,
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
onLoad() {
|
||||
|
||||
const sys = wx.getSystemInfoSync();
|
||||
this.setData({ statusBar: sys.statusBarHeight || 20 });
|
||||
|
||||
this.setData({ statusBar: sys.statusBarHeight || 20, isOwner: !isStaffMode() });
|
||||
|
||||
this.loadList();
|
||||
|
||||
},
|
||||
|
||||
goBack() {
|
||||
wx.navigateBack();
|
||||
|
||||
|
||||
onShow() {
|
||||
|
||||
this.loadList();
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
formatList(raw) {
|
||||
|
||||
return (raw || []).map((item) => ({
|
||||
|
||||
...item,
|
||||
|
||||
avatar: avatarUrl(item.avatar),
|
||||
|
||||
todayDispatchCount: item.todayDispatchCount != null ? item.todayDispatchCount : 0,
|
||||
|
||||
todayDispatchAmount: item.todayDispatchAmount || '0.00',
|
||||
|
||||
dispatchCount: item.dispatchCount != null ? item.dispatchCount : (item.orderCount || 0),
|
||||
|
||||
dispatchAmount: item.dispatchAmount || '0.00',
|
||||
|
||||
settleCount: item.settleCount != null ? item.settleCount : 0,
|
||||
|
||||
settleAmount: item.settleAmount || '0.00',
|
||||
|
||||
refundCount: item.refundCount != null ? item.refundCount : 0,
|
||||
|
||||
refundAmount: item.refundAmount || '0.00',
|
||||
|
||||
quotaAvailable: item.quota_available || '0.00',
|
||||
|
||||
quotaRevokable: item.quota_available || '0.00',
|
||||
|
||||
isSelf: !!item.isSelf,
|
||||
|
||||
}));
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
rejectSelf(id) {
|
||||
|
||||
const row = (this.data.list || []).find((x) => String(x.id) === String(id));
|
||||
|
||||
if (row && row.isSelf) {
|
||||
|
||||
wx.showToast({ title: '不能对本人操作', icon: 'none' });
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
async loadList() {
|
||||
|
||||
try {
|
||||
|
||||
const res = await request({ url: STAFF_API.memberList, method: 'POST' });
|
||||
|
||||
if (res.data && res.data.code === 0) {
|
||||
|
||||
const raw = (res.data.data && res.data.data.list) || [];
|
||||
|
||||
this.setData({ list: this.formatList(raw) });
|
||||
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
|
||||
wx.showToast({ title: '加载失败', icon: 'none' });
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
goBack() { wx.navigateBack(); },
|
||||
|
||||
|
||||
|
||||
copyUid(e) {
|
||||
|
||||
wx.setClipboardData({ data: e.currentTarget.dataset.uid || '' });
|
||||
|
||||
},
|
||||
|
||||
onAction(e) {
|
||||
wx.showToast({ title: '后端接入后生效', icon: 'none' });
|
||||
|
||||
|
||||
async onAllocate(e) {
|
||||
|
||||
const id = e.currentTarget.dataset.id;
|
||||
|
||||
if (this.rejectSelf(id)) return;
|
||||
|
||||
wx.showModal({
|
||||
|
||||
title: '划额度',
|
||||
|
||||
editable: true,
|
||||
|
||||
placeholderText: '输入金额',
|
||||
|
||||
success: async (r) => {
|
||||
|
||||
if (!r.confirm || !r.content) return;
|
||||
|
||||
const res = await request({
|
||||
|
||||
url: STAFF_API.walletAllocate,
|
||||
|
||||
method: 'POST',
|
||||
|
||||
data: { member_id: id, amount: r.content },
|
||||
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
|
||||
title: (res.data && res.data.code === 0) ? '成功' : (res.data.msg || '失败'),
|
||||
|
||||
icon: 'none',
|
||||
|
||||
});
|
||||
|
||||
this.loadList();
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
async onRevoke(e) {
|
||||
|
||||
const id = e.currentTarget.dataset.id;
|
||||
|
||||
if (this.rejectSelf(id)) return;
|
||||
|
||||
const available = e.currentTarget.dataset.available || '0';
|
||||
|
||||
wx.showModal({
|
||||
|
||||
title: '收回额度',
|
||||
|
||||
editable: true,
|
||||
|
||||
placeholderText: `最多可收回 ${available} 元`,
|
||||
|
||||
success: async (r) => {
|
||||
|
||||
if (!r.confirm || !r.content) return;
|
||||
|
||||
const res = await request({
|
||||
|
||||
url: STAFF_API.walletRevoke,
|
||||
|
||||
method: 'POST',
|
||||
|
||||
data: { member_id: id, amount: r.content },
|
||||
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
|
||||
title: (res.data && res.data.code === 0) ? '已收回' : (res.data.msg || '失败'),
|
||||
|
||||
icon: 'none',
|
||||
|
||||
});
|
||||
|
||||
if (res.data && res.data.code === 0) this.loadList();
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
async onToggleDisable(e) {
|
||||
|
||||
const id = e.currentTarget.dataset.id;
|
||||
|
||||
if (this.rejectSelf(id)) return;
|
||||
|
||||
const disabled = e.currentTarget.dataset.disabled;
|
||||
|
||||
const action = disabled ? '解封' : '封禁';
|
||||
|
||||
wx.showModal({
|
||||
|
||||
title: `${action}客服`,
|
||||
|
||||
content: disabled ? '解封后该客服可继续登录操作' : '封禁后该客服无法使用商家端功能',
|
||||
|
||||
success: async (r) => {
|
||||
|
||||
if (!r.confirm) return;
|
||||
|
||||
const res = await request({
|
||||
|
||||
url: STAFF_API.memberDisable,
|
||||
|
||||
method: 'POST',
|
||||
|
||||
data: { member_id: id, disable: !disabled },
|
||||
|
||||
});
|
||||
|
||||
wx.showToast({ title: (res.data && res.data.msg) || action, icon: 'none' });
|
||||
|
||||
this.loadList();
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
async onRemove(e) {
|
||||
|
||||
const id = e.currentTarget.dataset.id;
|
||||
|
||||
wx.showModal({
|
||||
|
||||
title: '移除客服',
|
||||
|
||||
content: '移除后该用户可认证商家或绑定其他商家',
|
||||
|
||||
success: async (r) => {
|
||||
|
||||
if (!r.confirm) return;
|
||||
|
||||
const res = await request({
|
||||
|
||||
url: STAFF_API.memberRemove,
|
||||
|
||||
method: 'POST',
|
||||
|
||||
data: { member_id: id },
|
||||
|
||||
});
|
||||
|
||||
wx.showToast({ title: res.data.msg || '完成', icon: 'none' });
|
||||
|
||||
this.loadList();
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
async onAssignRole(e) {
|
||||
|
||||
const memberId = e.currentTarget.dataset.id;
|
||||
|
||||
if (this.rejectSelf(memberId)) return;
|
||||
|
||||
const rolesRes = await request({ url: STAFF_API.roleList, method: 'POST' });
|
||||
|
||||
const roles = (rolesRes.data && rolesRes.data.data && rolesRes.data.data.roles) || [];
|
||||
|
||||
if (!roles.length) {
|
||||
|
||||
wx.showToast({ title: '请先创建角色', icon: 'none' });
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
wx.showActionSheet({
|
||||
|
||||
itemList: roles.map((r) => r.role_name),
|
||||
|
||||
success: async (r) => {
|
||||
|
||||
const role = roles[r.tapIndex];
|
||||
|
||||
const res = await request({
|
||||
|
||||
url: STAFF_API.memberUpdateRole,
|
||||
|
||||
method: 'POST',
|
||||
|
||||
data: { member_id: memberId, role_id: role.id },
|
||||
|
||||
});
|
||||
|
||||
const ok = res.data && res.data.code === 0;
|
||||
|
||||
const roleName = (res.data && res.data.data && res.data.data.role_name) || role.role_name;
|
||||
|
||||
wx.showToast({
|
||||
|
||||
title: ok ? `已设为${roleName}` : (res.data.msg || '失败'),
|
||||
|
||||
icon: 'none',
|
||||
|
||||
});
|
||||
|
||||
if (ok) this.loadList();
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
goRank() {
|
||||
|
||||
wx.navigateTo({ url: '/pages/merchant-staff-audit/merchant-staff-audit?tab=rank' });
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
goAudit() {
|
||||
|
||||
wx.navigateTo({ url: '/pages/merchant-staff-audit/merchant-staff-audit' });
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,47 +1,162 @@
|
||||
<view class="sub-page sj-page">
|
||||
|
||||
<view class="status-bar" style="height:{{statusBar}}px"></view>
|
||||
|
||||
<view class="sub-nav flexb">
|
||||
|
||||
<text class="back" bindtap="goBack">‹</text>
|
||||
|
||||
<text class="sub-nav-title">客服列表</text>
|
||||
|
||||
<text class="back placeholder"></text>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<scroll-view scroll-y class="sub-scroll" enhanced show-scrollbar="{{false}}">
|
||||
<view wx:for="{{list}}" wx:key="id" class="kefu-card sj-mx20">
|
||||
|
||||
<view wx:for="{{list}}" wx:key="id" class="kefu-card sj-mx20 {{item.isDisabled ? 'disabled-card' : ''}}">
|
||||
|
||||
<view class="kefu-head flex">
|
||||
|
||||
<image class="kefu-av" src="{{item.avatar}}" mode="aspectFill"/>
|
||||
|
||||
<view class="kefu-info">
|
||||
|
||||
<view class="flex">
|
||||
|
||||
<text class="kefu-name">{{item.nickname}}</text>
|
||||
|
||||
<text class="badge badge-role">{{item.role_name || '客服'}}</text>
|
||||
|
||||
<text wx:if="{{item.isChief}}" class="badge badge-chief">总管</text>
|
||||
|
||||
<text wx:if="{{item.isSelf}}" class="badge badge-self">本人</text>
|
||||
|
||||
<text wx:if="{{item.isFinance}}" class="badge badge-finance">财务</text>
|
||||
|
||||
<text wx:if="{{item.isDisabled}}" class="badge badge-off">已封禁</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="uid-row flex">
|
||||
|
||||
<text class="kefu-uid">ID: {{item.uid}}</text>
|
||||
|
||||
<text class="copy-link" bindtap="copyUid" data-uid="{{item.uid}}">复制</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="quota-row">
|
||||
|
||||
<text class="quota-label">剩余额度</text>
|
||||
|
||||
<text class="quota-val">¥{{item.quotaAvailable}}</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="kefu-stats flexa">
|
||||
|
||||
<view class="stat-box flexmc">
|
||||
<text class="stat-n">{{item.orderCount}}</text>
|
||||
<text class="stat-l">接单数</text>
|
||||
|
||||
<text class="stat-n">{{item.todayDispatchCount}}</text>
|
||||
|
||||
<text class="stat-l">今日派单</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="stat-box flexmc">
|
||||
|
||||
<text class="stat-n">{{item.dispatchCount}}</text>
|
||||
|
||||
<text class="stat-l">累计派单</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="stat-box flexmc">
|
||||
|
||||
<text class="stat-n">{{item.settleCount}}</text>
|
||||
<text class="stat-l">结算数</text>
|
||||
|
||||
<text class="stat-l">结单数</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="stat-box flexmc">
|
||||
<text class="stat-n">¥{{item.settleAmount}}</text>
|
||||
<text class="stat-l">结算金额</text>
|
||||
|
||||
<text class="stat-n">{{item.refundCount}}</text>
|
||||
|
||||
<text class="stat-l">退款数</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="kefu-actions flexa">
|
||||
<text class="act" bindtap="onAction">查看订单</text>
|
||||
<text class="act" bindtap="onAction">权限管理</text>
|
||||
<text class="act act-del" bindtap="onAction">删除</text>
|
||||
|
||||
<view class="kefu-stats amount-stats flexa">
|
||||
|
||||
<view class="stat-box flexmc">
|
||||
|
||||
<text class="stat-n stat-amt">¥{{item.todayDispatchAmount}}</text>
|
||||
|
||||
<text class="stat-l">今日派单额</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="stat-box flexmc">
|
||||
|
||||
<text class="stat-n stat-amt">¥{{item.dispatchAmount}}</text>
|
||||
|
||||
<text class="stat-l">派单金额</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="stat-box flexmc">
|
||||
|
||||
<text class="stat-n stat-amt">¥{{item.settleAmount}}</text>
|
||||
|
||||
<text class="stat-l">结算金额</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="stat-box flexmc">
|
||||
|
||||
<text class="stat-n stat-amt">¥{{item.refundAmount}}</text>
|
||||
|
||||
<text class="stat-l">退款金额</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!item.isSelf}}" class="kefu-actions flexa">
|
||||
|
||||
<text class="act" bindtap="onAllocate" data-id="{{item.id}}">划额度</text>
|
||||
|
||||
<text class="act" bindtap="onRevoke" data-id="{{item.id}}" data-available="{{item.quotaRevokable}}">收回额度</text>
|
||||
|
||||
<text class="act" bindtap="onAssignRole" data-id="{{item.id}}">换角色</text>
|
||||
|
||||
<text class="act" bindtap="onToggleDisable" data-id="{{item.id}}" data-disabled="{{item.isDisabled}}">{{item.isDisabled ? '解封' : '封禁'}}</text>
|
||||
|
||||
<text wx:if="{{isOwner}}" class="act act-del" bindtap="onRemove" data-id="{{item.id}}">移除</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view wx:else class="self-tip sj-mx20">本人账号不可在此操作,如需调整请联系商家老板</view>
|
||||
|
||||
</view>
|
||||
<view class="load-tip">预览 · 接口就绪后对接真实数据</view>
|
||||
|
||||
<view wx:if="{{!list.length}}" class="load-tip">暂无客服成员</view>
|
||||
|
||||
<view class="load-tip link-tip" bindtap="goAudit">操作日志 ›</view>
|
||||
|
||||
<view class="load-tip link-tip" bindtap="goRank">客服排行 ›</view>
|
||||
|
||||
</scroll-view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
@@ -31,6 +31,34 @@
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.kefu-card.disabled-card { opacity: 0.72; }
|
||||
|
||||
.badge-role {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
background: #f0f0f0;
|
||||
color: #666;
|
||||
border-radius: 6rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.badge-off {
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.quota-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12rpx 0 8rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.quota-label { font-size: 24rpx; color: #888; }
|
||||
.quota-val { font-size: 30rpx; color: #C9A962; font-weight: 600; }
|
||||
|
||||
.kefu-av {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
@@ -55,6 +83,15 @@
|
||||
|
||||
.badge-chief { background: #ffb508; }
|
||||
.badge-finance { background: #1b6ef3; }
|
||||
.badge-self { background: #9e9e9e; }
|
||||
|
||||
.self-tip {
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx 0 4rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.kefu-uid {
|
||||
font-size: 24rpx;
|
||||
@@ -75,12 +112,23 @@
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.amount-stats {
|
||||
margin-top: 0;
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.stat-n {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #e67e22;
|
||||
}
|
||||
|
||||
.stat-amt {
|
||||
font-size: 24rpx;
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
.stat-l {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
|
||||
Reference in New Issue
Block a user