restore: 恢复橙色逍遥梦UI版本(2ea2860)到主分支

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-09 00:17:03 +08:00
parent 267de7c780
commit 566aeaa3b7
228 changed files with 22076 additions and 11864 deletions

112
app.js
View File

@@ -3,6 +3,11 @@ import GoEasy from './static/lib/goeasy-2.13.24.esm.min';
const ChatCore = require('./utils/chat-core');
import { ensurePhoneAuth } from './utils/phone-auth';
import { getPrimaryRole, lockPrimaryRole, migrateLegacyCenterRole, PRIMARY_DEFAULT_PAGES } from './utils/primary-role';
import { setClubId, getConfiguredClubId, buildClubHeaders, getClubId } from './utils/club-context';
import { CLUB_ID, WX_APP_ID } from './config/club-config';
import { applyMiniappAssetsFromConfig, warmupPindaoConfig } from './utils/miniapp-icons.js';
import { refreshDashouMembership } from './utils/dashou-profile.js';
const { getDefaultAvatarUrl, resolveAvatarFromStorage } = require('./utils/avatar.js');
// 三端固定首页
const roleDefaultPage = PRIMARY_DEFAULT_PAGES;
@@ -36,7 +41,8 @@ App({
guanshiqun: '',
guanshiqunid: '',
appId:'wx0e4be86faac4a8d1',
appId: WX_APP_ID,
clubId: CLUB_ID,
shangjiastatus: 0,
dashoustatus: 0,
@@ -49,11 +55,7 @@ App({
yajin: null,
chenghao: '',
jinfen: null,
clumber: [{
huiyuanid: '',
huiyuanming: '',
daoqi: ''
}],
clumber: [],
chengjiaoliang: null,
zaixianZhuangtai: null,
@@ -80,6 +82,8 @@ App({
xshenfen: 1,
goEasyConfig: null,
chatEnabled: false,
groupInfoMap: {},
kefuConfig: {
link: '',
enterpriseId: ''
@@ -151,6 +155,7 @@ App({
pageState: {
currentPage: '',
isInChatPage: false,
isInGroupChat: false,
currentChatId: '',
lastPageUpdate: 0
},
@@ -159,11 +164,23 @@ App({
debugMode: false,
currentUser: null,
currentRole: 'normal',
primaryRole: 'normal'
primaryRole: 'normal',
/** 客服浮钮full | minionLaunch 重置为 full */
kefuFloatMode: 'full',
/** 本次启动内隐藏客服浮钮(不持久化,杀进程后恢复) */
kefuFloatSessionHidden: false,
kefuFloatY: null,
/** 从「我的」跳抢单页时高亮指定单 */
_acceptOrderHighlight: '',
},
// 核心启动流程
async onLaunch() {
this.globalData.kefuFloatMode = 'full';
this.globalData.kefuFloatSessionHidden = false;
try { wx.removeStorageSync('kefuFloatPermanentHidden'); } catch (e) {}
setClubId(getConfiguredClubId(), this);
this.globalData.clubId = CLUB_ID;
// ① 隐藏返回首页按钮
wx.hideHomeButton();
wx.onAppRoute((res) => {
@@ -193,6 +210,11 @@ App({
if (!phoneOk) return;
}
// ②c 打手端冷启动:同步会员/押金等到 globalData抢单页依赖 clumber
if (wx.getStorageSync('token') && savedRole === 'dashou') {
refreshDashouMembership(this).catch(() => {});
}
const targetPage = roleDefaultPage[savedRole] || '/pages/index/index';
if (savedRole !== 'normal') {
setTimeout(() => {
@@ -214,13 +236,11 @@ App({
this.emitEvent('staffContextChanged', {});
}
// ⑤ 建立连接
const saved = this.getSavedConnection();
console.log('【启动】缓存身份:', saved ? saved.identityType : '无',
'userId:', saved ? saved.userId : '无');
// ⑤ 建立连接(文赫式:全局监听,不反复 disconnect
setTimeout(() => {
if (this.startImWhenReady) this.startImWhenReady();
else if (this.ensureConnection) this.ensureConnection();
if (this.globalData.chatEnabled && this.ensureConnection) {
this.ensureConnection();
}
}, 300);
// ⑥ 获取远程配置
@@ -228,10 +248,16 @@ App({
.then(latestConfig => {
this.saveConfigToStorage(latestConfig);
this.applyDynamicConfig(latestConfig);
warmupPindaoConfig(this);
try {
const { fetchGonggaoLunbo } = require('./utils/display-config');
fetchGonggaoLunbo(this, 'accept_order').catch(() => {});
} catch (e) {}
this.initGoEasyWithConfig();
this.initCurrentUser();
if (this.startImWhenReady) this.startImWhenReady();
else if (this.connectForCurrentRole) this.connectForCurrentRole();
if (this.globalData.chatEnabled && this.ensureConnection) {
this.ensureConnection();
}
console.log('远程配置更新完成');
})
.catch(err => {
@@ -239,10 +265,25 @@ App({
});
},
/** 从后台切回前台时再次向后端校验 */
/** 从后台切回前台时再次向后端校验,并维持 IM 连接 */
async onShow() {
if (!wx.getStorageSync('token')) return;
await ensurePhoneAuth({ redirect: true });
if (this.globalData.chatEnabled && typeof this.startImWhenReady === 'function') {
this.startImWhenReady();
} else if (this.globalData.chatEnabled && typeof this.ensureConnection === 'function') {
this.globalData.goEasyConnection.autoReconnect = true;
if (typeof this.restoreTabBarBadge === 'function') {
this.restoreTabBarBadge();
}
this.ensureConnection();
if (typeof this.syncConnectionStatus === 'function') {
setTimeout(() => this.syncConnectionStatus(), 800);
}
if (typeof this.loadConversations === 'function') {
setTimeout(() => this.loadConversations(), 300);
}
}
},
// 连接管理方法
@@ -258,11 +299,16 @@ App({
},
// 配置缓存读写
CONFIG_CACHE_KEY: 'app_dynamic_config',
CONFIG_CACHE_KEY_PREFIX: 'app_dynamic_config_',
getConfigCacheKey() {
const cid = getClubId(this) || CLUB_ID;
return this.CONFIG_CACHE_KEY_PREFIX + cid;
},
readConfigFromStorage() {
try {
const data = wx.getStorageSync(this.CONFIG_CACHE_KEY);
const data = wx.getStorageSync(this.getConfigCacheKey());
if (data && typeof data === 'object') {
return data;
}
@@ -273,7 +319,7 @@ App({
saveConfigToStorage(config) {
try {
if (config && typeof config === 'object') {
wx.setStorageSync(this.CONFIG_CACHE_KEY, config);
wx.setStorageSync(this.getConfigCacheKey(), config);
}
} catch (e) {}
},
@@ -284,7 +330,7 @@ App({
wx.request({
url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq',
method: 'POST',
header: { 'content-type': 'application/json' },
header: buildClubHeaders({ 'content-type': 'application/json' }),
success: (res) => {
if (res.statusCode === 200 && res.data && res.data.code === 0 && res.data.data) {
resolve(res.data.data);
@@ -307,7 +353,7 @@ App({
wx.request({
url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq',
method: 'POST',
header: { 'content-type': 'application/json' },
header: buildClubHeaders({ 'content-type': 'application/json' }),
success: (res) => {
if (res.statusCode === 200 && res.data && res.data.code === 0) {
resolve(res.data.data);
@@ -350,6 +396,19 @@ App({
enterpriseId: config.kefu.enterpriseId || ''
};
}
try {
applyMiniappAssetsFromConfig(this, config);
} catch (e) {
console.warn('miniapp assets apply failed', e);
}
if (this.emitEvent) {
this.emitEvent('configApplied', {
ossImageUrl: this.globalData.ossImageUrl,
morentouxiang: this.globalData.morentouxiang,
});
}
},
// 仅在有效 appkey 时初始化
@@ -373,9 +432,10 @@ App({
modules: ['im', 'pubsub']
});
wx.GoEasy = GoEasy;
this.globalData.chatEnabled = true;
console.log('GoEasy 初始化成功');
if (this.startImWhenReady && wx.getStorageSync('uid')) {
setTimeout(() => this.startImWhenReady(), 100);
if (this.ensureConnection && wx.getStorageSync('uid')) {
setTimeout(() => this.ensureConnection(), 100);
}
} catch (error) {
console.error('GoEasy 初始化失败:', error);
@@ -385,10 +445,12 @@ App({
initCurrentUser() {
const uid = wx.getStorageSync('uid');
if (uid) {
const def = getDefaultAvatarUrl(this);
const avatar = resolveAvatarFromStorage(this) || def;
this.globalData.currentUser = {
id: uid,
name: '用户' + uid.substring(0, 6),
avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang
name: wx.getStorageSync('nicheng') || ('用户' + uid.substring(0, 6)),
avatar: avatar || def,
};
}
},

323
app.json
View File

@@ -2,82 +2,252 @@
"pages": [
"pages/index/index",
"pages/category/category",
"pages/pick-user/pick-user",
"pages/messages/messages",
"pages/mine/mine",
"pages/product-detail/product-detail",
"pages/submit/submit",
"pages/edit/edit",
"pages/fighter/fighter",
"pages/merchant/merchant",
"pages/merchant-home/merchant-home",
"pages/merchant-kefu-key/merchant-kefu-key",
"pages/merchant-kefu-list/merchant-kefu-list",
"pages/staff-join/staff-join",
"pages/merchant-staff-audit/merchant-staff-audit",
"pages/merchant-staff-role/merchant-staff-role",
"pages/manager/manager",
"pages/orders/orders",
"pages/order-detail/order-detail",
"pages/fighter-rules/fighter-rules",
"pages/fighter-edit/fighter-edit",
"pages/fighter-rank/fighter-rank",
"pages/fighter-recharge/fighter-recharge",
"pages/fighter-msg/fighter-msg",
"pages/fighter-orders/fighter-orders",
"pages/accept-order/accept-order",
"pages/withdraw/withdraw",
"pages/invite-fighter/invite-fighter",
"pages/my-fighter/my-fighter",
"pages/recharge-log/recharge-log",
"pages/manager-rank/manager-rank",
"pages/merchant-dispatch/merchant-dispatch",
"pages/fighter-orders/fighter-orders",
"pages/fighter/fighter",
"pages/merchant-home/merchant-home",
"pages/merchant-orders/merchant-orders",
"pages/merchant-msg/merchant-msg",
"pages/merchant-rank/merchant-rank",
"pages/merchant-recharge/merchant-recharge",
"pages/merchant-order-detail/merchant-order-detail",
"pages/fighter-order-detail/fighter-order-detail",
"pages/chat/chat",
"pages/express-order/express-order",
"pages/order-pool/order-pool",
"pages/poster/poster",
"pages/gold-fighter/gold-fighter",
"pages/leader/leader",
"pages/manager-assign/manager-assign",
"pages/leader-bonus-log/leader-bonus-log",
"pages/invite-manager/invite-manager",
"pages/order-pool2/order-pool2",
"pages/group-chat/group-chat",
"pages/cs-chat/cs-chat",
"pages/verify/verify",
"pages/merchant/merchant",
"components/global-notification/global-notification",
"components/popup-notice/popup-notice",
"components/order-card/order-card",
"components/order-sender/order-sender",
"pages/penalty/penalty/penalty",
"pages/penalty/components/fakuan-list/fakuan-list",
"pages/penalty/components/jifen-list/jifen-list",
"pages/penalty/components/fakuan-pay/fakuan-pay",
"pages/phone-auth/phone-auth",
"components/chenghao-tag/chenghao-tag",
"pages/assessor/assessor",
"pages/assess-score/assess-score",
"pages/assess-log/assess-log",
"pages/assess-center/assess-center",
"pages/assess-gold/assess-gold",
"pages/escort-orders/escort-orders",
"pages/withdraw/components/mode1/mode1",
"pages/withdraw/components/mode2/mode2"
"components/chenghao-tag/chenghao-tag"
],
"subPackages": [
{
"root": "pages/dashouduan",
"pages": ["dashouduan"]
},
{
"root": "pages/guanshiduan",
"pages": ["guanshiduan"]
},
{
"root": "pages/pick-user",
"pages": ["pick-user"]
},
{
"root": "pages/product-detail",
"pages": ["product-detail"]
},
{
"root": "pages/submit",
"pages": ["submit"]
},
{
"root": "pages/edit",
"pages": ["edit"]
},
{
"root": "pages/merchant-kefu-key",
"pages": ["merchant-kefu-key"]
},
{
"root": "pages/merchant-kefu-list",
"pages": ["merchant-kefu-list"]
},
{
"root": "pages/staff-join",
"pages": ["staff-join"]
},
{
"root": "pages/merchant-staff-audit",
"pages": ["merchant-staff-audit"]
},
{
"root": "pages/merchant-staff-role",
"pages": ["merchant-staff-role"]
},
{
"root": "pages/manager",
"pages": ["manager"]
},
{
"root": "pages/orders",
"pages": ["orders"]
},
{
"root": "pages/order-detail",
"pages": ["order-detail"]
},
{
"root": "pages/fighter-rules",
"pages": ["fighter-rules"]
},
{
"root": "pages/fighter-edit",
"pages": ["fighter-edit"]
},
{
"root": "pages/fighter-rank",
"pages": ["fighter-rank"]
},
{
"root": "pages/fighter-recharge",
"pages": ["fighter-recharge", "fighter-deposit"]
},
{
"root": "pages/fighter-ledger",
"pages": ["fighter-ledger"]
},
{
"root": "pages/fighter-msg",
"pages": ["fighter-msg"]
},
{
"root": "pages/dashou-exam",
"pages": ["dashou-exam"]
},
{
"root": "pages/withdraw",
"pages": ["withdraw", "withdraw-records", "components/mode1/mode1", "components/mode2/mode2"]
},
{
"root": "pages/invite-fighter",
"pages": ["invite-fighter"]
},
{
"root": "pages/my-fighter",
"pages": ["my-fighter"]
},
{
"root": "pages/recharge-log",
"pages": ["recharge-log"]
},
{
"root": "pages/manager-rank",
"pages": ["manager-rank"]
},
{
"root": "pages/merchant-data-stats",
"pages": ["merchant-data-stats"]
},
{
"root": "pages/merchant-dispatch",
"pages": ["merchant-dispatch"]
},
{
"root": "pages/merchant-regular-dispatch",
"pages": ["merchant-regular-dispatch"]
},
{
"root": "pages/merchant-msg",
"pages": ["merchant-msg"]
},
{
"root": "pages/merchant-penalty",
"pages": ["merchant-penalty"]
},
{
"root": "pages/merchant-rank",
"pages": ["merchant-rank"]
},
{
"root": "pages/merchant-recharge",
"pages": ["merchant-recharge"]
},
{
"root": "pages/merchant-order-detail",
"pages": ["merchant-order-detail"]
},
{
"root": "pages/fighter-order-detail",
"pages": ["fighter-order-detail"]
},
{
"root": "pages/chat",
"pages": ["chat"]
},
{
"root": "pages/express-order",
"pages": ["express-order"]
},
{
"root": "pages/order-pool",
"pages": ["order-pool"]
},
{
"root": "pages/poster",
"pages": ["poster"]
},
{
"root": "pages/gold-fighter",
"pages": ["gold-fighter"]
},
{
"root": "pages/leader",
"pages": ["leader"]
},
{
"root": "pages/manager-assign",
"pages": ["manager-assign"]
},
{
"root": "pages/leader-bonus-log",
"pages": ["leader-bonus-log"]
},
{
"root": "pages/invite-manager",
"pages": ["invite-manager"]
},
{
"root": "pages/group-chat",
"pages": ["group-chat"]
},
{
"root": "pages/cs-chat",
"pages": ["cs-chat"]
},
{
"root": "pages/verify",
"pages": ["verify"]
},
{
"root": "pages/penalty",
"pages": [
"penalty/penalty",
"components/fakuan-list/fakuan-list",
"components/jifen-list/jifen-list",
"components/fakuan-pay/fakuan-pay"
]
},
{
"root": "pages/phone-auth",
"pages": ["phone-auth"]
},
{
"root": "pages/assessor",
"pages": ["assessor"]
},
{
"root": "pages/escort-orders",
"pages": ["escort-orders"]
},
{
"root": "pages/assess-center",
"name": "assess",
"pages": ["assess-center"]
},
{
"root": "pages/assess-score",
"pages": ["assess-score"]
},
{
"root": "pages/assess-log",
"pages": ["assess-log"]
},
{
"root": "pages/assess-gold",
"pages": ["assess-gold"]
}
],
"usingComponents": {
"popup-notice": "/components/popup-notice/popup-notice",
"tab-bar": "/tab-bar/index"
},
"window": {
"navigationBarTextStyle": "black",
"navigationStyle": "default",
@@ -88,25 +258,32 @@
"tabBar": {
"custom": true,
"list": [
{
"pagePath": "pages/order-pool/order-pool",
"pagePath": "pages/accept-order/accept-order",
"text": "接单池",
"iconPath": "/images/order-pool.png",
"selectedIconPath": "/images/order-pool.png"
},
{
"pagePath": "pages/order-pool2/order-pool2",
"text": "接单池",
"iconPath": "/images/order-pool.png",
"selectedIconPath": "/images/order-pool.png"
"pagePath": "pages/fighter-orders/fighter-orders",
"text": "订单",
"iconPath": "/images/orders.png",
"selectedIconPath": "/images/orders.png"
},
{
"pagePath": "pages/messages/messages",
"text": "消息",
"iconPath": "/images/messages.png",
"selectedIconPath": "/images/messages.png"
},
{
"pagePath": "pages/fighter/fighter",
"text": "我的",
"iconPath": "/images/mine.png",
"selectedIconPath": "/images/mine.png"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"

View File

@@ -2,15 +2,6 @@
/* ========== CSS 变量 ========== */
page {
/* 星阙紫色主题 */
--theme-primary: #9333ea;
--theme-primary-light: #a855f7;
--theme-primary-lighter: #c4b5fd;
--theme-primary-lightest: #ede9fe;
--theme-bg-tint: #f5f3ff;
--theme-gradient: linear-gradient(180deg, #c4b5fd, #a78bfa);
--theme-gradient-bg: linear-gradient(180deg, #c4b5fd 0%, #fff 28%, #f5f3ff 100%);
--color-price: #ff4444;
--color-text-main: #333;
--color-text-gray: #999;
@@ -467,7 +458,7 @@ page {
.tag-warning {
background: rgba(250, 173, 20, 0.1);
color: #a855f7;
color: #faad14;
}
.tag-danger {
@@ -477,7 +468,7 @@ page {
.tag-gold {
background: rgba(201, 169, 98, 0.1);
color: #9333ea;
color: #C9A962;
}
/* ========== 页面容器 ========== */
@@ -524,8 +515,8 @@ page {
}
.btn-primary--gold {
background: linear-gradient(135deg, #9333ea, #a855f7);
box-shadow: 0 8rpx 20rpx rgba(147, 51, 234, 0.3);
background: linear-gradient(135deg, #C9A962, #D4B56A);
box-shadow: 0 8rpx 20rpx rgba(201, 169, 98, 0.3);
}
.btn-primary--green {
@@ -539,8 +530,8 @@ page {
}
.btn-primary--orange {
background: linear-gradient(135deg, #a855f7, #7c3aed);
box-shadow: 0 8rpx 20rpx rgba(124, 58, 237, 0.3);
background: linear-gradient(135deg, #ffb347, #ff8c1a);
box-shadow: 0 8rpx 20rpx rgba(255, 140, 26, 0.3);
}
.btn-primary--purple {
@@ -574,8 +565,8 @@ page {
}
.btn-sm--gold {
background: linear-gradient(135deg, #c4b5fd, #9333ea);
box-shadow: 0 6rpx 16rpx rgba(147, 51, 234, 0.3);
background: linear-gradient(135deg, #ffcc00, #ffaa00);
box-shadow: 0 6rpx 16rpx rgba(255, 170, 0, 0.3);
}
.btn-sm--green {
@@ -613,8 +604,8 @@ page {
}
.btn-ghost--gold {
color: #9333ea;
border-color: #9333ea;
color: #C9A962;
border-color: #C9A962;
}
/* 禁用状态 */
@@ -863,8 +854,8 @@ page {
}
.status-tag--warning {
background: #faf5ff;
color: #a855f7;
background: #fffbe6;
color: #faad14;
}
.status-tag--info {
@@ -971,8 +962,8 @@ page {
}
@keyframes crownFloat {
0%, 100% { transform: translateY(0) rotate(5deg) scale(1); filter: drop-shadow(0 0 10rpx rgba(196, 181, 253, 0.8)); }
50% { transform: translateY(-15rpx) rotate(10deg) scale(1.1); filter: drop-shadow(0 0 25rpx rgba(196, 181, 253, 1)); }
0%, 100% { transform: translateY(0) rotate(5deg) scale(1); filter: drop-shadow(0 0 10rpx rgba(255, 215, 0, 0.8)); }
50% { transform: translateY(-15rpx) rotate(10deg) scale(1.1); filter: drop-shadow(0 0 25rpx rgba(255, 215, 0, 1)); }
}
@keyframes haloRotate {
@@ -1112,14 +1103,14 @@ page {
}
.chat-order-bubble {
background: #f5f3ff;
background: #fff7e0;
padding: 12rpx;
border-radius: 8rpx;
}
.chat-order-label {
font-size: 24rpx;
color: #a855f7;
color: #e6a23c;
font-weight: 600;
}
@@ -1614,7 +1605,7 @@ page {
.rank-card--1 .rank-podium-base { height: 80rpx; }
.rank-podium-gold { background: linear-gradient(180deg, rgba(196, 181, 253, 0.3) 0%, rgba(196, 181, 253, 0.1) 100%); border: 1rpx solid rgba(196, 181, 253, 0.4); }
.rank-podium-gold { background: linear-gradient(180deg, rgba(255, 215, 0, 0.3) 0%, rgba(255, 215, 0, 0.1) 100%); border: 1rpx solid rgba(255, 215, 0, 0.4); }
.rank-podium-silver { background: linear-gradient(180deg, rgba(192, 192, 192, 0.3) 0%, rgba(192, 192, 192, 0.1) 100%); border: 1rpx solid rgba(192, 192, 192, 0.4); }
.rank-podium-bronze { background: linear-gradient(180deg, rgba(205, 127, 50, 0.3) 0%, rgba(205, 127, 50, 0.1) 100%); border: 1rpx solid rgba(205, 127, 50, 0.4); }
@@ -1642,7 +1633,7 @@ page {
font-size: 85rpx;
position: relative;
z-index: 2;
filter: drop-shadow(0 0 10rpx rgba(196, 181, 253, 0.8));
filter: drop-shadow(0 0 10rpx rgba(255, 215, 0, 0.8));
animation: crownFloat 3s ease-in-out infinite;
transform: rotate(5deg);
}
@@ -1654,7 +1645,7 @@ page {
transform: translate(-50%, -50%);
width: 200rpx;
height: 200rpx;
background: radial-gradient(circle, rgba(196, 181, 253, 0.3) 0%, rgba(196, 181, 253, 0.1) 40%, transparent 70%);
background: radial-gradient(circle, rgba(255, 215, 0, 0.3) 0%, rgba(255, 215, 0, 0.1) 40%, transparent 70%);
z-index: 0;
animation: glowPulse 2s ease-in-out infinite;
}
@@ -1686,7 +1677,7 @@ page {
.rank-card--1 .rank-avatar-container { border-width: 6rpx; }
.rank-frame-gold .rank-avatar-container { border-color: rgba(196, 181, 253, 0.3); filter: drop-shadow(0 0 30rpx rgba(196, 181, 253, 0.5)); }
.rank-frame-gold .rank-avatar-container { border-color: rgba(255, 215, 0, 0.3); filter: drop-shadow(0 0 30rpx rgba(255, 215, 0, 0.5)); }
.rank-frame-silver .rank-avatar-container { border-color: rgba(192, 192, 192, 0.3); filter: drop-shadow(0 0 25rpx rgba(192, 192, 192, 0.5)); }
.rank-frame-bronze .rank-avatar-container { border-color: rgba(205, 127, 50, 0.3); filter: drop-shadow(0 0 25rpx rgba(205, 127, 50, 0.5)); }
@@ -1710,7 +1701,7 @@ page {
animation: haloRotate 10s linear infinite;
}
.rank-halo-gold { width: 220rpx; height: 220rpx; background: radial-gradient(circle, rgba(196, 181, 253, 0.4) 0%, rgba(196, 181, 253, 0.2) 30%, transparent 70%); }
.rank-halo-gold { width: 220rpx; height: 220rpx; background: radial-gradient(circle, rgba(255, 215, 0, 0.4) 0%, rgba(255, 215, 0, 0.2) 30%, transparent 70%); }
.rank-card--1 .rank-halo-gold { width: 260rpx; height: 260rpx; }
.rank-halo-silver { width: 200rpx; height: 200rpx; background: radial-gradient(circle, rgba(192, 192, 192, 0.4) 0%, rgba(192, 192, 192, 0.2) 30%, transparent 70%); }
.rank-halo-bronze { width: 200rpx; height: 200rpx; background: radial-gradient(circle, rgba(205, 127, 50, 0.4) 0%, rgba(205, 127, 50, 0.2) 30%, transparent 70%); }
@@ -1748,7 +1739,7 @@ page {
.rank-card--1 .rank-badge { width: 70rpx; height: 70rpx; font-size: 32rpx; bottom: -15rpx; right: -15rpx; }
.rank-badge-gold { background: linear-gradient(135deg, #c4b5fd, #9333ea); }
.rank-badge-gold { background: linear-gradient(135deg, #ffd700, #ff9800); }
.rank-badge-silver { background: linear-gradient(135deg, #c0c0c0, #808080); }
.rank-badge-bronze { background: linear-gradient(135deg, #cd7f32, #a0522d); }
@@ -1763,7 +1754,7 @@ page {
overflow: hidden;
}
.rank-card--1 .rank-user-info-card { border-color: rgba(196, 181, 253, 0.3); background: rgba(20, 15, 5, 0.95); }
.rank-card--1 .rank-user-info-card { border-color: rgba(255, 215, 0, 0.3); background: rgba(20, 15, 5, 0.95); }
.rank-card--2 .rank-user-info-card { border-color: rgba(192, 192, 192, 0.3); background: rgba(25, 25, 35, 0.95); }
.rank-card--3 .rank-user-info-card { border-color: rgba(205, 127, 50, 0.3); background: rgba(30, 20, 10, 0.95); }
@@ -1778,7 +1769,7 @@ page {
text-align: center;
}
.rank-card--1 .rank-user-name { color: #c4b5fd; text-shadow: 0 0 15rpx rgba(196, 181, 253, 0.7); font-size: 34rpx; }
.rank-card--1 .rank-user-name { color: #ffd700; text-shadow: 0 0 15rpx rgba(255, 215, 0, 0.7); font-size: 34rpx; }
.rank-card--2 .rank-user-name { color: #c0c0c0; text-shadow: 0 0 10rpx rgba(192, 192, 192, 0.7); }
.rank-card--3 .rank-user-name { color: #cd7f32; text-shadow: 0 0 10rpx rgba(205, 127, 50, 0.7); }
@@ -1816,7 +1807,7 @@ page {
margin-right: 4rpx;
}
.rank-card--1 .rank-currency { color: #c4b5fd; }
.rank-card--1 .rank-currency { color: #ffd700; }
.rank-card--2 .rank-currency { color: #c0c0c0; }
.rank-card--3 .rank-currency { color: #cd7f32; }
@@ -1827,7 +1818,7 @@ page {
text-shadow: 0 0 10rpx rgba(74, 222, 128, 0.5);
}
.rank-card--1 .rank-amount { color: #c4b5fd; text-shadow: 0 0 15rpx rgba(196, 181, 253, 0.7); }
.rank-card--1 .rank-amount { color: #ffd700; text-shadow: 0 0 15rpx rgba(255, 215, 0, 0.7); }
.rank-card--2 .rank-amount { color: #c0c0c0; text-shadow: 0 0 10rpx rgba(192, 192, 192, 0.7); }
.rank-card--3 .rank-amount { color: #cd7f32; text-shadow: 0 0 10rpx rgba(205, 127, 50, 0.7); }

View File

@@ -1,35 +1,90 @@
// components/chenghao-tag/chenghao-tag.js
const app = getApp();
const PILL_SHAPES = ['pill', 'rectangle', 'rounded'];
Component({
properties: {
mingcheng: { type: String, value: '' },
texiaoJson: { type: Object, value: {} }
texiaoJson: { type: null, value: null },
},
data: {
// 默认值宽152高52六边形无背景图无动画白色字
width: 152,
height: 52,
shapeClass: 'liubianxing', // 保证至少不是矩形
inlineStyle: '',
shapeClass: 'tag-pill',
animationClass: '',
bgStyle: '',
textColor: '#FFFFFF',
textSize: 22,
imageUrl: ''
imageUrl: '',
isPill: true,
},
observers: {
'texiaoJson, mingcheng': function () {
this._applyConfig(this.properties.texiaoJson);
},
},
lifetimes: {
attached() {
const cfg = this.properties.texiaoJson || {};
this._applyConfig(this.properties.texiaoJson);
},
},
methods: {
_applyConfig(raw) {
let cfg = raw || {};
if (typeof cfg === 'string') {
try {
cfg = JSON.parse(cfg);
} catch (e) {
cfg = {};
}
}
if (Array.isArray(cfg)) {
cfg = cfg[0] || {};
}
if (!cfg || typeof cfg !== 'object') {
cfg = {};
}
const ossImageUrl = app.globalData.ossImageUrl || '';
// 1. 尺寸稍大一些一行能放3~4个
const width = cfg.width || 152;
const height = cfg.height || 52;
// 无 shape身份装饰标签 / 自定义色圆角标;有 shape考核称号等特殊形状
const isPill = !cfg.shape || PILL_SHAPES.includes(cfg.shape);
// 2. 背景处理:优先背景图,否则用渐变/纯色
if (isPill) {
const height = Number(cfg.height) || 44;
const minWidth = Number(cfg.width) || 72;
const solidColor = cfg.bg_color || cfg.background || cfg.color;
let bgStyle = '';
if (cfg.bg_gradient) {
bgStyle = `background: ${cfg.bg_gradient};`;
} else if (solidColor) {
bgStyle = `background: ${solidColor};`;
} else {
bgStyle = 'background: linear-gradient(135deg, #FFD700, #FF8C00);';
}
const flash = cfg.flash || cfg.animation === 'shine' || cfg.animation === 'glow';
let animationClass = cfg.animation || '';
if (flash && !animationClass) {
animationClass = 'pill-flash';
}
const borderColor = cfg.borderColor || cfg.border_color;
const borderStyle = borderColor ? `border: 2rpx solid ${borderColor};` : '';
const textColor = cfg.text_color || cfg.textColor || '#FFFFFF';
this.setData({
isPill: true,
shapeClass: 'tag-pill',
animationClass,
inlineStyle: `min-width: ${minWidth}rpx; height: ${height}rpx; padding: 0 18rpx; ${bgStyle} ${borderStyle}`,
textColor,
textSize: cfg.text_size || 22,
imageUrl: '',
});
return;
}
const width = Number(cfg.width) || 152;
const height = Number(cfg.height) || 52;
let bgStyle = '';
let imageUrl = '';
if (cfg.image_url) {
const ossImageUrl = app.globalData.ossImageUrl || '';
imageUrl = cfg.image_url.startsWith('http')
? cfg.image_url
: ossImageUrl + cfg.image_url;
@@ -38,36 +93,27 @@ Component({
} else if (cfg.bg_color) {
bgStyle = `background: ${cfg.bg_color};`;
} else {
// 默认紫色渐变
bgStyle = `background: linear-gradient(135deg, #c4b5fd, #9333ea);`;
bgStyle = 'background: linear-gradient(135deg, #FFD700, #FF8C00);';
}
// 3. 形状(后端传入 shape 字段,默认 liubianxing
const shapeClass = cfg.shape || 'liubianxing';
// 4. 动画
const animationClass = cfg.animation || '';
// 5. 文字
const textColor = cfg.text_color || '#FFFFFF';
const textSize = cfg.text_size || 22;
this.setData({
width, height,
bgStyle, imageUrl,
shapeClass, animationClass,
textColor, textSize
isPill: false,
inlineStyle: `width: ${width}rpx; height: ${height}rpx; ${bgStyle}`,
shapeClass: cfg.shape || 'liubianxing',
animationClass: cfg.animation || '',
textColor: cfg.text_color || '#FFFFFF',
textSize: cfg.text_size || 22,
imageUrl,
});
},
onImageError() {
this.setData({ imageUrl: '' });
if (!this.data.inlineStyle.includes('background')) {
this.setData({
inlineStyle: this.data.inlineStyle + ' background: linear-gradient(135deg, #FFD700, #FF8C00);',
});
}
},
methods: {
// 背景图加载失败时回退为纯色背景
onImageError() {
this.setData({ imageUrl: '' });
// 如果 bgStyle 为空,给个默认背景
if (!this.data.bgStyle) {
this.setData({ bgStyle: 'background: linear-gradient(135deg, #c4b5fd, #9333ea);' });
}
}
}
},
});

View File

@@ -1,18 +1,15 @@
<!-- components/chenghao-tag/chenghao-tag.wxml -->
<view
class="tag-root {{shapeClass}} {{animationClass}}"
style="width: {{width}}rpx; height: {{height}}rpx; {{bgStyle}}"
style="{{inlineStyle}}"
>
<!-- 背景图(如果有) -->
<image
wx:if="{{imageUrl}}"
wx:if="{{imageUrl && !isPill}}"
class="tag-bg-image"
src="{{imageUrl}}"
mode="aspectFill"
binderror="onImageError"
></image>
<!-- 文字层 -->
/>
<text
class="tag-text"
style="color: {{textColor}}; font-size: {{textSize}}rpx;"

View File

@@ -13,6 +13,18 @@
box-sizing: border-box;
}
/* 身份装饰 / 自定义色:圆角标 */
.tag-root.tag-pill {
border-radius: 20rpx;
clip-path: none;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12);
}
.tag-pill .tag-text {
padding: 0;
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.25);
}
/* ========== 背景图 ========== */
.tag-bg-image {
position: absolute;
@@ -85,6 +97,21 @@
animation: glow-anim 2s ease-in-out infinite;
}
@keyframes glow-anim {
0%, 100% { box-shadow: 0 0 8rpx rgba(196, 181, 253, 0.4); }
50% { box-shadow: 0 0 20rpx rgba(196, 181, 253, 0.8), 0 0 40rpx rgba(196, 181, 253, 0.4); }
0%, 100% { box-shadow: 0 0 8rpx rgba(255, 215, 0, 0.4); }
50% { box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.8), 0 0 40rpx rgba(255, 215, 0, 0.4); }
}
/* 身份标签闪光 */
.tag-root.pill-flash::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 60%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.55), transparent);
transform: skewX(-20deg);
animation: shine-anim 2.2s infinite;
z-index: 1;
pointer-events: none;
}

View File

@@ -1,4 +1,4 @@
const { resolveAvatarUrl } = require('../../utils/avatar.js');
const { resolveAvatarUrl, getDefaultAvatarUrl } = require('../../utils/avatar.js');
Component({
properties: {
@@ -53,6 +53,7 @@ Component({
// 静音状态
isMuted: false,
formattedTime: '',
// 自动隐藏计时器
autoHideTimeout: null
@@ -123,7 +124,7 @@ Component({
let avatar = resolveAvatarUrl(
data.avatar || data.message?.senderData?.avatar,
app
);
) || getDefaultAvatarUrl(app);
this.setData({
show: true,
@@ -131,6 +132,7 @@ Component({
message: data.content || '[消息内容]',
avatar: avatar,
notificationData: data,
formattedTime: this.formatTime(data.timestamp),
progress: 100,
swipingClass: '',
swipeStyle: ''
@@ -149,6 +151,7 @@ Component({
this.clearTimers();
this.setData({
show: false,
formattedTime: '',
progress: 100,
swipingClass: '',
swipeStyle: ''
@@ -266,6 +269,13 @@ Component({
this.hideNotification();
},
onAvatarError() {
const def = getDefaultAvatarUrl(getApp());
if (def && this.data.avatar !== def) {
this.setData({ avatar: def });
}
},
onMute(e) {
e.stopPropagation();
const newMutedState = !this.data.isMuted;

View File

@@ -15,14 +15,15 @@
<!-- 通知内容 -->
<view class="notification-content">
<!-- 左侧头像 -->
<image class="notification-avatar" src="{{avatar}}" mode="aspectFill"></image>
<image class="notification-avatar" src="{{avatar}}" mode="aspectFill" binderror="onAvatarError"></image>
<!-- 中间内容区域 -->
<view class="notification-body">
<view class="notification-tag">新消息</view>
<view class="notification-title">{{title}}</view>
<view class="notification-message">{{message}}</view>
<view class="notification-time" wx:if="{{showTime}}">
{{formatTime(notificationData ? notificationData.timestamp : '')}}
<view class="notification-time" wx:if="{{show && showTime && formattedTime}}">
{{formattedTime}}
</view>
</view>

View File

@@ -1,29 +1,40 @@
.global-notification {
position: fixed;
left: 20rpx;
right: 20rpx;
top: 0rpx;
left: 24rpx;
right: 24rpx;
top: calc(env(safe-area-inset-top) + 108rpx);
z-index: 99999;
opacity: 0;
transform: translateY(-120%);
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
transform: translateY(-160%);
transition: all 0.42s cubic-bezier(0.34, 1.2, 0.64, 1);
pointer-events: none;
overflow: hidden;
border-radius: 24rpx;
background: linear-gradient(135deg,
rgba(41, 47, 61, 0.98) 0%,
rgba(31, 36, 48, 0.98) 100%);
backdrop-filter: blur(30rpx) saturate(180%);
-webkit-backdrop-filter: blur(30rpx) saturate(180%);
border: 1rpx solid rgba(255, 255, 255, 0.08);
border-radius: 28rpx;
background: linear-gradient(145deg,
rgba(28, 32, 42, 0.97) 0%,
rgba(22, 26, 36, 0.98) 100%);
backdrop-filter: blur(40rpx) saturate(180%);
-webkit-backdrop-filter: blur(40rpx) saturate(180%);
border: 1rpx solid rgba(255, 255, 255, 0.1);
box-shadow:
0 20rpx 60rpx rgba(0, 0, 0, 0.4),
0 0 0 1rpx rgba(255, 255, 255, 0.03),
inset 0 1rpx 0 rgba(255, 255, 255, 0.1);
min-height: 140rpx;
0 24rpx 64rpx rgba(0, 0, 0, 0.45),
0 0 0 1rpx rgba(255, 255, 255, 0.04),
inset 0 1rpx 0 rgba(255, 255, 255, 0.12);
min-height: 148rpx;
touch-action: pan-y;
}
.global-notification::before {
content: '';
position: absolute;
left: 0;
top: 24rpx;
bottom: 24rpx;
width: 6rpx;
border-radius: 0 6rpx 6rpx 0;
background: linear-gradient(180deg, #07c160 0%, #D4AF37 100%);
}
.global-notification.show {
opacity: 1;
transform: translateY(0);
@@ -58,8 +69,8 @@
.notification-content {
display: flex;
align-items: center;
padding: 32rpx;
min-height: 140rpx;
padding: 28rpx 28rpx 28rpx 36rpx;
min-height: 148rpx;
position: relative;
}
@@ -84,6 +95,18 @@
justify-content: center;
}
.notification-tag {
display: inline-block;
align-self: flex-start;
font-size: 20rpx;
color: #07c160;
background: rgba(7, 193, 96, 0.15);
padding: 4rpx 14rpx;
border-radius: 20rpx;
margin-bottom: 8rpx;
font-weight: 500;
}
.notification-title {
font-size: 34rpx;
font-weight: 600;

View File

@@ -0,0 +1,170 @@
import { openCustomerServiceChat, resolveKefuIconUrl } from '../../utils/kefu-nav.js';
const CS_HOUR_START = 8;
const CS_HOUR_END = 23;
function getCsOnlineStatus() {
const hour = new Date().getHours();
const online = hour >= CS_HOUR_START && hour < CS_HOUR_END;
return {
csOnline: online,
csHoursText: '8:00-23:00',
csStatusText: online ? '在线' : '在线时间',
};
}
Component({
properties: {
bottom: { type: String, value: '200rpx' },
},
data: {
mode: 'full',
sessionHidden: false,
csUnread: 0,
floatY: 520,
iconUrl: '',
csOnline: false,
csHoursText: '8:00-23:00',
csStatusText: '在线时间',
},
lifetimes: {
attached() {
const app = getApp();
this._syncIcon();
this._syncFromGlobal();
this._initFloatY();
this._convHandler = () => this.syncCsUnread();
this._configHandler = () => this._syncIcon();
if (app.on) {
app.on('conversationsUpdated', this._convHandler);
app.on('unreadCountChanged', this._convHandler);
app.on('configApplied', this._configHandler);
}
this.syncCsUnread();
this._syncOnlineStatus();
},
detached() {
const app = getApp();
if (app.off) {
if (this._convHandler) {
app.off('conversationsUpdated', this._convHandler);
app.off('unreadCountChanged', this._convHandler);
}
if (this._configHandler) {
app.off('configApplied', this._configHandler);
}
}
},
},
pageLifetimes: {
show() {
this._syncIcon();
this._syncFromGlobal();
this.syncCsUnread();
this._syncOnlineStatus();
},
},
methods: {
_syncOnlineStatus() {
this.setData(getCsOnlineStatus());
},
_syncIcon() {
const url = resolveKefuIconUrl(getApp());
if (url && url !== this.data.iconUrl) {
this.setData({ iconUrl: url });
}
},
_initFloatY() {
const app = getApp();
if (app.globalData.kefuFloatY != null) {
this.setData({ floatY: app.globalData.kefuFloatY });
return;
}
try {
const sys = wx.getSystemInfoSync();
const defaultY = Math.floor((sys.windowHeight || 600) * 0.55);
this.setData({ floatY: defaultY });
} catch (e) {
this.setData({ floatY: 520 });
}
},
_syncFromGlobal() {
const app = getApp();
const sessionHidden = !!app.globalData.kefuFloatSessionHidden;
const mode = app.globalData.kefuFloatMode || 'full';
this.setData({ sessionHidden, mode });
},
onFloatMove(e) {
const y = e.detail && e.detail.y;
if (y == null) return;
const app = getApp();
app.globalData.kefuFloatY = y;
},
syncCsUnread() {
if (this.data.sessionHidden) return;
if (!wx.goEasy?.im?.latestConversations) return;
wx.goEasy.im.latestConversations({
onSuccess: (res) => {
const list = res.content?.conversations || res.conversations || [];
let unread = 0;
list.forEach((c) => {
if (c.type === 'cs' || c.teamId === 'support_team') {
unread += c.unread || 0;
}
});
if (unread !== this.data.csUnread) {
this.setData({ csUnread: unread });
}
},
onFailed: () => {},
});
},
onIconError() {
this._syncIcon();
},
onContact() {
openCustomerServiceChat();
},
onHideTap(e) {
if (e && e.stopPropagation) e.stopPropagation();
const app = getApp();
app.globalData.kefuFloatMode = 'mini';
this.setData({ mode: 'mini' });
},
onExpand(e) {
if (e && e.stopPropagation) e.stopPropagation();
const app = getApp();
app.globalData.kefuFloatMode = 'full';
this.setData({ mode: 'full' });
},
onSessionDismiss(e) {
if (e && e.stopPropagation) e.stopPropagation();
wx.showModal({
title: '隐藏联系客服',
content: '本次使用期间不再显示此按钮。清空后台重新进入小程序后会再次出现。',
confirmText: '确定隐藏',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
const app = getApp();
app.globalData.kefuFloatSessionHidden = true;
this.setData({ sessionHidden: true });
}
},
});
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,44 @@
<movable-area class="kefu-movable-area" wx:if="{{!sessionHidden}}">
<movable-view
class="kefu-movable-view"
direction="vertical"
y="{{floatY}}"
damping="40"
friction="2"
bindchange="onFloatMove"
>
<block wx:if="{{mode === 'full'}}">
<view class="kefu-wrap">
<view class="kefu-float kefu-float--full">
<view class="kefu-unread-badge" wx:if="{{csUnread > 0}}">{{csUnread > 99 ? '99+' : csUnread}}</view>
<view class="kefu-float-main" catchtap="onContact">
<image class="kefu-float-icon-img" src="{{iconUrl}}" mode="aspectFit" binderror="onIconError"/>
<view class="kefu-text-col">
<text class="kefu-float-text">联系客服</text>
<text class="kefu-float-sub kefu-float-sub--warn" wx:if="{{csUnread > 0}}">有新消息</text>
<text class="kefu-float-sub {{csOnline ? 'kefu-float-sub--online' : ''}}" wx:else>{{csStatusText}} {{csHoursText}}</text>
</view>
</view>
<view class="kefu-float-close" catchtap="onHideTap">
<text>×</text>
</view>
</view>
<view class="kefu-permanent-row" catchtap="onSessionDismiss">
<text class="kefu-permanent-label">本次不再显示</text>
</view>
</view>
</block>
<block wx:else>
<view class="kefu-wrap kefu-wrap--mini">
<view class="kefu-float kefu-float--mini" catchtap="onContact" bindlongpress="onExpand">
<view class="kefu-unread-badge kefu-unread-badge--mini" wx:if="{{csUnread > 0}}">{{csUnread > 99 ? '99+' : csUnread}}</view>
<image class="kefu-float-mini-icon-img" src="{{iconUrl}}" mode="aspectFit" binderror="onIconError"/>
</view>
<view class="kefu-permanent-row kefu-permanent-row--mini" catchtap="onSessionDismiss">
<text class="kefu-permanent-label">隐藏</text>
</view>
</view>
</block>
</movable-view>
</movable-area>

View File

@@ -0,0 +1,186 @@
.kefu-movable-area {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 900;
}
.kefu-movable-view {
width: auto;
height: auto;
pointer-events: auto;
position: absolute;
right: 20rpx;
top: 0;
}
.kefu-wrap {
display: flex;
flex-direction: column;
align-items: flex-end;
pointer-events: auto;
}
.kefu-float {
position: relative;
}
.kefu-float--full {
display: flex;
align-items: stretch;
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
border-radius: 999rpx;
box-shadow: 0 8rpx 28rpx rgba(7, 193, 96, 0.45);
border: 2rpx solid rgba(255, 255, 255, 0.85);
overflow: visible;
animation: kefu-pulse 2.4s ease-in-out infinite;
}
@keyframes kefu-pulse {
0%, 100% { box-shadow: 0 8rpx 28rpx rgba(7, 193, 96, 0.45); }
50% { box-shadow: 0 8rpx 36rpx rgba(7, 193, 96, 0.65), 0 0 0 8rpx rgba(7, 193, 96, 0.12); }
}
.kefu-unread-badge {
position: absolute;
top: -10rpx;
right: -6rpx;
min-width: 36rpx;
height: 36rpx;
line-height: 36rpx;
padding: 0 8rpx;
background: #fa5151;
color: #fff;
font-size: 20rpx;
font-weight: 700;
text-align: center;
border-radius: 18rpx;
border: 3rpx solid #fff;
z-index: 2;
}
.kefu-unread-badge--mini {
top: -6rpx;
right: -4rpx;
}
.kefu-float-main {
display: flex;
align-items: center;
padding: 18rpx 24rpx;
flex: 1;
}
.kefu-float-main:active {
opacity: 0.88;
}
.kefu-float-icon-img {
width: 44rpx;
height: 44rpx;
margin-right: 12rpx;
flex-shrink: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.95);
padding: 4rpx;
}
.kefu-text-col {
display: flex;
flex-direction: column;
min-width: 0;
}
.kefu-float-text {
font-size: 28rpx;
color: #fff;
font-weight: 700;
white-space: nowrap;
}
.kefu-float-sub {
font-size: 20rpx;
color: rgba(255, 255, 255, 0.92);
margin-top: 2rpx;
font-weight: 600;
}
.kefu-float-sub--online {
color: #e8ffe8;
}
.kefu-float-sub--warn {
color: #fff3e0;
}
.kefu-float-close {
width: 52rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.12);
color: rgba(255, 255, 255, 0.9);
font-size: 34rpx;
line-height: 1;
border-left: 1rpx solid rgba(255, 255, 255, 0.25);
border-radius: 0 999rpx 999rpx 0;
}
.kefu-float-close:active {
background: rgba(0, 0, 0, 0.2);
}
.kefu-permanent-row {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 6rpx;
padding: 4rpx 10rpx;
}
.kefu-permanent-row:active {
opacity: 0.75;
}
.kefu-permanent-row--mini {
margin-top: 4rpx;
padding: 2rpx 8rpx;
}
.kefu-permanent-label {
font-size: 20rpx;
color: #888;
}
.kefu-wrap--mini {
align-items: flex-end;
}
.kefu-float--mini {
width: 96rpx;
height: 96rpx;
padding: 0;
border-radius: 50%;
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.45);
border: 3rpx solid #fff;
display: flex;
align-items: center;
justify-content: center;
animation: kefu-pulse 2.4s ease-in-out infinite;
}
.kefu-float--mini:active {
transform: scale(0.96);
}
.kefu-float-mini-icon-img {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.95);
padding: 4rpx;
}

View File

@@ -15,7 +15,7 @@
.card-id { font-size: 24rpx; color: #999; margin-left: 15rpx; }
.cross-tag {
margin-left: auto;
background: #8b5cf6;
background: #ff9900;
color: #fff;
font-size: 20rpx;
padding: 2rpx 12rpx;

View File

@@ -1,16 +1,87 @@
import request from '../../utils/request';
const app = getApp();
function mapIdentityForApi() {
const role = app.globalData.currentRole || wx.getStorageSync('currentRole') || 'normal';
if (role === 'dashou') return 'dashou';
if (role === 'shangjia') return 'shangjia';
return 'normal';
}
Component({
properties: {
visible: Boolean,
showDetail: { type: Boolean, value: false }
visible: { type: Boolean, value: false },
groupId: { type: String, value: '' },
orderId: { type: String, value: '' },
},
data: {},
data: {
keyword: '',
loading: false,
orders: [],
filteredOrders: [],
},
observers: {
visible(v) {
if (v) {
this.setData({ keyword: '' });
this.loadOrders('');
}
},
},
methods: {
close() {
this.triggerEvent('close');
},
onSearchInput(e) {
const keyword = (e.detail.value || '').trim();
this.setData({ keyword });
if (this._searchTimer) clearTimeout(this._searchTimer);
this._searchTimer = setTimeout(() => {
this.loadOrders(keyword);
}, 350);
},
async loadOrders(keyword) {
const { groupId, orderId } = this.properties;
if (!groupId && !orderId) return;
this.setData({ loading: true });
try {
const res = await request({
url: '/dingdan/ltpddd',
method: 'POST',
data: {
groupId,
dingdan_id: orderId,
keyword: keyword || '',
identityType: mapIdentityForApi(),
},
});
const body = res?.data || {};
const list = body.data?.list || [];
this.setData({
orders: list,
filteredOrders: list,
loading: false,
});
} catch (e) {
console.warn('加载历史订单失败', e);
this.setData({ loading: false, orders: [], filteredOrders: [] });
wx.showToast({ title: '加载订单失败', icon: 'none' });
}
}
},
onSelectOrder(e) {
const index = e.currentTarget.dataset.index;
const order = this.data.filteredOrders[index];
if (!order) return;
this.triggerEvent('send', { order });
this.close();
},
},
});

View File

@@ -1,12 +1,28 @@
<view class="order-sender-mask" wx:if="{{visible}}" catchtap="close"></view>
<view class="order-sender {{visible ? 'show' : ''}}">
<view class="header">
<text class="title">提示</text>
<text class="title">选择订单发送</text>
<text class="close" bindtap="close">✕</text>
</view>
<view class="content">
<view class="icon">📋</view>
<text class="notice">此功能暂未开放</text>
<text class="sub">敬请期待</text>
<view class="search-row">
<input class="search-input" placeholder="搜索订单号/内容" value="{{keyword}}" bindinput="onSearchInput" confirm-type="search" />
</view>
<scroll-view scroll-y class="order-list" enable-flex>
<view class="loading-tip" wx:if="{{loading}}">加载中...</view>
<block wx:for="{{filteredOrders}}" wx:key="dingdan_id">
<view class="order-item" data-index="{{index}}" bindtap="onSelectOrder">
<view class="order-item-top">
<text class="order-id">{{item.dingdan_id}}</text>
<text class="order-status">{{item.zhuangtaiText}}</text>
</view>
<text class="order-desc">{{item.jieshao || '暂无介绍'}}</text>
<view class="order-item-bottom">
<text class="order-price">¥{{item.jine || '0'}}</text>
<text class="order-time">{{item.create_time}}</text>
</view>
<text class="order-send-hint">点击发送订单卡片</text>
</view>
</block>
<view class="empty-tip" wx:if="{{!loading && !filteredOrders.length}}">暂无相关订单</view>
</scroll-view>
</view>

View File

@@ -1,45 +1,140 @@
.order-sender-mask {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 2000;
}
.order-sender {
position: fixed; bottom: 0; left: 0; right: 0;
height: 360rpx;
}
.order-sender {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 72vh;
max-height: 900rpx;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
z-index: 2001;
transform: translateY(100%);
transition: 0.25s;
transition: transform 0.25s;
display: flex;
flex-direction: column;
}
.order-sender.show { transform: translateY(0); }
}
.header {
display: flex; justify-content: space-between;
.order-sender.show {
transform: translateY(0);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.title { font-size: 32rpx; font-weight: 600; }
.close { font-size: 44rpx; color: #999; padding: 0 12rpx; }
flex-shrink: 0;
}
.content {
.title {
font-size: 32rpx;
font-weight: 600;
}
.close {
font-size: 44rpx;
color: #999;
padding: 0 12rpx;
}
.search-row {
padding: 16rpx 24rpx;
flex-shrink: 0;
}
.search-input {
background: #f5f5f5;
border-radius: 32rpx;
padding: 16rpx 28rpx;
font-size: 28rpx;
}
.order-list {
flex: 1;
height: 0;
padding: 0 24rpx 24rpx;
box-sizing: border-box;
}
.loading-tip,
.empty-tip {
text-align: center;
color: #999;
font-size: 28rpx;
padding: 60rpx 0;
}
.order-item {
background: #fafafa;
border-radius: 16rpx;
padding: 20rpx 24rpx;
margin-bottom: 16rpx;
border: 1rpx solid #eee;
}
.order-item:active {
background: #f0f7ff;
}
.order-item-top {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
justify-content: center;
padding-bottom: 40rpx;
}
.icon { font-size: 80rpx; margin-bottom: 20rpx; }
.notice {
font-size: 34rpx;
margin-bottom: 8rpx;
}
.order-id {
font-size: 26rpx;
font-weight: 600;
color: #333;
margin-bottom: 10rpx;
}
.sub {
font-size: 24rpx;
color: #999;
}
}
.order-status {
font-size: 22rpx;
color: #07c160;
background: #e8f8ee;
padding: 4rpx 14rpx;
border-radius: 20rpx;
}
.order-desc {
font-size: 26rpx;
color: #666;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 8rpx;
}
.order-item-bottom {
display: flex;
justify-content: space-between;
align-items: center;
}
.order-price {
font-size: 28rpx;
color: #ff6b00;
font-weight: 600;
}
.order-time {
font-size: 22rpx;
color: #aaa;
}
.order-send-hint {
display: block;
font-size: 22rpx;
color: #007aff;
margin-top: 10rpx;
}

View File

@@ -0,0 +1,15 @@
Component({
properties: {
visible: { type: Boolean, value: false },
title: { type: String, value: '频道' },
channelNo: { type: String, value: '' },
images: { type: Array, value: [] },
},
methods: {
preventMove() {},
onClose() {
this.triggerEvent('close');
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,24 @@
<view class="pindao-mask" wx:if="{{visible}}" catchtouchmove="preventMove">
<view class="pindao-panel" catchtap="preventMove">
<view class="pindao-head flexb">
<text class="pindao-title">{{title}}</text>
<view class="pindao-close" bindtap="onClose">×</view>
</view>
<view class="pindao-channel" wx:if="{{channelNo}}">
<text class="pindao-channel-label">频道号</text>
<text class="pindao-channel-no">{{channelNo}}</text>
</view>
<scroll-view class="pindao-scroll" scroll-y enhanced show-scrollbar="{{false}}">
<view wx:if="{{!images || images.length === 0}}" class="pindao-empty">暂无频道内容</view>
<image
wx:for="{{images}}"
wx:key="index"
class="pindao-img"
src="{{item}}"
mode="widthFix"
lazy-load="{{false}}"
show-menu-by-longpress
/>
</scroll-view>
</view>
</view>

View File

@@ -0,0 +1,88 @@
.pindao-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.55);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
box-sizing: border-box;
}
.pindao-panel {
width: 100%;
max-height: 80vh;
background: #fff;
border-radius: 24rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.pindao-head {
padding: 28rpx 32rpx 16rpx;
align-items: center;
}
.pindao-title {
font-size: 32rpx;
font-weight: 600;
color: #222;
}
.pindao-close {
width: 56rpx;
height: 56rpx;
line-height: 52rpx;
text-align: center;
font-size: 40rpx;
color: #999;
}
.pindao-channel {
padding: 0 32rpx 20rpx;
text-align: center;
}
.pindao-channel-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.pindao-channel-no {
font-size: 36rpx;
font-weight: 700;
color: #c9a962;
}
.pindao-scroll {
flex: 1;
max-height: 60vh;
padding: 0 24rpx 24rpx;
box-sizing: border-box;
}
.pindao-img {
width: 100%;
display: block;
margin-bottom: 16rpx;
border-radius: 12rpx;
}
.pindao-empty {
text-align: center;
color: #999;
padding: 48rpx 0;
font-size: 28rpx;
}
.flexb {
display: flex;
justify-content: space-between;
}

View File

@@ -138,7 +138,7 @@
.countdown-tip {
text-align: center;
font-size: 28rpx;
color: #9333ea;
color: #ff6600;
margin-bottom: 20rpx;
}
@@ -223,7 +223,7 @@
height: 120rpx;
border-radius: 50%;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
border: 2rpx solid rgba(196, 181, 253, 0.6);
border: 2rpx solid rgba(255, 215, 0, 0.6);
background: #f0f0f0;
display: block;
transform: translateZ(0);

11
config/club-config.js Normal file
View File

@@ -0,0 +1,11 @@
/**
* 小程序所属俱乐部配置 — 每个小程序工程编译前改这里即可。
* 星阙 = xq星之界 = xzj与 club 表 club_id 一致。
* 不要依赖后端猜测:前端必须明确所属 club。
*/
export const CLUB_ID = 'xzj';
/** 与 club 表 wx_appid 一致,登录时可传给后端反查 club */
export const WX_APP_ID = 'wxdefa454152e78a03';
export const CLUB_NAME = '星之界电竞';

View File

@@ -1,9 +1,16 @@
// pages/qiangdan/qiangdan.js
const app = getApp();
import request from '../../utils/request.js';
import { normalizeOrderTags } from '../../utils/order-tags.js';
import { refreshDashouMembership, userHasHuiyuan } from '../../utils/dashou-profile.js';
import { getDefaultAvatarUrl } from '../../utils/avatar.js';
import { warmupPindaoConfig } from '../../utils/miniapp-icons.js';
import PopupService from '../../services/popupService.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { ensurePhoneAuth } from '../../utils/phone-auth.js';
import { fetchGonggaoLunbo, isGonggaoCacheValid } from '../../utils/display-config.js';
import { checkPenaltyForGrab } from '../../utils/grab-order-gate.js';
import { fetchMyZhidingOrders, submitZhidingResponse, processZhidingList, filterZhidingBannerList, ZHIDING_HF_MAP } from '../../utils/zhiding-order.js';
Page({
data: {
@@ -30,7 +37,7 @@ Page({
// 4. 刷新控制字段
scrollViewRefreshing: false,
lastRefreshTime: 0,
refreshCooldown: 2000,
refreshCooldown: 0,
isLoadingMore: false,
// 5. 切换类型冷却
@@ -47,13 +54,93 @@ Page({
// 商品轮播展示(只读 globalData无额外接口
lunboList: [],
gonggao: '',
examRequired: false,
examPassed: false,
_examChecked: false,
myZhidingList: [],
highlightOrderId: '',
myZhidingCount: 0,
defaultAvatarUrl: '',
showZhidingDetail: false,
zhidingDetailOrder: null,
showLaterModal: false,
laterOrderId: '',
laterNote: '',
},
async onLoad(options) {
this.syncShopBannerFromGlobal();
const defAvatar = getDefaultAvatarUrl(app);
const highlight = (options && options.highlight) ? String(options.highlight) : '';
this.setData({ defaultAvatarUrl: defAvatar, highlightOrderId: highlight });
if (options && options.highlight) {
this._pendingHighlight = String(options.highlight);
}
if (app.globalData._acceptOrderSessionReady && app.globalData._acceptOrderPageCache) {
const cache = { ...app.globalData._acceptOrderPageCache };
this.data.shangpinleixing = cache.shangpinleixing || [];
const dingdanList = Array.isArray(cache.dingdanList)
? cache.dingdanList.map((item) => this.processDingdanItem(item))
: [];
this.setData({
...cache,
shangpinleixing: cache.shangpinleixing || [],
dingdanList,
bankuaiBiaoqian: cache.bankuaiBiaoqian || [],
lunboList: cache.lunboList || [],
});
if ((!cache.lunboList || cache.lunboList.length === 0) && !cache.gonggao && isGonggaoCacheValid(app)) {
const lunboList = this.processLunboUrls(app.globalData.shangpinlunbo);
const gonggao = app.globalData.shangpingonggao || '';
this.setData({ lunboList, gonggao });
this.persistPageCache({ lunboList, gonggao });
}
this.loadGlobalStatus();
await this.loadShangpinLeixing();
this.registerNotificationComponent();
await this.syncDashouProfileFromServer();
if (!this.data.shangpinleixing || this.data.shangpinleixing.length === 0) {
await this.loadShangpinLeixing(true, false, false);
} else if (this.data.xuanzhongLeixingId) {
await this.loadDingdanList(true, true);
}
this.persistPageCache();
this._skipShowRefresh = true;
await this.loadMyZhidingOrders(true);
return;
}
this.loadGlobalStatus();
const banner = await this.loadGonggaoAndLunbo(false);
await this.syncDashouProfileFromServer();
await this.loadShangpinLeixing(true, false, false);
await this.loadMyZhidingOrders(true);
this.persistPageCache(banner);
app.globalData._acceptOrderSessionReady = true;
if (!app.globalData._acceptOrderPopupDone) {
PopupService.checkAndShow(this, 'jiedan');
app.globalData._acceptOrderPopupDone = true;
}
this._skipShowRefresh = true;
},
persistPageCache(extra = {}) {
const d = this.data;
app.globalData._acceptOrderPageCache = {
shangpinleixing: d.shangpinleixing,
xuanzhongLeixingId: d.xuanzhongLeixingId,
dingdanList: d.dingdanList,
page: d.page,
hasMore: d.hasMore,
bankuaiBiaoqian: d.bankuaiBiaoqian,
xuanzhongBiaoqianId: d.xuanzhongBiaoqianId,
lunboList: extra.lunboList ?? d.lunboList,
gonggao: extra.gonggao ?? d.gonggao,
lastRefreshTime: d.lastRefreshTime,
ossImageUrl: d.ossImageUrl,
};
},
onHide: function () {
@@ -64,65 +151,94 @@ Page({
},
async onShow() {
if (!app.globalData._dashouPhoneChecked) {
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
if (!phoneOk) return;
app.globalData._dashouPhoneChecked = true;
}
this.syncShopBannerFromGlobal();
this.registerNotificationComponent();
this.loadGlobalStatus();
await this.syncDashouProfileFromServer();
warmupPindaoConfig(app);
if (wx.getStorageSync('uid')) {
// IM 延后连接,避免切 Tab 时卡顿;订单列表不自动刷新,由用户下拉刷新
setTimeout(() => {
reconnectForRole('dashou');
if (app.startImWhenReady) app.startImWhenReady();
}, 600);
}
await this.checkExamStatus(false);
this.setData({ defaultAvatarUrl: getDefaultAvatarUrl(app) });
await this.loadMyZhidingOrders(true);
const hl = app.globalData._acceptOrderHighlight;
if (hl) {
app.globalData._acceptOrderHighlight = '';
this.setData({ highlightOrderId: hl });
await this.loadMyZhidingOrders(true);
}
if (this._skipShowRefresh) {
this._skipShowRefresh = false;
return;
}
if (app.globalData._acceptOrderSessionReady && !this._silentRefreshRunning) {
this._silentRefreshRunning = true;
this.silentRefreshOnShow().finally(() => {
this._silentRefreshRunning = false;
});
}
},
/** 同步点单端已加载的商品轮播图,仅展示用 */
syncShopBannerFromGlobal() {
const oss = app.globalData.ossImageUrl || '';
const lunboList = (app.globalData.shangpinlunbo || []).map((url) => {
/** 进入页面静默刷新(无 loading 遮罩、不清空列表) */
async silentRefreshOnShow() {
try {
const banner = await this.loadGonggaoAndLunbo(true);
await this.syncDashouProfileFromServer();
const leixingId = this.data.xuanzhongLeixingId;
if (leixingId) {
await this.loadDingdanList(true, true);
await this.loadBankuaiBiaoqian();
}
await this.loadMyZhidingOrders(true);
this.loadGlobalStatus();
this.persistPageCache(banner);
} catch (e) {
console.error('静默刷新失败:', e);
}
},
/** 公告 + 轮播(首次/下拉强制拉接口,其余读 globalData 或页面缓存) */
processLunboUrls(urlList) {
const oss = app.globalData.ossImageUrl || this.data.ossImageUrl || '';
return (urlList || []).map((url) => {
if (!url) return '';
return url.startsWith('http') ? url : oss + url;
}).filter(Boolean);
this.setData({ lunboList });
},
async refreshDashouProfileSilent() {
async loadGonggaoAndLunbo(forceFetch = false) {
let lunboRaw = app.globalData.shangpinlunbo || [];
let gonggaoText = app.globalData.shangpingonggao || '';
if (forceFetch || !isGonggaoCacheValid(app)) {
try {
const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' });
if (!res || res.data.code != 200) return;
const data = res.data.data || {};
const patch = {
dashouNicheng: data.dashounicheng || '',
zhanghaoStatus: data.zhanghaostatus || '',
dashouzhuangtai: data.dashouzhuangtai || '',
yajin: data.yajin || 0,
jifen: data.jifen || 0,
clumber: data.clumber || [],
};
Object.assign(app.globalData, {
dashouNicheng: patch.dashouNicheng,
zhanghaoStatus: patch.zhanghaoStatus,
dashouzhuangtai: patch.dashouzhuangtai,
yajin: patch.yajin,
jinfen: patch.jifen,
clumber: patch.clumber,
});
if (data.dashoustatus !== undefined) {
wx.setStorageSync('dashoustatus', data.dashoustatus);
app.globalData.dashoustatus = data.dashoustatus;
const data = await fetchGonggaoLunbo(app, 'accept_order');
if (data) {
lunboRaw = data.shangpinlunbo || app.globalData.shangpinlunbo || [];
gonggaoText = data.shangpingonggao || app.globalData.shangpingonggao || '';
}
this.setData({
zhuanghaoStatus: patch.zhanghaoStatus,
dashouzhuangtai: patch.dashouzhuangtai,
huiyuanList: patch.clumber,
yajin: patch.yajin,
jifen: patch.jifen,
});
} catch (e) {}
} catch (e) {
lunboRaw = app.globalData.shangpinlunbo || [];
gonggaoText = app.globalData.shangpingonggao || '';
}
}
const lunboList = this.processLunboUrls(lunboRaw);
const gonggao = gonggaoText;
this.setData({ lunboList, gonggao });
return { lunboList, gonggao };
},
registerNotificationComponent() {
@@ -148,9 +264,22 @@ Page({
});
},
/** 拉取最新打手信息(会员/押金/积分),并刷新订单卡片上的会员展示 */
async syncDashouProfileFromServer() {
if (!wx.getStorageSync('token')) return;
await refreshDashouMembership(app);
this.loadGlobalStatus();
if (this.data.dingdanList && this.data.dingdanList.length) {
this.setData({
dingdanList: this.data.dingdanList.map((item) => this.processDingdanItem(item)),
});
}
},
// 加载商品类型
async loadShangpinLeixing() {
wx.showLoading({ title: '加载商品类型...' });
async loadShangpinLeixing(autoLoadOrders = false, preserveSelection = false, showLoading = true) {
if (showLoading) wx.showLoading({ title: '加载商品类型...' });
const prevId = this.data.xuanzhongLeixingId;
try {
const res = await request({
url: '/dingdan/dsqdhqddlx',
@@ -167,14 +296,16 @@ Page({
else if (Array.isArray(data)) list = data;
}
const processedList = this.processTupianUrl(list);
const firstId = processedList[0]?.id || null;
const keepPrev = preserveSelection && prevId && processedList.some((i) => i.id === prevId);
const selectedId = keepPrev ? prevId : firstId;
this.setData({
shangpinleixing: processedList,
xuanzhongLeixingId: processedList[0]?.id || null
xuanzhongLeixingId: selectedId,
});
if (this.data.xuanzhongLeixingId) {
this.loadDingdanList(true);
// 🆕 加载板块标签
this.loadBankuaiBiaoqian();
if (autoLoadOrders && selectedId) {
await this.loadDingdanList(true);
await this.loadBankuaiBiaoqian();
}
} else {
wx.showToast({ title: res.data?.msg || '加载商品类型失败', icon: 'none' });
@@ -182,7 +313,7 @@ Page({
} catch (error) {
wx.showToast({ title: '网络错误,加载失败', icon: 'none' });
} finally {
wx.hideLoading();
if (showLoading) wx.hideLoading();
}
},
@@ -249,8 +380,8 @@ Page({
xuanzhongBiaoqianId: 0 // 重置标签筛选
});
await this.loadDingdanList(true);
// 🆕 切换类型后加载新标签
await this.loadBankuaiBiaoqian();
this.persistPageCache();
} catch (error) {
console.error('切换类型失败:', error);
} finally {
@@ -270,19 +401,24 @@ Page({
hasMore: true
});
await this.loadDingdanList(true);
this.persistPageCache();
},
// 加载订单列表
async loadDingdanList(isRefresh = false) {
if (this.data.isLoading || !this.data.xuanzhongLeixingId) return;
async loadDingdanList(isRefresh = false, silent = false) {
if ((!silent && this.data.isLoading) || !this.data.xuanzhongLeixingId) return;
if (silent && this._listRequesting) return;
const loadPage = isRefresh ? 1 : this.data.page;
if (!isRefresh && !this.data.hasMore) return;
if (!silent) {
this.setData({
isLoading: true,
isLoadingMore: !isRefresh
isLoadingMore: !isRefresh,
});
}
this._listRequesting = true;
try {
const res = await request({
@@ -302,6 +438,7 @@ Page({
isLoadingMore: false,
scrollViewRefreshing: false
});
this._listRequesting = false;
if (res.data.code === 200 || res.data.code === 0) {
const newList = res.data.data.list || [];
@@ -322,6 +459,7 @@ Page({
page: loadPage,
hasMore: hasMore
});
this.persistPageCache();
if (isRefresh) {
this.setData({ lastRefreshTime: Date.now() });
@@ -336,25 +474,36 @@ Page({
isLoadingMore: false,
scrollViewRefreshing: false
});
this._listRequesting = false;
if (!silent) {
wx.showToast({ title: '网络请求失败', icon: 'none' });
}
}
},
// 处理单条订单数据(包含标签、会员判断)
processDingdanItem(item) {
const ossUrl = app.globalData.ossImageUrl || '';
const leixing = this.data.shangpinleixing.find((l) => l.id == item.leixing_id);
let fullTupianUrl = '';
if (item.tupian) {
fullTupianUrl = !item.tupian.startsWith('http') ? ossUrl + item.tupian : item.tupian;
// 卡片左上角优先用订单类型图,没有再退回默认图(不用商品图/头像顶替类型)
if (leixing && leixing.full_tupian_url) {
fullTupianUrl = leixing.full_tupian_url;
} else if (leixing && leixing.tupian_url) {
fullTupianUrl = leixing.tupian_url.startsWith('http')
? leixing.tupian_url
: ossUrl + leixing.tupian_url;
} else {
const leixing = this.data.shangpinleixing.find(l => l.id === item.leixing_id);
fullTupianUrl = leixing ? leixing.full_tupian_url : '/images/default-order.png';
fullTupianUrl = '/images/default-order.png';
}
const isZhiding = item.zhuangtai === 7;
const uid = this.data.uid;
const isMyZhiding = isZhiding && uid && String(item.zhiding_uid) === String(uid);
let zhidingAvatar = '';
const defAvatar = getDefaultAvatarUrl(app);
let zhidingAvatar = defAvatar;
if (item.zhiding_avatar) {
zhidingAvatar = !item.zhiding_avatar.startsWith('http')
? ossUrl + item.zhiding_avatar
@@ -362,31 +511,41 @@ Page({
}
const zhidingNicheng = item.zhiding_nicheng || '';
// 🆕 判断用户是否有订单所需的会员(优先后端 hasRequiredMember
// 订单要求指定会员时,核对用户是否持有该会员类型(优先后端字段
let hasRequiredMember = true;
if (item.hasRequiredMember !== undefined) {
hasRequiredMember = !!item.hasRequiredMember;
} else if (item.has_required_member !== undefined) {
hasRequiredMember = !!item.has_required_member;
} else if (item.yaoqiuleixing == 1 && item.huiyuan_id) {
const userHasIt = this.data.huiyuanList.some(h =>
(h.huiyuanid == item.huiyuan_id) || (h.huiyuan_id == item.huiyuan_id)
);
hasRequiredMember = userHasIt;
hasRequiredMember = userHasHuiyuan(this.data.huiyuanList, item.huiyuan_id);
}
let shangjiaAvatar = getDefaultAvatarUrl(app);
const sjAvatar = (item.sj_avatar || '').trim();
if (sjAvatar) {
shangjiaAvatar = sjAvatar.startsWith('http') ? sjAvatar : ossUrl + sjAvatar;
}
const tags = normalizeOrderTags(item)
return {
...item,
full_tupian_url: fullTupianUrl,
leixing_icon_url: fullTupianUrl,
shangjia_avatar_full: shangjiaAvatar,
isZhiding: isZhiding,
isMyZhiding,
isHighlighted: this.data.highlightOrderId && item.dingdan_id === this.data.highlightOrderId,
zhiding_hf: item.zhiding_hf,
zhiding_hf_text: item.zhiding_hf_text || ZHIDING_HF_MAP[item.zhiding_hf] || '',
zhiding_dhj_sm: item.zhiding_dhj_sm || '',
isPingtai: item.pingtai == 1,
isShangjia: item.pingtai == 2,
zhiding_avatar_full: zhidingAvatar,
zhiding_nicheng: zhidingNicheng,
// 🆕 三层标签(后端返回,直接保留)
xuqiu_biaoqian: item.xuqiu_biaoqian || [],
dashou_biaoqian: item.dashou_biaoqian || [],
shangjia_biaoqian: item.shangjia_biaoqian || [],
...tags,
shangjia_youzhi: !!item.shangjia_youzhi,
// 🆕 是否有查看价格的权限
hasRequiredMember: hasRequiredMember,
};
@@ -399,13 +558,16 @@ Page({
switch (failureType) {
case 'huiyuan':
params = { needScroll: '0', scrollTo: 'member' };
url = '/pages/fighter-recharge/fighter-recharge';
params = { scrollTo: 'member' };
break;
case 'yajin':
params = { needScroll: '1', scrollTo: 'bottom', requiredYajin: requiredYajin };
url = '/pages/fighter-recharge/fighter-deposit';
params = { requiredYajin: requiredYajin };
break;
case 'jifen':
params = { needScroll: '1', scrollTo: 'bottom' };
url = '/pages/fighter-recharge/fighter-recharge';
params = { scrollTo: 'jifen' };
break;
}
@@ -424,11 +586,63 @@ Page({
});
},
/** 抢单前考试状态(仅更新状态,不自动跳转考试页) */
async checkExamStatus() {
try {
const res = await request({
url: '/jituan/dashou-exam/status',
method: 'POST',
header: { 'content-type': 'application/json' },
});
const body = res?.data;
if (body && (body.code === 200 || body.code === 0) && body.data) {
const d = body.data;
this.setData({
examRequired: !!d.exam_required,
examPassed: !!d.exam_passed,
});
}
} catch (e) {
console.error('考试状态检查失败', e);
}
return true;
},
// 抢单按钮(原封不动)
async onQiangdanTap(e) {
const dingdanItem = e.currentTarget.dataset.item;
if (!dingdanItem) return;
const penalty = await checkPenaltyForGrab();
if (penalty.blocked) {
wx.showToast({
title: penalty.msg,
icon: 'none',
duration: 2000,
});
setTimeout(() => {
wx.navigateTo({ url: penalty.url });
}, 500);
return;
}
await this.checkExamStatus();
if (this.data.examRequired && !this.data.examPassed) {
wx.showModal({
title: '须通过接单考试',
content: '抢单前需先通过接单考试,是否现在去考试?',
confirmText: '去考试',
cancelText: '暂不',
success: (r) => {
if (r.confirm) {
wx.navigateTo({ url: '/pages/dashou-exam/dashou-exam' });
}
},
});
return;
}
// 校验1: 打手身份
if (!this.data.dashoustatus || this.data.dashoustatus != 1) {
wx.showToast({ title: '请先开启接单员身份', icon: 'none' });
@@ -453,9 +667,7 @@ Page({
}
// 校验5: 会员要求
if (dingdanItem.yaoqiuleixing == 1) {
const hasHuiyuan = this.data.huiyuanList.some(h =>
(h.huiyuanid == dingdanItem.huiyuan_id) || (h.huiyuan_id == dingdanItem.huiyuan_id)
);
const hasHuiyuan = userHasHuiyuan(this.data.huiyuanList, dingdanItem.huiyuan_id);
if (!hasHuiyuan) {
wx.showToast({
title: '未开通对应会员,无法抢单',
@@ -521,6 +733,8 @@ Page({
item => item.dingdan_id !== dingdanItem.dingdan_id
);
that.setData({ dingdanList: newList });
that.persistPageCache();
that.loadMyZhidingOrders(true);
} else {
wx.showToast({
title: qiangdanRes.data.msg || '抢单失败',
@@ -551,43 +765,134 @@ Page({
});
},
onReachBottom() {
const now = Date.now();
const lastTime = this.data.lastRefreshTime;
const cooldown = this.data.refreshCooldown;
async loadMyZhidingOrders(silent = false) {
if (!this.data.uid && !wx.getStorageSync('uid')) return;
try {
const raw = await fetchMyZhidingOrders();
const uid = this.data.uid || wx.getStorageSync('uid');
const highlight = this.data.highlightOrderId || this._pendingHighlight || '';
const list = filterZhidingBannerList(processZhidingList(raw, app, uid, highlight));
this.setData({ myZhidingList: list, myZhidingCount: list.length });
if (highlight && list.some((o) => o.dingdan_id === highlight)) {
this.setData({ highlightOrderId: highlight });
this._pendingHighlight = '';
}
} catch (e) {
if (!silent) console.warn('loadMyZhidingOrders', e);
}
},
if (now - lastTime < cooldown) {
wx.showToast({
title: `操作太快,请${Math.ceil((cooldown - (now - lastTime)) / 1000)}秒后再试`,
icon: 'none',
duration: 1500
openZhidingDetail(e) {
const item = e.currentTarget.dataset.item;
if (!item) return;
this.setData({ showZhidingDetail: true, zhidingDetailOrder: item });
},
closeZhidingDetail() {
this.setData({ showZhidingDetail: false, zhidingDetailOrder: null });
},
async onZhidingReject(e) {
const item = e.currentTarget.dataset.item;
if (!item) return;
wx.showModal({
title: '确认婉拒',
content: '婉拒后订单将退回抢单大厅,确定不想接此单吗?',
confirmText: '不想接',
confirmColor: '#e64340',
success: async (res) => {
if (!res.confirm) return;
wx.showLoading({ title: '提交中...', mask: true });
try {
const body = await submitZhidingResponse(item.dingdan_id, 2);
wx.hideLoading();
if (body && (body.code === 200 || body.code === 0)) {
wx.showToast({ title: body.msg || '已婉拒', icon: 'none' });
this.closeZhidingDetail();
await this.loadMyZhidingOrders(true);
await this.loadDingdanList(true, true);
} else {
wx.showToast({ title: body?.msg || '操作失败', icon: 'none' });
}
} catch (err) {
wx.hideLoading();
wx.showToast({ title: '网络错误', icon: 'none' });
}
},
});
},
onZhidingLater(e) {
const item = e.currentTarget.dataset.item;
if (!item) return;
this.setData({
showZhidingDetail: false,
zhidingDetailOrder: null,
showLaterModal: true,
laterOrderId: item.dingdan_id,
laterNote: item.zhiding_dhj_sm || '',
});
},
closeLaterModal() {
this.setData({ showLaterModal: false, laterOrderId: '', laterNote: '' });
},
onLaterNoteInput(e) {
this.setData({ laterNote: e.detail.value });
},
async confirmZhidingLater() {
const { laterOrderId, laterNote } = this.data;
if (!laterNote.trim()) {
wx.showToast({ title: '请填写预计接待时间', icon: 'none' });
return;
}
wx.showLoading({ title: '提交中...', mask: true });
try {
const body = await submitZhidingResponse(laterOrderId, 3, laterNote.trim());
wx.hideLoading();
if (body && (body.code === 200 || body.code === 0)) {
const note = laterNote.trim();
wx.showToast({ title: `已记录:${note}`, icon: 'none', duration: 3500 });
this.closeLaterModal();
this.closeZhidingDetail();
await this.loadMyZhidingOrders(true);
await this.loadDingdanList(true, true);
} else {
wx.showToast({ title: body?.msg || '操作失败', icon: 'none' });
}
} catch (e) {
wx.hideLoading();
wx.showToast({ title: '网络错误', icon: 'none' });
}
},
async onZhidingAccept(e) {
const item = e.currentTarget.dataset.item;
if (!item) return;
wx.showLoading({ title: '处理中...', mask: true });
try {
await submitZhidingResponse(item.dingdan_id, 1);
wx.hideLoading();
} catch (e) {
wx.hideLoading();
}
const processed = this.processDingdanItem(item);
this.onQiangdanTap({ currentTarget: { dataset: { item: processed } } });
},
noop() {},
onReachBottom() {
if (this.data.isLoading || this.data.isLoadingMore) return;
if (this.data.hasMore && !this.data.isLoading && this.data.xuanzhongLeixingId) {
this.setData({ page: this.data.page + 1 });
this.loadDingdanList(false);
}
},
onPullDownRefresh() {
const now = Date.now();
const lastTime = this.data.lastRefreshTime;
const cooldown = this.data.refreshCooldown;
if (now - lastTime < cooldown) {
this.setData({ scrollViewRefreshing: false });
wx.showToast({
title: `操作太快,请${Math.ceil((cooldown - (now - lastTime)) / 1000)}秒后再试`,
icon: 'none',
duration: 1500
});
return;
}
async onPullDownRefresh() {
if (this.data.isLoading) {
this.setData({ scrollViewRefreshing: false });
return;
@@ -595,15 +900,26 @@ Page({
this.setData({
scrollViewRefreshing: true,
lastRefreshTime: now,
lastRefreshTime: Date.now(),
page: 1,
hasMore: true
hasMore: true,
});
if (this.data.xuanzhongLeixingId) {
this.refreshDashouProfileSilent();
this.loadDingdanList(true);
} else {
try {
const banner = await this.loadGonggaoAndLunbo(true);
await this.syncDashouProfileFromServer();
await this.loadShangpinLeixing(false, true, false);
const leixingId = this.data.xuanzhongLeixingId;
if (leixingId) {
await this.loadBankuaiBiaoqian();
await this.loadDingdanList(true);
}
await this.loadMyZhidingOrders(true);
this.loadGlobalStatus();
this.persistPageCache(banner);
} catch (e) {
console.error('下拉刷新失败:', e);
} finally {
this.setData({ scrollViewRefreshing: false });
}
}

View File

@@ -1,12 +1,14 @@
{
"navigationBarTitleText": "抢单大厅",
"navigationBarBackgroundColor": "#c4b5fd",
"navigationBarTitleText": "抢单列表",
"navigationBarBackgroundColor": "#f7dc51",
"backgroundColorTop": "#f7dc51",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": false,
"backgroundTextStyle": "dark",
"backgroundColor": "#f5f3ff",
"backgroundColor": "#fff8e1",
"usingComponents": {
"chenghao-tag": "/components/chenghao-tag/chenghao-tag",
"global-notification": "/components/global-notification/global-notification"
"global-notification": "/components/global-notification/global-notification",
"kefu-float": "/components/kefu-float/kefu-float"
}
}

View File

@@ -18,7 +18,7 @@
refresher-default-style="black"
refresher-background="#f5f3ff"
refresher-background="#fff8e1"
refresher-triggered="{{scrollViewRefreshing}}"
@@ -38,6 +38,38 @@
<!-- 公告置顶 -->
<view class="xym-gonggao-bar" wx:if="{{gonggao}}">
<image class="xym-gonggao-ico" src="https://bintao.xmxym88.com/xcx/bintao/38.png" mode="aspectFit"/>
<text class="xym-gonggao-txt">{{gonggao}}</text>
</view>
<!-- 被指定订单提醒 -->
<view class="my-zhiding-banner" wx:if="{{myZhidingList.length > 0}}">
<view class="my-zhiding-banner-head">
<view class="my-zhiding-head-left">
<text class="my-zhiding-badge">指定给您</text>
<text class="my-zhiding-count-num">{{myZhidingCount || myZhidingList.length}}单</text>
</view>
<text class="my-zhiding-sub">请确认是否接待</text>
</view>
<view wx:for="{{myZhidingList}}" wx:key="dingdan_id" class="my-zhiding-card {{item.isHighlighted ? 'my-zhiding-card--highlight' : ''}}">
<view class="my-zhiding-card-main" data-item="{{item}}" bindtap="openZhidingDetail">
<view class="my-zhiding-card-top">
<text class="my-zhiding-order-id">单号 {{item.dingdan_id}}</text>
<text class="my-zhiding-reward">¥{{item.dashou_fencheng || 0}}</text>
</view>
<text class="my-zhiding-desc line2">{{item.jieshao || '暂无介绍'}}</text>
<text class="my-zhiding-status" wx:if="{{item.zhiding_hf_text}}">您的回复:{{item.zhiding_hf_text}}{{item.zhiding_dhj_sm ? ' · ' + item.zhiding_dhj_sm : ''}}</text>
</view>
<view class="my-zhiding-actions">
<view class="zhiding-act zhiding-act--ghost" data-item="{{item}}" catchtap="onZhidingReject">不想接</view>
<view class="zhiding-act zhiding-act--ghost" data-item="{{item}}" catchtap="onZhidingLater">等会接</view>
<view class="zhiding-act zhiding-act--primary" data-item="{{item}}" catchtap="onZhidingAccept">立即接待</view>
</view>
</view>
</view>
<!-- 商品轮播(只读 globalData与点单端同源 -->
<view class="xym-lunbo-container" wx:if="{{lunboList.length > 0}}">
@@ -50,7 +82,7 @@
indicator-color="rgba(255,255,255,0.6)"
indicator-active-color="#9333ea"
indicator-active-color="#ffd061"
autoplay="{{lunboList.length > 1}}"
@@ -74,11 +106,11 @@
</view>
<view class="xym-section-gap" wx:if="{{lunboList.length > 0}}"></view>
<!-- 商品类型选择 -->
<view class="leixing-quyu">
<view class="leixing-quyu dingdanxiang1 filter-card mx20">
<scroll-view class="leixing-scroll" scroll-x enhanced show-scrollbar="{{false}}">
@@ -176,7 +208,7 @@
<!-- 平台订单(逍遥梦卡片布局) -->
<view wx:if="{{item.isPingtai}}" class="xym-order-item xym-pingtai-order" data-item="{{item}}">
<view wx:if="{{item.isPingtai}}" class="xym-order-item xym-pingtai-order {{item.isMyZhiding ? 'order-my-zhiding' : ''}} {{item.isHighlighted ? 'order-zhiding-highlight' : ''}}" data-item="{{item}}">
<view class="xym-pingtai-head flexb">
@@ -190,16 +222,6 @@
<view class="order-con">
<view wx:if="{{item.isZhiding}}" class="zhiding-strip myflex light">
<text class="zhiding-label">指定</text>
<image wx:if="{{item.zhiding_avatar_full}}" class="zhiding-avatar" src="{{item.zhiding_avatar_full}}" mode="aspectFill"/>
<text class="zhiding-name">{{item.zhiding_nicheng || '指定接单员'}}</text>
</view>
<view class="order-row myflex">
<image class="o-logo" src="{{item.full_tupian_url}}" mode="aspectFill"/>
@@ -212,6 +234,20 @@
</view>
<block wx:if="{{item.isZhiding}}">
<view class="zhiding-strip light">
<view class="zhiding-strip-top myflex">
<text class="zhiding-label">指定</text>
<image class="zhiding-avatar" src="{{item.zhiding_avatar_full || defaultAvatarUrl}}" mode="aspectFill"/>
<text class="zhiding-name">{{item.zhiding_nicheng || '指定接单员'}}</text>
</view>
<view wx:if="{{item.dashou_biaoqian.length > 0 || item.zhiding_identity_biaoqian.length > 0}}" class="zhiding-strip-tags">
<chenghao-tag wx:for="{{item.dashou_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
<chenghao-tag wx:for="{{item.zhiding_identity_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</view>
</block>
<view class="reward-badge platform-reward" wx:if="{{item.hasRequiredMember}}">
<text class="reward-yen sm">¥</text>
@@ -226,9 +262,9 @@
</view>
<view wx:if="{{item.beizhu}}" class="remark-block gold" data-type="beizhu" data-content="{{item.beizhu}}" bindtap="onViewDetail">
<view class="remark-block gold" data-type="beizhu" data-content="{{item.beizhu}}" bindtap="onViewDetail">
<text class="remark-label">备注:</text>{{item.beizhu}}
<text class="remark-label">备注:</text>{{item.beizhu || '暂无'}}
</view>
@@ -236,10 +272,10 @@
<text class="xuqiu-label">需求标签</text>
<scroll-view class="biaoqian-inline-scroll" scroll-x>
<scroll-view class="biaoqian-inline-scroll" scroll-x enable-flex>
<view class="biaoqian-inline-inner">
<chenghao-tag wx:for="{{item.xuqiu_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</scroll-view>
</view>
@@ -248,7 +284,7 @@
<text class="o-amt light">平台派单</text>
<view class="qiangdan flexa btn-bg btn-qiang btn-qiang-brown" data-item="{{item}}" bindtap="onQiangdanTap">抢单</view>
<view class="qiangdan flexa btn-bg btn-qiang btn-qiang-brown" data-item="{{item}}" bindtap="onQiangdanTap">{{item.isMyZhiding ? '立即接待' : '抢单'}}</view>
</view>
@@ -260,9 +296,84 @@
<!-- 优质商家订单(金牌卡片) -->
<view wx:elif="{{item.isShangjia && item.shangjia_youzhi}}" class="xym-order-item gold-card {{item.isMyZhiding ? 'order-my-zhiding' : ''}} {{item.isHighlighted ? 'order-zhiding-highlight' : ''}}" data-item="{{item}}">
<view class="gold-head flexb">
<view class="kehuduan-banner-wrap">
<image class="gold-banner" src="https://bintao.xmxym88.com/xcx/bintao/82.png" mode="widthFix"/>
</view>
</view>
<view class="gold-body-wrap">
<view class="order-con gold-inner">
<view class="order-row myflex">
<image class="o-logo" src="{{item.leixing_icon_url}}" mode="aspectFill"/>
<view class="o-col">
<view class="o-goods line2" data-type="jieshao" data-content="{{item.jieshao}}" bindtap="onViewDetail">{{item.jieshao || '暂无介绍'}}</view>
<view class="tag-row myflex">
<text class="tag-pill tag-youzhi">优质商家</text>
</view>
</view>
</view>
<block wx:if="{{item.isZhiding}}">
<view class="zhiding-strip">
<view class="zhiding-strip-top myflex">
<text class="zhiding-label">指定</text>
<image class="zhiding-avatar" src="{{item.zhiding_avatar_full || defaultAvatarUrl}}" mode="aspectFill"/>
<text class="zhiding-name">{{item.zhiding_nicheng || '指定接单员'}}</text>
</view>
<view wx:if="{{item.dashou_biaoqian.length > 0 || item.zhiding_identity_biaoqian.length > 0}}" class="zhiding-strip-tags">
<chenghao-tag wx:for="{{item.dashou_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
<chenghao-tag wx:for="{{item.zhiding_identity_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</view>
</block>
<view class="reward-badge normal-reward gold-reward" wx:if="{{item.hasRequiredMember}}">
<text class="reward-yen sm">¥</text>
<text class="reward-num dark">{{item.dashou_fencheng || 0}}</text>
</view>
<view class="reward-badge member-tip" wx:else>
<text class="reward-tip">未开通会员</text>
</view>
<view class="remark-block dark" data-type="beizhu" data-content="{{item.beizhu}}" bindtap="onViewDetail">
<text class="remark-label dark-t">商家备注:</text>{{item.beizhu || '暂无'}}
</view>
<view wx:if="{{item.shangjia_biaoqian.length > 0 || item.shangjia_identity_biaoqian.length > 0 || item.xuqiu_biaoqian.length > 0}}" class="xuqiu-tags-row">
<scroll-view wx:if="{{item.shangjia_biaoqian.length > 0}}" class="biaoqian-inline-scroll" scroll-x enable-flex>
<view class="biaoqian-inline-inner">
<chenghao-tag wx:for="{{item.shangjia_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</scroll-view>
<scroll-view wx:if="{{item.shangjia_identity_biaoqian.length > 0}}" class="biaoqian-inline-scroll" scroll-x enable-flex style="margin-top:8rpx">
<view class="biaoqian-inline-inner">
<chenghao-tag wx:for="{{item.shangjia_identity_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</scroll-view>
<scroll-view wx:if="{{item.xuqiu_biaoqian.length > 0}}" class="biaoqian-inline-scroll" scroll-x enable-flex style="margin-top:8rpx">
<view class="biaoqian-inline-inner">
<chenghao-tag wx:for="{{item.xuqiu_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</scroll-view>
</view>
<view class="merchant-plain flexb">
<view class="merchant-user myflex">
<image class="m-avatar" src="{{item.shangjia_avatar_full}}" mode="aspectFill"/>
<view class="m-info">
<view class="m-name m-name-light line1">{{item.sjnicheng || '未知商家'}}</view>
<view class="m-sn m-sn-light">发布于 {{item.creat_time}}</view>
</view>
</view>
<view class="qiangdan flexa btn-bg btn-qiang" data-item="{{item}}" bindtap="onQiangdanTap">{{item.isMyZhiding ? '立即接待' : '抢单'}}</view>
</view>
<view class="o-foot flexb kehuduan-foot gold-foot-text-only">
<text class="o-amt-grey">商家派单</text>
</view>
</view>
</view>
</view>
<!-- 商家订单(逍遥梦卡片布局) -->
<view wx:elif="{{item.isShangjia}}" class="xym-order-item xym-shangjia-order" data-item="{{item}}">
<view wx:elif="{{item.isShangjia}}" class="xym-order-item xym-shangjia-order {{item.isMyZhiding ? 'order-my-zhiding' : ''}} {{item.isHighlighted ? 'order-zhiding-highlight' : ''}}" data-item="{{item}}">
<view class="xym-shangjia-head flexb">
@@ -276,19 +387,23 @@
<view class="order-con">
<view wx:if="{{item.isZhiding}}" class="zhiding-strip myflex">
<block wx:if="{{item.isZhiding}}">
<view class="zhiding-strip">
<view class="zhiding-strip-top myflex">
<text class="zhiding-label">指定</text>
<image wx:if="{{item.zhiding_avatar_full}}" class="zhiding-avatar" src="{{item.zhiding_avatar_full}}" mode="aspectFill"/>
<image class="zhiding-avatar" src="{{item.zhiding_avatar_full || defaultAvatarUrl}}" mode="aspectFill"/>
<text class="zhiding-name">{{item.zhiding_nicheng || '指定接单员'}}</text>
</view>
<view wx:if="{{item.dashou_biaoqian.length > 0 || item.zhiding_identity_biaoqian.length > 0}}" class="zhiding-strip-tags">
<chenghao-tag wx:for="{{item.dashou_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
<chenghao-tag wx:for="{{item.zhiding_identity_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</view>
</block>
<view class="merchant-line myflex">
<image class="m-avatar-sm" src="{{item.full_tupian_url}}" mode="aspectFill"/>
<image class="m-avatar-sm" src="{{item.shangjia_avatar_full}}" mode="aspectFill"/>
<text class="m-name-sm">{{item.sjnicheng || '未知商家'}}</text>
@@ -318,24 +433,33 @@
</view>
<view wx:if="{{item.beizhu}}" class="remark-block dark" data-type="beizhu" data-content="{{item.beizhu}}" bindtap="onViewDetail">
<view class="remark-block dark" data-type="beizhu" data-content="{{item.beizhu}}" bindtap="onViewDetail">
<text class="remark-label">商家备注:</text>{{item.beizhu}}
<text class="remark-label">商家备注:</text>{{item.beizhu || '暂无'}}
</view>
<view wx:if="{{item.shangjia_biaoqian.length > 0 || item.xuqiu_biaoqian.length > 0}}" class="xuqiu-tags-row">
<view wx:if="{{item.shangjia_biaoqian.length > 0 || item.shangjia_identity_biaoqian.length > 0 || item.xuqiu_biaoqian.length > 0}}" class="xuqiu-tags-row">
<scroll-view wx:if="{{item.shangjia_biaoqian.length > 0}}" class="biaoqian-inline-scroll" scroll-x>
<scroll-view wx:if="{{item.shangjia_biaoqian.length > 0}}" class="biaoqian-inline-scroll" scroll-x enable-flex>
<view class="biaoqian-inline-inner">
<chenghao-tag wx:for="{{item.shangjia_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</scroll-view>
<scroll-view wx:if="{{item.xuqiu_biaoqian.length > 0}}" class="biaoqian-inline-scroll" scroll-x style="margin-top:8rpx">
<scroll-view wx:if="{{item.shangjia_identity_biaoqian.length > 0}}" class="biaoqian-inline-scroll" scroll-x enable-flex style="margin-top:8rpx">
<view class="biaoqian-inline-inner">
<chenghao-tag wx:for="{{item.shangjia_identity_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</scroll-view>
<scroll-view wx:if="{{item.xuqiu_biaoqian.length > 0}}" class="biaoqian-inline-scroll" scroll-x enable-flex style="margin-top:8rpx">
<view class="biaoqian-inline-inner">
<chenghao-tag wx:for="{{item.xuqiu_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
</view>
</scroll-view>
</view>
@@ -344,7 +468,7 @@
<text class="o-amt">商家派单</text>
<view class="qiangdan flexa btn-bg btn-qiang" data-item="{{item}}" bindtap="onQiangdanTap">抢单</view>
<view class="qiangdan flexa btn-bg btn-qiang" data-item="{{item}}" bindtap="onQiangdanTap">{{item.isMyZhiding ? '立即接待' : '抢单'}}</view>
</view>
@@ -384,8 +508,58 @@
<global-notification id="global-notification" />
<kefu-float bottom="200rpx" />
<popup-notice id="popupNotice" />
<!-- 指定单详情 -->
<view class="zhiding-modal-mask" wx:if="{{showZhidingDetail}}" catchtap="closeZhidingDetail">
<view class="zhiding-modal" catchtap="noop">
<view class="zhiding-modal-head">
<text class="zhiding-modal-title">指定订单详情</text>
<text class="zhiding-modal-close" catchtap="closeZhidingDetail">×</text>
</view>
<view class="zhiding-modal-body" wx:if="{{zhidingDetailOrder}}">
<view class="zhiding-modal-row"><text class="lbl">单号</text><text>{{zhidingDetailOrder.dingdan_id}}</text></view>
<view class="zhiding-modal-row"><text class="lbl">分成</text><text class="reward">¥{{zhidingDetailOrder.dashou_fencheng || 0}}</text></view>
<view class="zhiding-modal-row col"><text class="lbl">介绍</text><text>{{zhidingDetailOrder.jieshao || '暂无'}}</text></view>
<view class="zhiding-modal-row col" wx:if="{{zhidingDetailOrder.beizhu}}"><text class="lbl">备注</text><text>{{zhidingDetailOrder.beizhu}}</text></view>
<view class="zhiding-modal-row" wx:if="{{zhidingDetailOrder.zhiding_hf_text}}"><text class="lbl">您的回复</text><text>{{zhidingDetailOrder.zhiding_hf_text}}</text></view>
</view>
<view class="my-zhiding-actions zhiding-modal-actions" wx:if="{{zhidingDetailOrder}}">
<view class="zhiding-act zhiding-act--ghost" data-item="{{zhidingDetailOrder}}" catchtap="onZhidingReject">不想接</view>
<view class="zhiding-act zhiding-act--ghost" data-item="{{zhidingDetailOrder}}" catchtap="onZhidingLater">等会接</view>
<view class="zhiding-act zhiding-act--primary" data-item="{{zhidingDetailOrder}}" catchtap="onZhidingAccept">立即接待</view>
</view>
</view>
</view>
<!-- 等会接时间 -->
<view class="zhiding-modal-mask zhiding-modal-mask--top" wx:if="{{showLaterModal}}" catchtap="closeLaterModal">
<view class="zhiding-modal zhiding-modal--sm" catchtap="noop">
<view class="zhiding-modal-head">
<text class="zhiding-modal-title">等会接</text>
<text class="zhiding-modal-close" catchtap="closeLaterModal">×</text>
</view>
<view class="zhiding-modal-body">
<text class="later-hint">请填写预计接待时间例如「30分钟后」「今晚8点」</text>
<input
class="later-input"
placeholder="预计接待时间"
placeholder-class="later-input-ph"
value="{{laterNote}}"
bindinput="onLaterNoteInput"
maxlength="50"
focus="{{showLaterModal}}"
adjust-position="{{true}}"
confirm-type="done"
bindconfirm="confirmZhidingLater"
/>
</view>
<view class="later-confirm" catchtap="confirmZhidingLater">确认</view>
</view>
</view>
<tab-bar />
</view>

View File

@@ -1,5 +1,11 @@
/* pages/qiangdan/qiangdan.wxss - 高级机甲风格重构版(对称优化) */
/* pages/qiangdan/qiangdan.wxss - 逍遥梦抢单页 */
@import '../../styles/dashou-xym-theme.wxss';
@import '../../styles/dashou-xym-order-card.wxss';
page {
background-color: #f7dc51;
}
.qiangdan-page {
height: 100vh;
display: flex;
@@ -126,6 +132,7 @@
flex: 1;
height: 0;
-webkit-overflow-scrolling: touch;
background: transparent;
}
.scroll-bottom-spacer {
@@ -486,7 +493,7 @@
background: rgba(0, 0, 0, 0.3);
padding: 8rpx 20rpx;
border-radius: 60rpx;
border: 1rpx solid rgba(196, 181, 253, 0.3);
border: 1rpx solid rgba(255, 215, 0, 0.3);
}
/* 分佣图标占位符,用户可替换为自己图标 */
@@ -504,10 +511,10 @@
.fenyong-price {
font-size: 44rpx;
color: #c4b5fd;
color: #ffd700;
font-weight: 800;
margin-right: 4rpx;
text-shadow: 0 0 20px rgba(196, 181, 253, 0.8);
text-shadow: 0 0 20px rgba(255, 215, 0, 0.8);
}
.fenyong-unit {
@@ -764,4 +771,179 @@
max-width: 50%;
}
@import '../../styles/dashou-xym-order-card.wxss';
/* 被指定订单(星阙功能保留,不影响星之界卡片 UI */
.my-zhiding-banner {
margin: 16rpx 24rpx 0;
padding: 22rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, #ff9500 0%, #ff6b00 100%);
border: 3rpx solid #ff4500;
box-shadow: 0 12rpx 32rpx rgba(255, 69, 0, 0.45);
position: relative;
z-index: 20;
}
.my-zhiding-banner-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.my-zhiding-head-left {
display: flex;
align-items: center;
gap: 12rpx;
}
.my-zhiding-badge {
font-size: 26rpx;
font-weight: 800;
color: #ff4500;
background: #fff;
padding: 8rpx 18rpx;
border-radius: 999rpx;
}
.my-zhiding-count-num {
font-size: 32rpx;
font-weight: 800;
color: #fff;
}
.my-zhiding-sub {
font-size: 26rpx;
color: #fff;
font-weight: 700;
}
.my-zhiding-card {
background: #fff;
border-radius: 16rpx;
padding: 18rpx;
margin-bottom: 12rpx;
border: 2rpx solid #ffe0b2;
}
.my-zhiding-card--highlight {
border-color: #ff8c00;
box-shadow: 0 0 0 4rpx rgba(255, 140, 0, 0.15);
}
.my-zhiding-card-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.my-zhiding-order-id {
font-size: 24rpx;
color: #666;
}
.my-zhiding-reward {
font-size: 30rpx;
font-weight: 700;
color: #e64340;
}
.my-zhiding-desc {
font-size: 28rpx;
color: #333;
line-height: 1.5;
}
.my-zhiding-status {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
color: #059669;
}
.my-zhiding-actions {
display: flex;
gap: 12rpx;
margin-top: 16rpx;
}
.zhiding-act {
flex: 1;
text-align: center;
padding: 14rpx 0;
border-radius: 999rpx;
font-size: 26rpx;
font-weight: 600;
}
.zhiding-act--ghost {
background: #f3f4f6;
color: #666;
}
.zhiding-act--primary {
background: linear-gradient(90deg, #ff8c00, #ff6b00);
color: #fff;
}
.order-my-zhiding {
border: 3rpx solid #ff4500 !important;
box-shadow: 0 0 0 6rpx rgba(255, 69, 0, 0.25), 0 12rpx 36rpx rgba(255, 69, 0, 0.35) !important;
}
.order-zhiding-highlight {
animation: zhidingPulse 1.5s ease-in-out 2;
}
@keyframes zhidingPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(255, 140, 0, 0.3); }
50% { box-shadow: 0 0 0 12rpx rgba(255, 140, 0, 0); }
}
.zhiding-modal-mask {
position: fixed;
left: 0; right: 0; top: 0; bottom: 0;
background: rgba(0, 0, 0, 0.62);
z-index: 10050;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
}
.zhiding-modal-mask--top {
z-index: 10060;
}
.zhiding-modal {
width: 100%;
max-width: 640rpx;
background: #ffffff;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 24rpx 64rpx rgba(0, 0, 0, 0.35);
}
.zhiding-modal--sm { max-width: 580rpx; }
.zhiding-modal-head {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 28rpx;
border-bottom: 1rpx solid #eee;
background: #fff;
}
.zhiding-modal-title { font-size: 32rpx; font-weight: 700; color: #222; }
.zhiding-modal-close { font-size: 44rpx; color: #666; line-height: 1; padding: 0 8rpx; }
.zhiding-modal-body { padding: 24rpx 28rpx; background: #fff; }
.zhiding-modal-row {
display: flex;
justify-content: space-between;
margin-bottom: 16rpx;
font-size: 28rpx;
color: #333;
}
.zhiding-modal-row.col { flex-direction: column; gap: 8rpx; }
.zhiding-modal-row .lbl { color: #999; font-size: 24rpx; }
.zhiding-modal-row .reward { color: #e64340; font-weight: 700; }
.zhiding-modal-actions { padding: 0 28rpx 28rpx; margin-top: 0; background: #fff; }
.later-hint { font-size: 26rpx; color: #444; display: block; margin-bottom: 16rpx; }
.later-input {
width: 100%;
box-sizing: border-box;
border: 2rpx solid #ddd;
border-radius: 12rpx;
padding: 22rpx 20rpx;
font-size: 28rpx;
color: #222;
background: #f9fafb;
min-height: 88rpx;
}
.later-input-ph { color: #aaa; }
.later-confirm {
margin: 0 28rpx 28rpx;
text-align: center;
padding: 24rpx;
background: linear-gradient(90deg, #ff8c00, #ff4500);
color: #fff;
border-radius: 999rpx;
font-size: 30rpx;
font-weight: 700;
}

View File

@@ -132,7 +132,7 @@ page {
border-radius: 20rpx;
font-weight: 500;
}
.status-0 { background: #F3E8FF; color: #9333ea; }
.status-0 { background: #FFF3E0; color: #EF6C00; }
.status-1 { background: #E8F5E9; color: #2E7D32; }
.status-2 { background: #FFEBEE; color: #C62828; }
.status-3 { background: #E3F2FD; color: #1565C0; }

View File

@@ -1,6 +1,7 @@
// pages/category/category.js
const app = getApp()
import { createPage } from '../../utils/base-page.js';
import { buildClubHeaders } from '../../utils/club-context.js';
Page(createPage({
data: {
@@ -114,9 +115,7 @@ Page(createPage({
wx.request({
url: app.globalData.apiBaseUrl + '/shangpin/shangpinhuoqu/',
method: 'POST',
header: {
'content-type': 'application/json'
},
header: buildClubHeaders({ 'content-type': 'application/json' }),
success(res) {
if (res.statusCode === 200 && res.data) {
const data = res.data

View File

@@ -60,13 +60,13 @@
.search-btn {
margin-left: 20rpx;
box-shadow: 0 6rpx 20rpx rgba(147, 51, 234, 0.3);
box-shadow: 0 6rpx 20rpx rgba(255, 170, 0, 0.3);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.search-btn:active {
transform: scale(0.95);
box-shadow: 0 4rpx 15rpx rgba(147, 51, 234, 0.4);
box-shadow: 0 4rpx 15rpx rgba(255, 170, 0, 0.4);
}
.search-btn-text {
@@ -127,7 +127,7 @@
transform: translate(-50%, -50%);
width: 300rpx;
height: 300rpx;
background: radial-gradient(circle, rgba(168, 85, 247, 0.1) 0%, transparent 70%);
background: radial-gradient(circle, rgba(255, 204, 0, 0.1) 0%, transparent 70%);
z-index: -1;
}
@@ -167,8 +167,8 @@
.leixing-active .leixing-image-box {
transform: scale(1.12);
box-shadow:
0 18rpx 40rpx rgba(168, 85, 247, 0.3),
0 8rpx 16rpx rgba(147, 51, 234, 0.2);
0 18rpx 40rpx rgba(255, 204, 0, 0.3),
0 8rpx 16rpx rgba(255, 170, 0, 0.2);
}
.leixing-image {
@@ -185,7 +185,7 @@
right: -3rpx;
bottom: -3rpx;
border-radius: 50%;
background: linear-gradient(135deg, #c4b5fd, #9333ea, #7c3aed);
background: linear-gradient(135deg, #ffcc00, #ffaa00, #ff8800);
opacity: 0;
transition: opacity 0.4s;
z-index: -1;
@@ -223,10 +223,10 @@
}
.leixing-active .leixing-text {
color: #8b5cf6;
color: #ff9900;
font-weight: 600;
transform: translateY(-2rpx);
text-shadow: 0 2rpx 4rpx rgba(168, 85, 247, 0.2);
text-shadow: 0 2rpx 4rpx rgba(255, 153, 0, 0.2);
}
.leixing-active-bar {
@@ -234,12 +234,12 @@
bottom: -4rpx;
width: 44rpx;
height: 4rpx;
background: linear-gradient(90deg, #c4b5fd, #9333ea);
background: linear-gradient(90deg, #ffcc00, #ffaa00);
border-radius: 2rpx;
opacity: 0;
transform: scaleX(0);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 2rpx 8rpx rgba(147, 51, 234, 0.4);
box-shadow: 0 2rpx 8rpx rgba(255, 170, 0, 0.4);
}
.leixing-active-bar-show {
@@ -261,7 +261,7 @@
.border-effect-active {
opacity: 1;
border-color: rgba(168, 85, 247, 0.5);
border-color: rgba(255, 204, 0, 0.5);
animation: borderGlow 2s infinite;
}
@@ -295,7 +295,7 @@
}
.zhuanqu-item:active {
background: rgba(168, 85, 247, 0.05);
background: rgba(255, 204, 0, 0.05);
}
.zhuanqu-item-content {
@@ -320,9 +320,9 @@
}
.zhuanqu-active .zhuanqu-name {
color: #8b5cf6;
color: #ff9900;
font-weight: 600;
text-shadow: 0 2rpx 4rpx rgba(168, 85, 247, 0.2);
text-shadow: 0 2rpx 4rpx rgba(255, 153, 0, 0.2);
}
.zhuanqu-count {
@@ -332,7 +332,7 @@
}
.zhuanqu-active .zhuanqu-count {
color: #9333ea;
color: #ffaa00;
font-weight: 500;
}
@@ -343,7 +343,7 @@
transform: translateY(-50%);
width: 6rpx;
height: 44rpx;
background: linear-gradient(180deg, #a855f7, #9333ea);
background: linear-gradient(180deg, #ffcc00, #ffaa00);
border-radius: 3rpx 0 0 3rpx;
opacity: 0;
transition: all 0.3s;
@@ -353,7 +353,7 @@
.zhuanqu-indicator-glow {
opacity: 1;
animation: indicatorGlow 2s infinite;
box-shadow: 0 0 20rpx rgba(168, 85, 247, 0.5);
box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.5);
}
.indicator-sparkle {
@@ -372,7 +372,7 @@
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(168, 85, 247, 0.05), transparent);
background: linear-gradient(90deg, transparent, rgba(255, 204, 0, 0.05), transparent);
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
@@ -389,7 +389,7 @@
width: 0;
height: 0;
border-radius: 50%;
background: rgba(168, 85, 247, 0.1);
background: rgba(255, 204, 0, 0.1);
transform: translate(-50%, -50%);
opacity: 0;
pointer-events: none;
@@ -463,7 +463,7 @@
right: -4rpx;
bottom: -4rpx;
border-radius: 18rpx;
background: linear-gradient(135deg, rgba(168, 85, 247, 0.2), transparent);
background: linear-gradient(135deg, rgba(255, 204, 0, 0.2), transparent);
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
@@ -479,7 +479,7 @@
right: 0;
width: 20rpx;
height: 20rpx;
background: linear-gradient(135deg, #a855f7, transparent 70%);
background: linear-gradient(135deg, #ffcc00, transparent 70%);
border-radius: 0 14rpx 0 0;
}
@@ -514,7 +514,7 @@
left: 0;
width: 0;
height: 2rpx;
background: linear-gradient(90deg, #c4b5fd, #9333ea);
background: linear-gradient(90deg, #ffcc00, #ffaa00);
transition: width 0.3s;
}
@@ -537,20 +537,20 @@
.price-icon {
font-size: 24rpx;
color: #9333ea;
color: #ff6600;
font-weight: 500;
}
.price-integer {
font-size: 36rpx; /* 适当调整大小 */
color: #9333ea;
color: #ff6600;
font-weight: 600;
margin-left: 2rpx;
}
.price-decimal {
font-size: 24rpx;
color: #9333ea;
color: #ff6600;
font-weight: 500;
}
@@ -573,11 +573,11 @@
.detail-btn {
position: relative;
padding: 12rpx 28rpx;
background: linear-gradient(135deg, #c4b5fd, #9333ea);
background: linear-gradient(135deg, #ffcc00, #ffaa00);
border-radius: 40rpx;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
overflow: hidden;
box-shadow: 0 6rpx 20rpx rgba(147, 51, 234, 0.2);
box-shadow: 0 6rpx 20rpx rgba(255, 170, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
@@ -586,7 +586,7 @@
.detail-btn:active {
transform: scale(0.95);
box-shadow: 0 4rpx 15rpx rgba(147, 51, 234, 0.4);
box-shadow: 0 4rpx 15rpx rgba(255, 170, 0, 0.4);
}
.detail-btn-text {
@@ -659,7 +659,7 @@
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, transparent, rgba(168, 85, 247, 0.03), transparent);
background: linear-gradient(135deg, transparent, rgba(255, 204, 0, 0.03), transparent);
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
@@ -676,7 +676,7 @@
width: 0;
height: 0;
border-radius: 50%;
background: rgba(168, 85, 247, 0.1);
background: rgba(255, 204, 0, 0.1);
transform: translate(-50%, -50%);
opacity: 0;
pointer-events: none;
@@ -707,19 +707,19 @@
}
.shangpin-empty-btn {
background: linear-gradient(135deg, #c4b5fd, #9333ea);
background: linear-gradient(135deg, #ffcc00, #ffaa00);
color: white;
font-size: 26rpx;
font-weight: 500;
padding: 16rpx 44rpx;
border-radius: 40rpx;
box-shadow: 0 8rpx 24rpx rgba(147, 51, 234, 0.3);
box-shadow: 0 8rpx 24rpx rgba(255, 170, 0, 0.3);
transition: all 0.3s;
}
.shangpin-empty-btn:active {
transform: scale(0.95);
box-shadow: 0 4rpx 16rpx rgba(147, 51, 234, 0.4);
box-shadow: 0 4rpx 16rpx rgba(255, 170, 0, 0.4);
}
.load-more-line {
@@ -859,7 +859,7 @@
right: -4rpx;
bottom: -4rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, rgba(168, 85, 247, 0.2), transparent);
background: linear-gradient(135deg, rgba(255, 204, 0, 0.2), transparent);
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
@@ -896,7 +896,7 @@
.info-tag {
padding: 6rpx 12rpx;
background: rgba(168, 85, 247, 0.1);
background: rgba(255, 204, 0, 0.1);
border-radius: 6rpx;
}
@@ -921,7 +921,7 @@
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, transparent, rgba(168, 85, 247, 0.05), transparent);
background: linear-gradient(135deg, transparent, rgba(255, 204, 0, 0.05), transparent);
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
@@ -977,7 +977,7 @@
width: 100%;
height: 100%;
border: 4rpx solid transparent;
border-top-color: #a855f7;
border-top-color: #ffcc00;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@@ -1028,7 +1028,7 @@
left: 50%;
width: 12rpx;
height: 12rpx;
background: #a855f7;
background: #ffcc00;
border-radius: 50%;
transform: translateX(-50%);
animation: spin 1s linear infinite;
@@ -1045,7 +1045,7 @@
left: 50%;
width: 200rpx;
height: 200rpx;
background: radial-gradient(circle, rgba(168, 85, 247, 0.2) 0%, transparent 70%);
background: radial-gradient(circle, rgba(255, 204, 0, 0.2) 0%, transparent 70%);
transform: translate(-50%, -50%);
z-index: -1;
}
@@ -1076,19 +1076,19 @@
@keyframes borderGlow {
0%, 100% {
border-color: rgba(168, 85, 247, 0.3);
border-color: rgba(255, 204, 0, 0.3);
}
50% {
border-color: rgba(168, 85, 247, 0.8);
border-color: rgba(255, 204, 0, 0.8);
}
}
@keyframes indicatorGlow {
0%, 100% {
box-shadow: 0 0 20rpx rgba(168, 85, 247, 0.3);
box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.3);
}
50% {
box-shadow: 0 0 30rpx rgba(168, 85, 247, 0.8);
box-shadow: 0 0 30rpx rgba(255, 204, 0, 0.8);
}
}

View File

@@ -1,7 +1,14 @@
const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { resolveAvatarUrl, getDefaultAvatarUrl } from '../../utils/avatar.js';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage, subscribeConfigAvatarRefresh } from '../../utils/avatar.js';
import { getLocalImUserId } from '../../utils/im-user.js';
import { recordConversationOpen } from '../../utils/group-chat.js';
import {
fetchHistoryMessages,
getOldestHistoryTimestamp,
mergeHistoryMessages,
waitChatImReady,
} from '../../utils/chat-history.js';
Page({
data: {
@@ -25,29 +32,58 @@ Page({
pendingImage: '',
pendingImageFile: null,
keyboardHeight: 0,
bottomSafeHeight: 10
bottomSafeHeight: 140,
defaultAvatarUrl: '',
},
onLoad(options) {
const defAvatar = getDefaultAvatarUrl(app);
if (options.data) {
try {
const p = JSON.parse(decodeURIComponent(options.data));
this.setData({
toUserId: p.toUserId || '',
toName: p.toName || '聊天',
toAvatar: resolveAvatarUrl(p.toAvatar, app),
toAvatar: resolveAvatarUrl(p.toAvatar, app) || defAvatar,
defaultAvatarUrl: defAvatar,
});
} catch (e) {}
} catch (e) {
this.setData({ defaultAvatarUrl: defAvatar });
}
} else {
this.setData({ defaultAvatarUrl: defAvatar });
}
wx.setNavigationBarTitle({ title: this.data.toName });
this.initCurrentUser();
this._unsubConfigAvatar = subscribeConfigAvatarRefresh(this, () => {
const def = getDefaultAvatarUrl(app);
this.setData({
defaultAvatarUrl: def,
toAvatar: resolveAvatarUrl(this.data.toAvatar, app) || def,
});
this.refreshCurrentUserAvatar();
});
},
onShow() {
const defAvatar = getDefaultAvatarUrl(app);
this.setData({
defaultAvatarUrl: defAvatar,
toAvatar: resolveAvatarUrl(this.data.toAvatar, app) || defAvatar,
});
this.refreshCurrentUserAvatar();
if (this.data.toUserId) {
if (!app.globalData.pageState) app.globalData.pageState = {};
app.globalData.pageState.lastOpenedConvId = this.data.toUserId;
recordConversationOpen({ userId: this.data.toUserId });
}
this.ensureChatReady();
},
onHide() {
if (this.data.toUserId) {
recordConversationOpen({ userId: this.data.toUserId });
}
this.clearPageListeners();
this.markPrivateMessageAsRead();
setTimeout(() => {
@@ -55,27 +91,34 @@ Page({
}, 250);
},
ensureChatReady() {
onUnload() {
if (this._unsubConfigAvatar) {
this._unsubConfigAvatar();
this._unsubConfigAvatar = null;
}
},
async ensureChatReady() {
const targetUserId = getLocalImUserId(app);
if (!targetUserId) return;
const ready = () => {
const ready = async () => {
this.setupAllListeners();
this.loadHistory(true);
await this.loadHistory(true);
};
if (app.ensureConnection) {
const ret = app.ensureConnection();
if (ret && typeof ret.then === 'function') {
ret.then(ready).catch(ready);
} else {
setTimeout(ready, 300);
}
try {
const ok = await waitChatImReady(app, 12000);
if (ok) {
await ready();
return;
}
} catch (e) {
console.warn('[私聊] IM 连接失败', e);
}
if (this.isConnected() && wx.goEasy.im?.userId === targetUserId) {
ready();
await ready();
return;
}
this.connectGoEasy(targetUserId);
@@ -133,14 +176,26 @@ Page({
const imId = getLocalImUserId(app);
const uid = wx.getStorageSync('uid');
const nick = app.globalData.nicheng || app.globalData.dashouNicheng || wx.getStorageSync('nicheng') || '';
const defAvatar = getDefaultAvatarUrl(app);
const avatar = resolveAvatarFromStorage(app) || defAvatar;
this.setData({
currentUser: {
id: imId || ('Ds' + uid),
name: nick || `用户${uid}`,
avatar: resolveAvatarUrl(wx.getStorageSync('touxiang'), app),
avatar,
},
});
},
refreshCurrentUserAvatar() {
const defAvatar = getDefaultAvatarUrl(app);
const cu = this.data.currentUser;
if (!cu) return;
const avatar = resolveAvatarUrl(cu.avatar || wx.getStorageSync('touxiang'), app) || defAvatar;
if (avatar !== cu.avatar) {
this.setData({ currentUser: { ...cu, avatar } });
}
},
fixAvatar(url) {
return resolveAvatarUrl(url, app);
},
@@ -156,6 +211,19 @@ Page({
normalizeMessage(msg) {
if (!msg) return msg;
if (!msg.payload) msg.payload = {};
const defAvatar = this.data.defaultAvatarUrl || getDefaultAvatarUrl(app);
const myId = this.data.currentUser?.id;
if (myId && msg.senderId === myId) {
msg.senderData = {
name: this.data.currentUser.name,
avatar: resolveAvatarUrl(this.data.currentUser.avatar, app) || defAvatar,
};
} else {
msg.senderData = {
name: this.data.toName,
avatar: resolveAvatarUrl(this.data.toAvatar, app) || defAvatar,
};
}
if (msg.type === 'image') {
const p = msg.payload;
if (!p.url) p.url = p.thumbnail || p.thumb || p.imageUrl || '';
@@ -188,28 +256,25 @@ Page({
onPullDownRefresh() {
if (this.data.loadingHistory) return;
if (!this.data.hasMore) {
wx.showToast({ title: '没有更早的消息了', icon: 'none' });
this.setData({ isRefreshing: false });
wx.stopPullDownRefresh();
return;
}
this.setData({ isRefreshing: true });
this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); });
this.loadHistory(false).finally(() => {
this.setData({ isRefreshing: false });
wx.stopPullDownRefresh();
});
},
loadHistory(refresh) {
if (!refresh && !this.data.hasMore) return Promise.resolve();
return new Promise((resolve) => {
this.setData({ loadingHistory: true });
const { toUserId, lastTimestamp } = this.data;
const ts = refresh ? null : lastTimestamp;
wx.goEasy.im.history({
type: wx.GoEasy.IM_SCENE.PRIVATE, id: toUserId, lastTimestamp: ts, limit: 20,
onSuccess: (res) => {
let list = res.content || [];
list.sort((a, b) => a.timestamp - b.timestamp);
list.forEach((m, idx) => {
mapHistoryMessage(m, idx, list) {
m = this.normalizeMessage(m);
list[idx] = m;
m.formattedTime = formatDate(m.timestamp);
if (idx === 0) m.showTime = true;
else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5;
if (m.senderId === this.data.currentUser.id) {
else m.showTime = (m.timestamp - list[idx - 1].timestamp) / 60000 > 5;
if (m.senderId === this.data.currentUser?.id) {
m.senderData = {
name: this.data.currentUser.name,
avatar: resolveAvatarUrl(this.data.currentUser.avatar, app),
@@ -220,31 +285,62 @@ Page({
avatar: resolveAvatarUrl(this.data.toAvatar, app),
};
}
return m;
},
async loadHistory(refresh) {
if (!refresh && !this.data.hasMore) return;
if (this.data.loadingHistory) return;
if (!this.data.toUserId) return;
this.setData({ loadingHistory: true });
try {
const ready = await waitChatImReady(app, 12000);
if (!ready) {
wx.showToast({ title: '消息服务未连接,请稍后重试', icon: 'none' });
return;
}
const ts = refresh
? null
: getOldestHistoryTimestamp(this.data.messages, this.data.lastTimestamp);
if (!refresh && ts == null) {
this.setData({ hasMore: false });
wx.showToast({ title: '没有更早的消息了', icon: 'none' });
return;
}
const raw = await fetchHistoryMessages({
scene: wx.GoEasy.IM_SCENE.PRIVATE,
id: this.data.toUserId,
lastTimestamp: ts,
limit: 20,
});
let final = refresh ? list : [...list, ...this.data.messages];
if (!refresh) {
for (let i=0; i<final.length; i++) {
if (i===0) final[i].showTime = true;
else final[i].showTime = (final[i].timestamp - final[i-1].timestamp) / 60000 > 5;
}
}
const ids = new Set();
const unique = [];
for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } }
const merged = mergeHistoryMessages({
incoming: raw,
existing: this.data.messages,
refresh,
limit: 20,
mapMessage: (m, idx, list) => this.mapHistoryMessage(m, idx, list),
});
const update = {
messages: unique,
hasMore: list.length >= 20,
lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp,
loadingHistory: false
messages: merged.messages,
hasMore: merged.hasMore,
lastTimestamp: merged.lastTimestamp,
};
if (refresh) update.scrollToView = 'msg-bottom';
this.setData(update);
if (refresh) this.markPrivateMessageAsRead();
resolve();
},
onFailed: () => { this.setData({ loadingHistory: false }); resolve(); }
});
});
} catch (e) {
console.warn('[私聊] 历史消息加载失败', e);
if (!refresh) {
wx.showToast({ title: '加载历史消息失败', icon: 'none' });
}
} finally {
this.setData({ loadingHistory: false });
}
},
listenNewMsg() {

View File

@@ -4,13 +4,13 @@
refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 20rpx);">
style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 24rpx);">
<view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<block wx:for="{{messages}}" wx:key="messageId">
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" />
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{toAvatar || defaultAvatarUrl}}" mode="aspectFill" binderror="onPeerAvatarError" />
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}"
@@ -23,14 +23,14 @@
<text class="chat-order-info">ID: {{item.payload.orderId}}</text>
</view>
</view>
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{item.senderData.avatar || currentUser.avatar}}" mode="aspectFill" binderror="onSelfAvatarError" data-index="{{index}}" />
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar || defaultAvatarUrl}}" mode="aspectFill" binderror="onSelfAvatarError" data-index="{{index}}" />
</view>
<view wx:if="{{item.senderId === currentUser.id && item.status === 'success'}}" class="chat-read-tag">
{{item.read ? '已读' : '未读'}}
</view>
</view>
</block>
<view id="msg-bottom" style="height:20rpx;"></view>
<view id="msg-bottom" style="height:56rpx;"></view>
</scroll-view>
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">

View File

@@ -1,7 +1,14 @@
const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { getLocalImUserId } from '../../utils/im-user.js';
import { resolveAvatarUrl } from '../../utils/avatar.js';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage, subscribeConfigAvatarRefresh, ensureAvatarUrl } from '../../utils/avatar.js';
import {
fetchHistoryMessages,
getOldestHistoryTimestamp,
mergeHistoryMessages,
waitChatImReady,
} from '../../utils/chat-history.js';
import { matchAutoReply, getWelcomeText } from '../../utils/scriptService.js';
Page({
data: {
@@ -29,11 +36,14 @@ Page({
pendingImage: '',
pendingImageFile: null,
keyboardHeight: 0,
bottomSafeHeight: 10
bottomSafeHeight: 140,
defaultAvatarUrl: '',
welcomeText: '你好,请问有什么可以帮到您的?',
},
onLoad(options) {
let teamId = '', teamName = '客服', teamAvatar = '';
const defAvatar = getDefaultAvatarUrl(app);
if (options.data) {
try {
const p = JSON.parse(decodeURIComponent(options.data));
@@ -43,38 +53,102 @@ Page({
} catch (e) {
}
}
if (!teamAvatar) {
teamAvatar = app.globalData.ossImageUrl + app.globalData.morentouxiang;
let teamAvatarResolved = teamAvatar ? ensureAvatarUrl(teamAvatar, app) : defAvatar;
if (!teamAvatarResolved || !teamAvatarResolved.startsWith('http')) {
teamAvatarResolved = defAvatar;
}
this.setData({ teamId, teamName, teamAvatar });
this.setData({ teamId, teamName, teamAvatar: teamAvatarResolved, defaultAvatarUrl: defAvatar });
wx.setNavigationBarTitle({ title: teamName });
this.initCurrentUser();
this.loadWelcomeScript();
this._unsubConfigAvatar = subscribeConfigAvatarRefresh(this, () => {
const def = getDefaultAvatarUrl(app);
this.setData({
defaultAvatarUrl: def,
teamAvatar: def,
});
this.refreshCurrentUserAvatar();
});
},
onUnload() {
if (this._unsubConfigAvatar) {
this._unsubConfigAvatar();
this._unsubConfigAvatar = null;
}
},
onShow() {
const defAvatar = getDefaultAvatarUrl(app);
const patch = {
defaultAvatarUrl: defAvatar,
teamAvatar: defAvatar,
};
this.setData(patch);
this.refreshCurrentUserAvatar();
this.autoConnect();
},
refreshCurrentUserAvatar() {
const defAvatar = getDefaultAvatarUrl(app);
const cu = this.data.currentUser;
if (!cu) return;
const avatar = resolveAvatarUrl(cu.avatar || wx.getStorageSync('touxiang'), app) || defAvatar;
if (avatar !== cu.avatar) {
this.setData({ currentUser: { ...cu, avatar } });
}
},
onHide() {
this.clearAllListeners();
},
autoConnect() {
async loadWelcomeScript() {
try {
const text = await getWelcomeText();
if (!text) return;
this.setData({ welcomeText: text });
const msgs = this.data.messages;
const idx = msgs.findIndex((m) => m.messageId === 'welcome-cs');
if (idx >= 0) {
const updated = [...msgs];
updated[idx] = { ...updated[idx], payload: { ...updated[idx].payload, text } };
this.setData({ messages: updated });
}
} catch (e) {
console.warn('loadWelcomeScript failed', e);
}
},
async autoConnect() {
const targetUserId = getLocalImUserId(app);
if (!targetUserId) return;
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
if (currentUserId === targetUserId) {
const ready = async () => {
this.setupAllListeners();
this.loadHistory(true);
await this.loadHistory(true);
};
try {
const ok = await waitChatImReady(app, 12000);
if (ok) {
await ready();
return;
}
} catch (e) {
console.warn('[客服] IM 连接失败', e);
}
if (this.isConnected()) {
await ready();
} else {
wx.goEasy.disconnect();
}
}
this.connectGoEasy(targetUserId);
}
},
isConnected() {
const s = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
return s === 'connected' || s === 'reconnected';
},
connectGoEasy(userId) {
@@ -84,7 +158,7 @@ Page({
id: userId,
data: {
name: currentUser.name,
avatar: resolveAvatarUrl(currentUser.avatar, app)
avatar: resolveAvatarUrl(currentUser.avatar, app) || getDefaultAvatarUrl(app)
},
onSuccess: () => {
this.setupAllListeners();
@@ -119,11 +193,13 @@ Page({
initCurrentUser() {
const imId = getLocalImUserId(app);
const uid = wx.getStorageSync('uid');
const defAvatar = getDefaultAvatarUrl(app);
const avatar = resolveAvatarFromStorage(app) || defAvatar;
this.setData({
currentUser: {
id: imId || ('Boss' + uid),
name: app.globalData.currentUser?.name || `用户${uid}`,
avatar: resolveAvatarUrl(wx.getStorageSync('touxiang'), app),
name: app.globalData.currentUser?.name || wx.getStorageSync('nicheng') || `用户${uid}`,
avatar,
},
});
},
@@ -169,66 +245,89 @@ Page({
this.setData({ messages: msgs });
},
// 历史消息
loadHistory(refresh) {
if (!refresh && !this.data.hasMore) return Promise.resolve();
return new Promise((resolve) => {
this.setData({ loadingHistory: true });
const { teamId, lastTimestamp } = this.data;
const ts = refresh ? null : lastTimestamp;
wx.goEasy.im.history({
type: wx.GoEasy.IM_SCENE.CS,
id: teamId,
lastTimestamp: ts,
limit: 20,
onSuccess: (res) => {
let list = res.content || [];
list.sort((a, b) => a.timestamp - b.timestamp);
list.forEach((m, idx) => {
mapHistoryMessage(m, idx, list) {
m = this.normalizeMessage(m);
list[idx] = m;
m.formattedTime = formatDate(m.timestamp);
if (idx === 0) m.showTime = true;
else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5;
if (m.senderId === this.data.currentUser.id) {
m.senderData = { name: this.data.currentUser.name, avatar: this.data.currentUser.avatar };
else m.showTime = (m.timestamp - list[idx - 1].timestamp) / 60000 > 5;
if (m.senderId === this.data.currentUser?.id) {
const av = resolveAvatarUrl(this.data.currentUser.avatar, app) || this.data.defaultAvatarUrl;
m.senderData = { name: this.data.currentUser.name, avatar: av };
} else {
m.senderData = { name: this.data.teamName, avatar: this.data.teamAvatar };
const av = resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl;
m.senderData = { name: this.data.teamName, avatar: av };
}
});
let final = refresh ? list : [...list, ...this.data.messages];
if (!refresh) {
for (let i=0; i<final.length; i++) {
if (i===0) final[i].showTime = true;
else final[i].showTime = (final[i].timestamp - final[i-1].timestamp) / 60000 > 5;
}
}
const ids = new Set();
const unique = [];
for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } }
return m;
},
// 插入欢迎语
injectWelcomeMessage(messages) {
const list = Array.isArray(messages) ? [...messages] : [];
const welcomeId = 'welcome-cs';
if (!unique.some(m => m.messageId === welcomeId)) {
if (!list.some((m) => m.messageId === welcomeId)) {
const welcome = this.createWelcomeMessage();
welcome.messageId = welcomeId;
welcome.showTime = true;
unique.unshift(welcome);
list.unshift(welcome);
}
return list;
},
// 历史消息
async loadHistory(refresh) {
if (!refresh && !this.data.hasMore) return;
if (this.data.loadingHistory) return;
if (!this.data.teamId) return;
this.setData({ loadingHistory: true });
try {
const ready = await waitChatImReady(app, 12000);
if (!ready) {
wx.showToast({ title: '消息服务未连接,请稍后重试', icon: 'none' });
return;
}
const ts = refresh
? null
: getOldestHistoryTimestamp(this.data.messages, this.data.lastTimestamp);
if (!refresh && ts == null) {
this.setData({ hasMore: false });
wx.showToast({ title: '没有更早的消息了', icon: 'none' });
return;
}
const raw = await fetchHistoryMessages({
scene: wx.GoEasy.IM_SCENE.CS,
id: this.data.teamId,
lastTimestamp: ts,
limit: 20,
});
let merged = mergeHistoryMessages({
incoming: raw,
existing: this.data.messages,
refresh,
limit: 20,
mapMessage: (m, idx, list) => this.mapHistoryMessage(m, idx, list),
});
merged.messages = this.injectWelcomeMessage(merged.messages);
merged.lastTimestamp = getOldestHistoryTimestamp(merged.messages, merged.lastTimestamp);
const update = {
messages: unique,
hasMore: list.length >= 20,
lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp,
loadingHistory: false
messages: merged.messages,
hasMore: merged.hasMore,
lastTimestamp: merged.lastTimestamp,
};
if (refresh) update.scrollToView = 'msg-bottom';
this.setData(update);
resolve();
},
onFailed: () => { this.setData({ loadingHistory: false }); resolve(); }
});
});
} catch (e) {
console.warn('[客服] 历史消息加载失败', e);
if (!refresh) {
wx.showToast({ title: '加载历史消息失败', icon: 'none' });
}
} finally {
this.setData({ loadingHistory: false });
}
},
createWelcomeMessage() {
@@ -238,8 +337,11 @@ Page({
timestamp: Date.now(),
senderId: 'system',
receiverId: this.data.currentUser?.id,
senderData: { name: '系统消息', avatar: '' },
payload: { text: '你好,请问有什么可以帮到您的?' },
senderData: {
name: this.data.teamName || '客服',
avatar: resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl,
},
payload: { text: this.data.welcomeText || '你好,请问有什么可以帮到您的?' },
status: 'success',
read: false,
formattedTime: formatDate(Date.now()),
@@ -255,7 +357,10 @@ Page({
if (msg.senderId === this.data.currentUser.id) return;
msg = this.normalizeMessage(msg);
msg.formattedTime = formatDate(msg.timestamp);
msg.senderData = { name: this.data.teamName, avatar: this.data.teamAvatar };
msg.senderData = {
name: this.data.teamName,
avatar: resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl,
};
const msgs = this.data.messages;
const last = msgs.length ? msgs[msgs.length-1] : null;
msg.showTime = last ? (msg.timestamp - last.timestamp) / 60000 > 5 : true;
@@ -370,11 +475,53 @@ Page({
});
wx.goEasy.im.sendMessage({
message:msg,
onSuccess:() => this.updateMsg(id, 'success'),
onSuccess:() => {
this.updateMsg(id, 'success');
this.tryAutoReply(text);
},
onFailed:() => this.updateMsg(id, 'failed')
});
},
async tryAutoReply(userText) {
try {
const result = await matchAutoReply(userText);
if (result && result.matched && result.content) {
this.insertCsAutoReply(result);
}
} catch (e) {
console.warn('tryAutoReply failed', e);
}
},
insertCsAutoReply(result) {
const replyType = (result && result.reply_type) || 'text';
const content = (result && result.content) || '';
if (!content) return;
const isImage = replyType === 'image';
const msg = {
messageId: 'auto-reply-' + Date.now(),
type: isImage ? 'image' : 'text',
timestamp: Date.now(),
senderId: this.data.teamId,
receiverId: this.data.currentUser?.id,
senderData: {
name: this.data.teamName,
avatar: resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl,
},
payload: isImage ? { url: content } : { text: content },
status: 'success',
read: false,
formattedTime: formatDate(Date.now()),
showTime: this.needShow(Date.now()),
};
if (isImage) msg.imageUrl = content;
this.setData({
messages: [...this.data.messages, msg],
scrollToView: 'msg-bottom',
});
},
sendImageMsg(filePath, fileObj) {
const { currentUser, teamId, teamName, teamAvatar } = this.data;
if (!teamId || !currentUser || !filePath) return;
@@ -521,9 +668,30 @@ Page({
onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); },
onTeamAvatarError() {
this.setData({ teamAvatar: getDefaultAvatarUrl(app) });
},
onSelfAvatarError() {
const def = getDefaultAvatarUrl(app);
const cu = this.data.currentUser;
if (cu) {
this.setData({ currentUser: { ...cu, avatar: def } });
}
},
onPullDownRefresh() {
if (this.data.loadingHistory) return;
if (!this.data.hasMore) {
wx.showToast({ title: '没有更早的消息了', icon: 'none' });
this.setData({ isRefreshing: false });
wx.stopPullDownRefresh();
return;
}
this.setData({ isRefreshing: true });
this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); });
this.loadHistory(false).finally(() => {
this.setData({ isRefreshing: false });
wx.stopPullDownRefresh();
});
}
});

View File

@@ -11,7 +11,7 @@
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<!-- 对方头像(客服) -->
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{teamAvatar}}" mode="aspectFill" />
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{defaultAvatarUrl}}" mode="aspectFill" binderror="onTeamAvatarError" />
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}"
@@ -24,19 +24,19 @@
<view class="chat-order-info">
<text>订单号:{{item.payload.dingdan_id || '无'}}</text>
<text>服务:{{item.payload.jieshao || ''}}</text>
<text style="color:#a855f7;font-weight:bold">¥{{item.payload.jine}}</text>
<text style="color:#e6a23c;font-weight:bold">¥{{item.payload.jine}}</text>
</view>
<button class="chat-copy-btn" catchtap="showOrderDetailPopup" data-order="{{item.payload}}" size="mini">查看详情</button>
</view>
<text wx:else class="chat-msg-text">{{item.payload.text || '[未知消息]'}}</text>
</view>
<!-- 自己头像 -->
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar}}" mode="aspectFill" />
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar || defaultAvatarUrl}}" mode="aspectFill" binderror="onSelfAvatarError" />
</view>
<!-- 🔥 已读/未读标签已彻底移除 -->
</view>
</block>
<view id="msg-bottom" style="height:20rpx;"></view>
<view id="msg-bottom" style="height:56rpx;"></view>
</scroll-view>
<!-- 底部输入区域(保持不变) -->

View File

@@ -0,0 +1,283 @@
import { createPage, request } from '../../utils/base-page.js';
import { refreshDashouMembership } from '../../utils/dashou-profile.js';
import { isApiSuccess, getApiMsg } from '../../utils/api-helper.js';
const LABELS = ['A', 'B', 'C', 'D', 'E', 'F'];
function toSlotNum(slot) {
const n = Number(slot);
return Number.isFinite(n) ? n : slot;
}
function normalizeSlots(slots) {
return (slots || []).map(toSlotNum);
}
function slotsEqual(a, b) {
const sa = normalizeSlots(a).sort((x, y) => x - y);
const sb = normalizeSlots(b).sort((x, y) => x - y);
return sa.length === sb.length && sa.every((v, i) => v == sb[i]);
}
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
Page(createPage({
data: {
loading: true,
alreadyPassed: false,
finished: false,
blocked: false,
blockTitle: '',
blockReason: '',
needRecharge: false,
questions: [],
currentIndex: 0,
currentQuestion: null,
questionType: 1,
total: 0,
wrongCount: 0,
maxWrong: 0,
correctCount: 0,
selectedSlots: [],
showExplanation: false,
},
onLoad() {
this.initExam();
},
showBlocked(title, reason, needRecharge = false) {
this.setData({
loading: false,
blocked: true,
blockTitle: title,
blockReason: reason,
needRecharge: !!needRecharge,
});
},
/** 进入页先查 status不满足条件留在本页提示不自动退出 */
async initExam() {
this.setData({ loading: true, blocked: false, needRecharge: false });
try {
await refreshDashouMembership(getApp());
const res = await request({ url: '/jituan/dashou-exam/status', method: 'POST' });
const body = res?.data;
if (!isApiSuccess(body)) {
this.showBlocked('无法开始考试', getApiMsg(body) || '无法获取考试状态,请稍后重试');
return;
}
const d = body.data || {};
if (!d.exam_enabled) {
this.showBlocked(
'未开启考试',
'本俱乐部尚未开启接单考试。若后台已开启,请确认配置的是当前小程序对应俱乐部。'
);
return;
}
if (d.exam_passed) {
this.setData({ loading: false, alreadyPassed: true });
return;
}
const cfg = d.config || {};
if (cfg.require_member && !d.has_member) {
this.showBlocked('须先开通会员', '本俱乐部要求开通会员后才能参加考试', true);
return;
}
if ((d.pool_count || 0) < 1) {
this.showBlocked(
'暂无可用考题',
'考试已开启,但题库没有「已上架」的题目。请在后台确认题目已上架,且每题至少两个选项并标记正确答案。'
);
return;
}
await this.loadBundle();
} catch (e) {
this.showBlocked('网络错误', '请检查网络后下拉重新进入本页');
}
},
async loadBundle() {
this.setData({ loading: true, finished: false, showExplanation: false, blocked: false });
try {
const res = await request({ url: '/jituan/dashou-exam/bundle', method: 'POST' });
const body = res?.data;
if (!isApiSuccess(body)) {
this.showBlocked('加载考题失败', getApiMsg(body) || '请稍后重试或联系管理员');
return;
}
const d = body.data || {};
if (d.already_passed) {
this.setData({ alreadyPassed: true, loading: false });
return;
}
const cfg = d.config || {};
const prepared = (d.questions || []).map((q) => this.prepareQuestion(q)).filter((q) => (q.displayOptions || []).length > 0);
if (!prepared.length) {
this.showBlocked(
'题目配置不完整',
'已抽到的题目缺少有效选项,请在后台检查题目是否至少两个选项并标记正确答案。'
);
return;
}
this.setData({
loading: false,
questions: prepared,
total: prepared.length,
currentIndex: 0,
wrongCount: 0,
correctCount: 0,
maxWrong: cfg.max_wrong || 0,
currentQuestion: prepared[0],
questionType: prepared[0].question_type || 1,
selectedSlots: [],
});
} catch (e) {
this.showBlocked('网络错误', '拉取考题失败,请稍后重试');
}
},
prepareQuestion(q) {
const oss = getApp().globalData.ossImageUrl || '';
const displayImages = (q.images || []).map((u) => (u.startsWith('http') ? u : oss + u));
const shuffled = shuffle(q.options || []).map((opt, idx) => ({
slot: toSlotNum(opt.slot),
text: opt.text,
label: LABELS[idx] || String(idx + 1),
selected: false,
}));
return {
...q,
question_type: Number(q.question_type) || 1,
displayImages,
displayOptions: shuffled,
correctSlots: normalizeSlots(q.correct_slots || []),
};
},
applySelectedToOptions(selectedSlots) {
const q = this.data.currentQuestion;
if (!q) return;
const set = normalizeSlots(selectedSlots);
const displayOptions = (q.displayOptions || []).map((opt) => ({
...opt,
selected: set.some((s) => s == opt.slot),
}));
this.setData({
selectedSlots: set,
currentQuestion: { ...q, displayOptions },
});
},
onSelectOption(e) {
const slot = toSlotNum(e.currentTarget.dataset.slot);
const q = this.data.currentQuestion;
if (!q || slot === undefined || slot === null || slot === '') return;
if (q.question_type === 2) {
const set = [...this.data.selectedSlots];
const i = set.findIndex((s) => s == slot);
if (i >= 0) set.splice(i, 1);
else set.push(slot);
if (this.data.showExplanation) {
this.setData({ showExplanation: false });
}
this.applySelectedToOptions(set);
return;
}
this.applySelectedToOptions([slot]);
if (this.data.showExplanation) {
this.setData({ showExplanation: false });
}
setTimeout(() => this.gradeAnswer([slot]), 120);
},
confirmMulti() {
if (this.data.showExplanation) {
this.setData({ showExplanation: false });
this.applySelectedToOptions([]);
return;
}
if (!this.data.selectedSlots.length) {
wx.showToast({ title: '请选择答案', icon: 'none' });
return;
}
this.gradeAnswer([...this.data.selectedSlots]);
},
/** 前端本地判分bundle 已含正确答案与解析) */
gradeAnswer(selected) {
const q = this.data.currentQuestion;
const correct = q.correctSlots || [];
const isRight = slotsEqual(selected, correct);
if (!isRight) {
const wrongCount = this.data.wrongCount + 1;
this.setData({ wrongCount, showExplanation: true });
if (wrongCount > this.data.maxWrong) {
wx.showModal({
title: '答题失败',
content: `${q.explanation || '本题答错了'}。错题过多,将重新开始本轮考试。`,
showCancel: false,
success: () => this.loadBundle(),
});
} else {
wx.showToast({ title: '答错了,请查看解析', icon: 'none' });
}
return;
}
const correctCount = this.data.correctCount + 1;
const next = this.data.currentIndex + 1;
if (next >= this.data.total) {
this.submitPassed(correctCount);
return;
}
const nextQ = this.data.questions[next];
this.setData({
correctCount,
currentIndex: next,
currentQuestion: nextQ,
questionType: nextQ.question_type,
selectedSlots: [],
showExplanation: false,
});
},
async submitPassed(correctCount) {
try {
const res = await request({ url: '/jituan/dashou-exam/mark-passed', method: 'POST' });
const body = res?.data;
if (isApiSuccess(body)) {
this.setData({ finished: true, correctCount });
} else {
wx.showToast({ title: getApiMsg(body) || '提交失败', icon: 'none' });
}
} catch (e) {
wx.showToast({ title: '提交失败', icon: 'none' });
}
},
goRecharge() {
wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' });
},
retryInit() {
this.initExam();
},
goBack() {
const pages = getCurrentPages();
if (pages.length > 1) {
wx.navigateBack();
} else {
wx.switchTab({ url: '/pages/accept-order/accept-order' });
}
},
}));

View File

@@ -0,0 +1,6 @@
{
"usingComponents": {},
"navigationBarTitleText": "接单考试",
"navigationBarBackgroundColor": "#c4b5fd",
"navigationBarTextStyle": "black"
}

View File

@@ -0,0 +1,66 @@
<view class="exam-page">
<view wx:if="{{loading}}" class="center-tip">正在检查考试资格...</view>
<view wx:elif="{{blocked}}" class="center-box">
<text class="tip-title">{{blockTitle}}</text>
<text class="tip-desc">{{blockReason}}</text>
<view class="btn-row">
<view wx:if="{{needRecharge}}" class="btn-primary block" bindtap="goRecharge">去开通会员</view>
<view class="btn-outline block" bindtap="retryInit">重试</view>
<view class="btn-outline block" bindtap="goBack">返回</view>
</view>
</view>
<view wx:elif="{{alreadyPassed}}" class="center-box">
<text class="tip-title">已通过考试</text>
<text class="tip-desc">无需重复考试,可直接抢单</text>
<view class="btn-primary block" bindtap="goBack">返回</view>
</view>
<view wx:elif="{{finished}}" class="center-box">
<text class="tip-title">考试通过</text>
<text class="tip-desc">恭喜通过,现在可以去抢单了</text>
<view class="btn-primary block" bindtap="goBack">去抢单</view>
</view>
<view wx:elif="{{currentQuestion}}" class="exam-body">
<view class="exam-card">
<view class="progress">第 {{currentIndex + 1}} / {{total}} 题</view>
<view class="progress-sub">已错 {{wrongCount}} 题 · 最多可错 {{maxWrong}} 题</view>
<view class="q-type-tag">{{questionType === 2 ? '多选题' : '单选题'}}</view>
<view class="q-stem">{{currentQuestion.stem}}</view>
<view class="q-images" wx:if="{{currentQuestion.displayImages && currentQuestion.displayImages.length}}">
<image wx:for="{{currentQuestion.displayImages}}" wx:key="*this" class="q-img" src="{{item}}" mode="widthFix" />
</view>
<view class="options">
<view
wx:for="{{currentQuestion.displayOptions}}"
wx:key="slot"
class="opt-item {{item.selected ? 'opt-active' : ''}}"
data-slot="{{item.slot}}"
bindtap="onSelectOption"
>
<view class="opt-radio {{item.selected ? 'opt-radio-on' : ''}}">
<view wx:if="{{item.selected}}" class="opt-radio-dot"></view>
</view>
<view class="opt-body">
<text class="opt-label">{{item.label}}</text>
<text class="opt-text">{{item.text}}</text>
</view>
</view>
</view>
<view wx:if="{{showExplanation}}" id="explain-box" class="explain-box">
<text class="explain-title">解析</text>
<text class="explain-text">{{currentQuestion.explanation || '暂无解析'}}</text>
</view>
</view>
<view class="foot-actions" wx:if="{{questionType === 2}}">
<view class="btn-primary foot-btn" bindtap="confirmMulti">{{showExplanation ? '重新作答' : '确认本题'}}</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,228 @@
.exam-page {
min-height: 100vh;
background: linear-gradient(180deg, #f5f3ff 0%, #fff3d6 100%);
padding: 24rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.center-tip,
.center-box {
padding: 80rpx 40rpx;
text-align: center;
}
.tip-title {
font-size: 36rpx;
font-weight: 600;
display: block;
margin-bottom: 16rpx;
color: #333;
}
.tip-desc {
font-size: 28rpx;
color: #666;
display: block;
margin-bottom: 32rpx;
line-height: 1.6;
}
.btn-primary {
background: linear-gradient(90deg, #c4b5fd, #a78bfa);
padding: 22rpx 48rpx;
border-radius: 48rpx;
font-weight: 600;
font-size: 30rpx;
color: #6d28d9;
text-align: center;
}
.btn-primary.block {
display: block;
width: 100%;
box-sizing: border-box;
}
.btn-row {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-top: 24rpx;
}
.btn-outline {
padding: 20rpx 48rpx;
border-radius: 48rpx;
border: 2rpx solid #ddd;
color: #666;
font-size: 28rpx;
text-align: center;
background: #fff;
}
.btn-outline.block {
display: block;
width: 100%;
box-sizing: border-box;
}
.exam-body {
display: flex;
flex-direction: column;
min-height: calc(100vh - 48rpx);
}
.exam-card {
flex: 1;
background: #fff;
border-radius: 24rpx;
padding: 28rpx;
box-shadow: 0 8rpx 32rpx rgba(73, 47, 0, 0.06);
}
.progress {
font-size: 28rpx;
color: #6d28d9;
font-weight: 600;
}
.progress-sub {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
margin-bottom: 24rpx;
}
.q-type-tag {
display: inline-block;
font-size: 22rpx;
color: #b8860b;
background: #f5f3ff;
border: 1rpx solid #c4b5fd;
border-radius: 8rpx;
padding: 4rpx 16rpx;
margin-bottom: 16rpx;
}
.q-stem {
font-size: 32rpx;
font-weight: 600;
line-height: 1.6;
margin-bottom: 24rpx;
color: #222;
}
.q-images {
margin-bottom: 24rpx;
}
.q-img {
width: 100%;
border-radius: 12rpx;
margin-bottom: 12rpx;
}
.options {
margin-top: 8rpx;
}
.opt-item {
background: #fafafa;
border: 2rpx solid #eee;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 16rpx;
display: flex;
align-items: flex-start;
gap: 20rpx;
}
.opt-active {
border-color: #c4b5fd;
background: #fffdf0;
}
.opt-radio {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #ccc;
flex-shrink: 0;
margin-top: 4rpx;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.opt-radio-on {
border-color: #c4b5fd;
background: #f5f3ff;
}
.opt-radio-dot {
width: 18rpx;
height: 18rpx;
border-radius: 50%;
background: #c4b5fd;
}
.opt-body {
flex: 1;
display: flex;
gap: 12rpx;
align-items: flex-start;
}
.opt-label {
font-weight: 700;
color: #6d28d9;
min-width: 40rpx;
font-size: 28rpx;
}
.opt-text {
flex: 1;
font-size: 28rpx;
line-height: 1.5;
color: #333;
}
.explain-box {
background: #fffbf0;
border-left: 6rpx solid #e6a23c;
padding: 20rpx 24rpx;
margin-top: 24rpx;
border-radius: 0 12rpx 12rpx 0;
}
.explain-title {
font-weight: 600;
color: #333;
display: block;
margin-bottom: 8rpx;
font-size: 28rpx;
}
.explain-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.foot-actions {
margin-top: 24rpx;
padding: 0 8rpx;
}
.foot-btn {
width: 100%;
box-sizing: border-box;
min-height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
line-height: 1.2;
padding: 0 32rpx;
}

View File

@@ -0,0 +1,14 @@
/** 旧二维码兼容dashouduan → 打手中心inviteCode 自动注册逻辑不变 */
import { parseSceneOptions } from '../../utils/base-page.js';
Page({
onLoad(options) {
const parsed = parseSceneOptions(options || {});
const inviteCode = parsed.inviteCode || options.inviteCode || '';
let url = '/pages/fighter/fighter';
if (inviteCode) {
url += `?inviteCode=${encodeURIComponent(inviteCode)}`;
}
wx.reLaunch({ url });
},
});

View File

@@ -0,0 +1 @@
{ "navigationBarTitleText": "加载中", "usingComponents": {} }

View File

@@ -0,0 +1 @@
<view class="tip">正在进入打手端…</view>

View File

@@ -0,0 +1 @@
.tip { display:flex; align-items:center; justify-content:center; min-height:100vh; color:#999; font-size:28rpx; }

View File

@@ -1,6 +1,6 @@
/* pages/edit/edit.wxss */
page {
background: linear-gradient(135deg, #faf5ff 0%, #f0f8ff 100%);
background: linear-gradient(135deg, #fffaf0 0%, #f0f8ff 100%);
min-height: 100vh;
}

View File

@@ -4,7 +4,7 @@
"chenghao-tag": "/components/chenghao-tag/chenghao-tag"
},
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#fff8e1",
"navigationBarBackgroundColor": "#f7dc51",
"navigationBarTitleText": "常规发单",
"navigationBarTextStyle": "black",
"backgroundColor": "#f5f5f5",

View File

@@ -289,7 +289,7 @@ page {
}
.filter-switch.active {
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
color: #492f00;
}
@@ -323,7 +323,7 @@ page {
.copy-link-btn {
padding: 8rpx 20rpx;
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
color: #492f00;
border-radius: 15rpx;
font-size: 22rpx;
@@ -337,7 +337,7 @@ page {
.load-more-btn {
display: inline-block;
padding: 15rpx 40rpx;
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
border-radius: 30rpx;
color: #492f00;
font-size: 26rpx;
@@ -373,7 +373,7 @@ page {
.generate-modal-grid-btn,
.copy-modal-grid-btn,
.edit-modal-grid-btn {
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
color: #492f00;
}
@@ -484,7 +484,7 @@ page {
}
.confirm-add-grid-btn {
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
color: #492f00;
}
@@ -538,7 +538,7 @@ page {
}
.confirm-new-link-grid-btn {
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
color: #492f00;
}
@@ -569,7 +569,7 @@ page {
width: 90rpx;
height: 90rpx;
border: 8rpx solid #eee;
border-top: 8rpx solid #9333ea;
border-top: 8rpx solid #ffd061;
border-radius: 50%;
animation: spin 1.2s linear infinite;
margin-bottom: 35rpx;

View File

@@ -1,7 +1,7 @@
/* 页面容器 */
.container {
min-height: 100vh;
background: linear-gradient(135deg, #f5f3ff 0%, #e6f7ff 100%);
background: linear-gradient(135deg, #fffacd 0%, #e6f7ff 100%);
padding: 20rpx;
box-sizing: border-box;
}
@@ -27,7 +27,7 @@
height: 180rpx;
border-radius: 50%;
border: 6rpx solid #ffffff;
box-shadow: 0 0 30rpx rgba(196, 181, 253, 0.4);
box-shadow: 0 0 30rpx rgba(255, 215, 0, 0.4);
z-index: 2;
position: relative;
background-color: #f5f5f5;
@@ -38,7 +38,7 @@
width: 220rpx;
height: 220rpx;
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 181, 253, 0.2) 0%, transparent 70%);
background: radial-gradient(circle, rgba(255, 215, 0, 0.2) 0%, transparent 70%);
z-index: 1;
animation: halo-pulse 2s infinite ease-in-out;
}

View File

@@ -0,0 +1,139 @@
import request from '../../utils/request.js';
import { getOrderStatusText } from '../../utils/api-helper.js';
const PAGE_SIZE = 15;
const TYPE_CONFIG = {
commission: { title: '佣金记录', statuses: [3, 8] },
settle: { title: '结算记录', statuses: [3] },
withdraw: { title: '提现记录', api: 'withdraw' },
deduct: { title: '扣款记录', api: 'deduct' },
};
Page({
data: {
pageTitle: '交易明细',
type: 'commission',
statusBar: 20,
list: [],
page: 1,
hasMore: true,
loading: false,
refreshing: false,
},
onLoad(options) {
const sys = wx.getSystemInfoSync();
const type = (options.type || 'commission').trim();
const cfg = TYPE_CONFIG[type] || TYPE_CONFIG.commission;
this.setData({
type,
pageTitle: cfg.title,
statusBar: sys.statusBarHeight || 20,
});
this.loadList(true);
},
goBack() {
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/fighter/fighter' }) });
},
onRefresh() {
this.setData({ refreshing: true });
this.loadList(true).finally(() => this.setData({ refreshing: false }));
},
onLoadMore() {
if (this.data.hasMore && !this.data.loading) {
this.loadList(false);
}
},
async loadList(refresh) {
const { type, loading } = this.data;
if (loading) return;
const page = refresh ? 1 : this.data.page;
this.setData({ loading: true });
try {
let rows = [];
let hasMore = false;
if (type === 'withdraw') {
const res = await request({
url: '/yonghu/tixianjiluhq',
method: 'POST',
data: { page, limit: PAGE_SIZE },
});
const body = res?.data;
if (body && (body.code === 200 || body.code === 0)) {
const raw = body.data?.list || [];
rows = raw.map((item, idx) => ({
id: item.id || item.jilu_id || `${page}-${idx}`,
title: item.beizhu || item.status_text || item.zhuangtai_text || '提现',
time: item.creat_time || item.create_time || item.tijiao_shijian || '',
amount: `${item.jine || item.amount || item.tixian_jine || '0.00'}`,
minus: true,
}));
hasMore = raw.length >= PAGE_SIZE;
}
} else if (type === 'deduct') {
const res = await request({
url: '/yonghu/dsfklbhq',
method: 'POST',
data: { page, page_size: PAGE_SIZE, zhuangtai: 0 },
});
const body = res?.data;
if (body && (body.code === 200 || body.code === 0)) {
const raw = body.data?.list || [];
rows = raw.map((item, idx) => ({
id: item.fadan_id || item.id || `${page}-${idx}`,
title: item.yuanyin || item.beizhu || item.fakuan_yuanyin || '扣款',
time: item.creat_time || item.create_time || '',
amount: `${item.jine || item.fakuan_jine || item.fakuanjine || '0.00'}`,
minus: true,
}));
hasMore = raw.length >= PAGE_SIZE;
}
} else {
const cfg = TYPE_CONFIG[type] || TYPE_CONFIG.commission;
const res = await request({
url: '/dingdan/dshqdingdan',
method: 'POST',
data: {
zhuangtai_list: cfg.statuses,
page,
page_size: PAGE_SIZE,
},
});
const body = res?.data;
if (body && (body.code === 200 || body.code === 0)) {
const raw = body.data?.list || [];
rows = raw.map((item) => {
const amt = item.jine || item.dashou_fencheng || '0.00';
const brief = (item.jieshao || '').trim();
const title = brief
? (brief.length > 18 ? `${brief.slice(0, 18)}` : brief)
: `订单 ${item.dingdan_id || ''}`;
return {
id: item.dingdan_id,
title,
time: item.wancheng_shijian || item.jiesuan_shijian || item.creat_time || getOrderStatusText(item.zhuangtai),
amount: `${amt}`,
minus: false,
};
});
hasMore = body.data?.has_more ?? raw.length >= PAGE_SIZE;
}
}
const list = refresh ? rows : [...this.data.list, ...rows];
this.setData({
list,
page: refresh ? 2 : page + 1,
hasMore,
});
} catch (e) {
console.error('ledger load fail', e);
} finally {
this.setData({ loading: false });
}
},
});

View File

@@ -0,0 +1,4 @@
{
"navigationStyle": "custom",
"usingComponents": {}
}

View File

@@ -0,0 +1,20 @@
<view class="ledger-page safe-area">
<view class="status-bar" style="height:{{statusBar}}px"></view>
<view class="nav-bar flexb">
<text class="back" bindtap="goBack"></text>
<text class="nav-title">{{pageTitle}}</text>
<text class="back placeholder"></text>
</view>
<scroll-view scroll-y class="ledger-scroll" refresher-enabled refresher-triggered="{{refreshing}}" bindrefresherrefresh="onRefresh" bindscrolltolower="onLoadMore">
<view wx:if="{{loading && list.length === 0}}" class="empty-box">加载中...</view>
<view wx:elif="{{list.length === 0}}" class="empty-box">暂无记录</view>
<view wx:for="{{list}}" wx:key="id" class="list-item flexb">
<view class="item-main">
<view class="row-t">{{item.title}}</view>
<view class="row-d">{{item.time}}</view>
</view>
<text class="row-r {{item.minus ? 'minus' : ''}}">{{item.amount}}</text>
</view>
<view wx:if="{{list.length > 0 && !hasMore}}" class="load-tip">— 已加载全部 —</view>
</scroll-view>
</view>

View File

@@ -0,0 +1,75 @@
@import '../../styles/xym-layout.wxss';
.ledger-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.nav-bar {
padding: 0 24rpx 16rpx;
}
.back {
width: 60rpx;
font-size: 48rpx;
color: #333;
line-height: 1;
}
.back.placeholder {
visibility: hidden;
}
.nav-title {
font-size: 34rpx;
font-weight: 700;
color: #222;
}
.ledger-scroll {
flex: 1;
height: 0;
padding: 0 24rpx;
box-sizing: border-box;
}
.list-item {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 16rpx;
}
.row-t {
font-size: 28rpx;
color: #222;
font-weight: 600;
}
.row-d {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.row-r {
font-size: 30rpx;
font-weight: 700;
color: #492f00;
flex-shrink: 0;
margin-left: 16rpx;
}
.row-r.minus {
color: #ff4c45;
}
.empty-box,
.load-tip {
text-align: center;
color: #999;
font-size: 26rpx;
padding: 80rpx 0;
}

View File

@@ -387,7 +387,7 @@ Page({
},
initCOSClient(tokenData) {
const COS = require('../../utils/cos-wx-sdk-v5.js')
const COS = require('../../utils/cos-wx-sdk-v5.min.js')
const credentials = tokenData.credentials || tokenData
const bucket = tokenData.bucket || 'julebu-1361527063'
const region = tokenData.region || 'ap-shanghai'

View File

@@ -296,9 +296,9 @@
}
.zhuangtai-shensuzhong {
background: rgba(147, 51, 234, 0.2);
border-color: #9333ea;
box-shadow: 0 0 8rpx rgba(147, 51, 234, 0.2);
background: rgba(255, 165, 0, 0.2);
border-color: #FFA500;
box-shadow: 0 0 8rpx rgba(255, 165, 0, 0.2);
}
.zhuangtai-weizhi {
@@ -657,9 +657,9 @@
}
.cbtn-shensu {
background: rgba(147, 51, 234, 0.1);
border-color: #9333ea;
box-shadow: 0 0 12rpx rgba(147, 51, 234, 0.2);
background: rgba(255, 165, 0, 0.1);
border-color: #FFA500;
box-shadow: 0 0 12rpx rgba(255, 165, 0, 0.2);
}
.cbtn-shensu-disabled {

View File

@@ -1,7 +1,8 @@
// pages/fighter-order-detail/fighter-order-detail.js - 【最终版:提交前公告弹窗 + 修改图片至少保留一张】
const app = getApp()
import request from '../../utils/request.js'
const COS = require('../../utils/cos-wx-sdk-v5.js')
import connectionManager from '../../utils/xiaoxilj.js'
const COS = require('../../utils/cos-wx-sdk-v5.min.js')
Page({
data: {
@@ -30,8 +31,8 @@ Page({
laoban_id: ''
},
zhuangtaiYanseMap: {
1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#F3E8FF',
5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#F5F3FF'
1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#FFF3E0',
5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#FFF8E1'
},
zhuangtaiWenziMap: {
1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中',
@@ -284,7 +285,7 @@ Page({
});
},
// ===== 联系老板(订单群 groupId 即订单号,与商家端 sjddxq 一致 =====
// ===== 联系老板/商家(配对群 group_Ds{打手}_Sj/Boss{对方} =====
goToChatWithBoss() {
const uid = wx.getStorageSync('uid')
if (!uid) {
@@ -298,37 +299,22 @@ Page({
return
}
const targetUserId = 'Ds' + uid
const connected = wx.goEasy && wx.goEasy.getConnectionStatus && wx.goEasy.getConnectionStatus() === 'connected'
const currentUserId = wx.goEasy?.im?.userId
if (!connected || currentUserId !== targetUserId) {
if (app.ensureConnection) app.ensureConnection()
wx.showToast({ title: '连接中,请稍后再试', icon: 'none' })
return
}
const fadanPingtai = this.data.jibenShuju.fadanpingtai || 0
const partnerUid = fadanPingtai === 2
? (this.data.xiangxiShuju.shangjia_id || '')
: (this.data.xiangxiShuju.laoban_id || '')
const orderIdStr = String(orderId)
const groupName = (this.data.jibenShuju.nicheng || '订单') + '的订单群'
const isCross = this.data.jibenShuju.fadanpingtai || 0
wx.goEasy.im.subscribeGroup({
groupIds: [orderIdStr],
onSuccess: () => {
const param = {
groupId: orderIdStr,
orderId: orderIdStr,
groupName,
groupAvatar: '',
isCross,
}
wx.navigateTo({
url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)),
})
},
onFailed: (err) => {
console.error('订阅群组失败', err)
wx.showToast({ title: '连接群聊失败', icon: 'none' })
},
connectionManager.connectToGroupChat({
identityType: 'dashou',
userId: 'Ds' + uid,
orderId: orderId,
partnerUid,
fadanPingtai,
groupName: (this.data.jibenShuju.nicheng || '订单') + '的订单群',
isCross: this.data.jibenShuju.fadanpingtai || 0,
}).catch((err) => {
console.error('跳转群聊失败', err)
wx.showToast({ title: '跳转聊天失败', icon: 'none' })
})
},

View File

@@ -179,7 +179,7 @@
<text class="item-label">申请状态:</text>
<text
class="item-value"
style="color: {{xiangxiShuju.chufa_zhuangtai == 0 ? '#9333ea' : (xiangxiShuju.chufa_zhuangtai == 1 ? '#4CAF50' : '#FF4444')}}"
style="color: {{xiangxiShuju.chufa_zhuangtai == 0 ? '#FF9800' : (xiangxiShuju.chufa_zhuangtai == 1 ? '#4CAF50' : '#FF4444')}}"
>
{{xiangxiShuju.chufa_zhuangtai == 0 ? '审核中' : (xiangxiShuju.chufa_zhuangtai == 1 ? '处罚成功' : '处罚驳回')}}
</text>

View File

@@ -432,9 +432,9 @@
/* 🆕 新增:联系老板按钮 */
.boss-btn {
background-color: #9333ea;
background-color: #ff9800;
color: #ffffff;
box-shadow: 0 4rpx 15rpx rgba(147, 51, 234, 0.3);
box-shadow: 0 4rpx 15rpx rgba(255, 152, 0, 0.3);
}
/* 🆕 新增:修改按钮(黑色) */

View File

@@ -19,7 +19,7 @@ Page(createPage({
statusList: [
{ name: '全部', key: 'all', zhuangtaiList: [], color: '#333333' },
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#9333ea' },
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
{ name: '已退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' },
@@ -49,18 +49,43 @@ Page(createPage({
onLoad() {
wx.setNavigationBarTitle({ title: '我的接单' });
this.loadShangpinLeixing();
this.loadShangpinLeixing().then(() => {
if (this.data.xuanzhongLeixingId) {
return this.loadCurrentStatusOrders(true);
}
}).finally(() => {
this._ordersSessionReady = true;
this._skipShowRefresh = true;
});
this.registerNotificationComponent();
},
onShow() {
this.registerNotificationComponent();
const app = getApp();
const filterKey = app.globalData._fighterOrdersStatusKey;
if (filterKey) {
app.globalData._fighterOrdersStatusKey = null;
if (this.data.statusList.some((s) => s.key === filterKey)) {
this.setData({ currentStatusKey: filterKey });
}
if (this.data.xuanzhongLeixingId) {
this.loadCurrentStatusOrders(true);
}
}
if (wx.getStorageSync('uid')) {
reconnectForRole('dashou');
if (app.startImWhenReady) app.startImWhenReady();
}
if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) {
this.loadCurrentStatusOrders(true);
if (this._skipShowRefresh) {
this._skipShowRefresh = false;
return;
}
if (this._ordersSessionReady && this.data.xuanzhongLeixingId && !this._silentRefreshRunning) {
this._silentRefreshRunning = true;
this.loadCurrentStatusOrders(true, true).finally(() => {
this._silentRefreshRunning = false;
});
}
},
@@ -141,17 +166,21 @@ Page(createPage({
},
// 加载订单(核心)
async loadCurrentStatusOrders(isRefresh = false) {
async loadCurrentStatusOrders(isRefresh = false, silent = false) {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading) return;
if (tabData.isLoading && !silent) return;
if (silent && this._listRequesting) return;
const page = isRefresh ? 1 : tabData.page;
if (!silent) {
this.setData({
[`dsDingdanShuju.${key}.isLoading`]: true,
isLoading: true,
isLoadingMore: !isRefresh
isLoadingMore: !isRefresh,
});
}
this._listRequesting = true;
try {
const apiUrl = this.data.orderType === 'peihu'
@@ -208,7 +237,10 @@ Page(createPage({
}
} catch (err) {
console.error('加载订单失败', err);
if (!silent) {
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
}
if (!silent) {
this.setData({
[`dsDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
@@ -217,6 +249,9 @@ Page(createPage({
});
this.refreshCurrentListView();
}
} finally {
this._listRequesting = false;
}
},
refreshCurrentListView() {

View File

@@ -1,6 +1,7 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
"global-notification": "/components/global-notification/global-notification",
"tab-bar": "/tab-bar/index"
},
"navigationBarTitleText": "我的接单",
"enablePullDownRefresh": false,

View File

@@ -347,3 +347,4 @@ page {
}
@import '../../styles/dashou-xym-orders-theme.wxss';
@import '../../styles/dashou-xym-orders-page.wxss';

View File

@@ -1,119 +1,121 @@
/**
* 鎺掕姒? * 榛樿 POST /yonghu/phbhqsj
* 鏄熶箣鐣?xzj) POST /yonghu/xzjphbhqsj锛堥個璇蜂汉鏁版帓搴忕瓑涓撶敤瑙勫垯锛? */
/**
* 排行榜
* 默认 POST /yonghu/phbhqsj
* 星之界(xzj) POST /yonghu/xzjphbhqsj邀请人数排序等专用规则
*/
import request from '../../utils/request.js'
import { resolveAvatarUrl, getDefaultAvatarUrl } from '../../utils/avatar.js'
import { CLUB_ID } from '../../config/club-config.js'
const IS_XZJ = CLUB_ID === 'xzj'
const RANK_API = IS_XZJ ? '/yonghu/xzjphbhqsj' : '/yonghu/phbhqsj'
const CLOSED_DATES = ['鏄ㄦ棩', '涓婂懆', '涓婃湀']
const CLOSED_DATES = ['昨日', '上周', '上月']
const ROLE_LIST = [
{ key: 'dashou', label: '鎺ュ崟鍛? },
{ key: 'guanshi', label: '' },
{ key: 'zuzhang', label: '缁勯暱' },
{ key: 'shangjia', label: '鍟嗗' },
{ key: 'dashou', label: '接单员' },
{ key: 'guanshi', label: '管事' },
{ key: 'zuzhang', label: '组长' },
{ key: 'shangjia', label: '商家' },
]
const DATE_LIST = [
{ key: '浠婃棩', label: '浠婃棩' },
{ key: '鏄ㄦ棩', label: '鏄ㄦ棩' },
{ key: '', label: '' },
{ key: '涓婂懆', label: '涓婂懆' },
{ key: '', label: '' },
{ key: '涓婃湀', label: '涓婃湀' },
{ key: '鎬绘', label: '鎬绘' },
{ key: '今日', label: '今日' },
{ key: '昨日', label: '昨日' },
{ key: '本周', label: '本周' },
{ key: '上周', label: '上周' },
{ key: '本月', label: '本月' },
{ key: '上月', label: '上月' },
{ key: '总榜', label: '总榜' },
]
const ROLE_META = {
dashou: {
title: '鎺ュ崟鍛樻帓琛屾',
title: '接单员排行榜',
sortField: 'chengjiao_zonge',
sortLabel: '鍒嗙孩鎬婚',
sortLabel: '分红总额',
mainType: 'money',
metrics: [
{ field: 'jiedan_zongliang', label: '鎺ュ崟閲?, type: 'int' },
{ field: 'jiedan_zonge', label: '鎺ュ崟棰?, type: 'money' },
{ field: 'chengjiao_zongliang', label: '鎴愪氦閲?, type: 'int' },
{ field: 'jiedan_zongliang', label: '接单量', type: 'int' },
{ field: 'jiedan_zonge', label: '接单额', type: 'money' },
{ field: 'chengjiao_zongliang', label: '成交量', type: 'int' },
],
},
guanshi: {
title: '绠′簨鎺掕姒?,
title: '管事排行榜',
sortField: 'shouru_zonge',
sortLabel: '鏀跺叆鎬婚',
sortLabel: '收入总额',
mainType: 'money',
metrics: [
{ field: 'yaoqing_dashou_shu', label: '?, type: 'int' },
{ field: 'chongzhi_dashou_shu', label: '鍏呭€?, type: 'int' },
{ field: 'yaoqing_dashou_shu', label: '邀请', type: 'int' },
{ field: 'chongzhi_dashou_shu', label: '充值', type: 'int' },
],
},
zuzhang: {
title: '缁勯暱鎺掕?,
title: '组长排行榜',
sortField: 'shouru_zonge',
sortLabel: '鏀跺叆鎬婚',
sortLabel: '收入总额',
mainType: 'money',
metrics: [
{ field: 'yaoqing_guanshi_shu', label: '閭€璇?, type: 'int' },
{ field: 'fenyong_jine', label: '鍒嗕剑', type: 'money' },
{ field: 'yaoqing_guanshi_shu', label: '邀请', type: 'int' },
{ field: 'fenyong_jine', label: '分佣', type: 'money' },
],
},
shangjia: {
title: '鍟嗗鎺掕?,
title: '商家排行榜',
sortField: 'jiesuan_jine',
sortLabel: '鎴愪氦鎬婚',
sortLabel: '成交总额',
mainType: 'money',
metrics: [
{ field: 'jiesuan_dingdan_shu', label: '鎴愪氦閲?, type: 'int' },
{ field: 'paifa_dingdan_shu', label: '娲惧崟閲?, type: 'int' },
{ field: 'paifa_jine', label: '娲惧崟棰?, type: 'money' },
{ field: 'jiesuan_dingdan_shu', label: '成交量', type: 'int' },
{ field: 'paifa_dingdan_shu', label: '派单量', type: 'int' },
{ field: 'paifa_jine', label: '派单额', type: 'money' },
],
},
}
/** 鏄熶箣鐣屼笓鐢ㄥ睍绀轰笌鎺掑簭瀛楁 */
/** 星之界专用展示与排序字段 */
const XZJ_ROLE_META = {
dashou: {
title: '鎺ュ崟鍛樻帓琛屾',
title: '接单员排行榜',
sortField: 'chengjiao_zonge',
sortLabel: '鎴愪氦閲戦',
sortLabel: '成交金额',
mainType: 'money',
metrics: [
{ field: 'jiedan_zongliang', label: '鎺ュ崟閲?, type: 'int' },
{ field: 'jiedan_zonge', label: '鎺ュ崟棰?, type: 'money' },
{ field: 'chengjiao_zongliang', label: '鎴愪氦閲?, type: 'int' },
{ field: 'jiedan_zongliang', label: '接单量', type: 'int' },
{ field: 'jiedan_zonge', label: '接单额', type: 'money' },
{ field: 'chengjiao_zongliang', label: '成交量', type: 'int' },
],
},
guanshi: {
title: '绠′簨鎺掕姒?,
title: '管事排行榜',
sortField: 'chongzhi_dashou_shu',
sortLabel: '鏈夋晥浜烘暟',
sortLabel: '有效人数',
mainType: 'int',
metrics: [
{ field: 'wuxiao_yaoqing_dashou_shu', label: '鏃犳晥閭?, type: 'int' },
{ field: 'yaoqing_dashou_shu', label: '閭€璇蜂汉鏁?, type: 'int' },
{ field: 'shouru_zonge', label: '鏀剁泭鎬婚', type: 'money' },
{ field: 'wuxiao_yaoqing_dashou_shu', label: '无效邀请', type: 'int' },
{ field: 'yaoqing_dashou_shu', label: '邀请人数', type: 'int' },
{ field: 'shouru_zonge', label: '收益总额', type: 'money' },
],
},
zuzhang: {
title: '缁勯暱鎺掕?,
title: '组长排行榜',
sortField: 'shouru_zonge',
sortLabel: '鏀剁泭鎬婚',
sortLabel: '收益总额',
mainType: 'money',
metrics: [
{ field: 'wuxiao_yaoqing_guanshi_shu', label: '鏃犳晥閭€璇?, type: 'int' },
{ field: 'youxiao_guanshi_shu', label: '鏈夋晥浜烘暟', type: 'int' },
{ field: 'yaoqing_guanshi_shu', label: '璇蜂汉鏁?, type: 'int' },
{ field: 'wuxiao_yaoqing_guanshi_shu', label: '无效邀请', type: 'int' },
{ field: 'youxiao_guanshi_shu', label: '有效人数', type: 'int' },
{ field: 'yaoqing_guanshi_shu', label: '邀请人数', type: 'int' },
],
},
shangjia: {
title: '鍟嗗鎺掕姒?,
title: '商家排行榜',
sortField: 'paifa_jine',
sortLabel: '娲惧崟娴佹按',
sortLabel: '派单流水',
mainType: 'money',
metrics: [
{ field: 'paifa_dingdan_shu', label: '娲惧崟閲?, type: 'int' },
{ field: 'paifa_jine', label: '娲惧崟娴佹按', type: 'money' },
{ field: 'paifa_dingdan_shu', label: '派单量', type: 'int' },
{ field: 'paifa_jine', label: '派单流水', type: 'money' },
],
},
}
@@ -143,11 +145,12 @@ Page({
roleList: ROLE_LIST,
dateList: DATE_LIST,
currentRole: 'dashou',
currentDate: '浠婃棩',
currentDate: '今日',
fanwei: 'club',
showJituanScope: false, // 涓存椂闅愯棌闆嗗洟鎬绘锛屾仮澶嶆椂鏀?true 骞惰皟鏁?fanwei 榛樿鍊? myClubId: CLUB_ID || 'xq',
showJituanScope: false, // 临时隐藏集团总榜,恢复时改 true 并调整 fanwei 默认值
myClubId: CLUB_ID || 'xq',
roleMeta: ROLE_META.dashou,
sortLabel: '鍒嗙孩鎬婚',
sortLabel: '分红总额',
isLoading: true,
rankList: [],
topThree: [],
@@ -183,7 +186,7 @@ Page({
})
},
/** 涓?app.js 涓€鑷达細ossImageUrl + morentouxiang锛岄厤缃紓姝ュ姞杞藉悗 onShow 鍐嶅悓姝?*/
/** app.js 一致:ossImageUrl + morentouxiang,配置异步加载后 onShow 再同步 */
syncDefaultAvatar() {
const def = getDefaultAvatar()
if (def && def !== this.data.defaultAvatar) {
@@ -193,7 +196,7 @@ Page({
}
},
/** 閰嶇疆鏅氫簬椤甸潰鍔犺浇鏃讹紝琛ュ叏绌哄ご鍍?*/
/** 配置晚于页面加载时,补全空头像 */
ensureAvatars() {
const def = getDefaultAvatar()
if (!def) return
@@ -223,7 +226,7 @@ Page({
applyRole(role, reload = true) {
const meta = ACTIVE_ROLE_META[role]
wx.setNavigationBarTitle({ title: '鎬绘帓琛屾' })
wx.setNavigationBarTitle({ title: '总排行榜' })
this.setData({ currentRole: role, roleMeta: meta, sortLabel: meta.sortLabel })
if (reload) this.fetchRankList()
},
@@ -252,19 +255,19 @@ Page({
onShowRules() {
const r = this.data.rewardInfo
if (!r || !r.tiers || !r.tiers.length) {
wx.showToast({ title: '鏆傛棤濂栧姳瑙勫垯', icon: 'none' })
wx.showToast({ title: '暂无奖励规则', icon: 'none' })
return
}
const lines = r.tiers.map((t) => {
const label = t.label || `绗?{t.rank_from}${t.rank_to > t.rank_from ? '-' + t.rank_to : ''}鍚峘
return `${label}锛毬?{t.amount}`
const label = t.label || `${t.rank_from}${t.rank_to > t.rank_from ? '-' + t.rank_to : ''}`
return `${label}:¥${t.amount}`
})
const tip = r.period_status === 'open' ? '锛堟湰鏈熻繘琛屼腑锛岀粨鏉熷悗鍙鍙栵級' : ''
const tip = r.period_status === 'open' ? '(本期进行中,结束后可领取)' : ''
wx.showModal({
title: `${r.title || '鎺掕姒滃鍔?}${tip}`,
title: `${r.title || '排行榜奖励'}${tip}`,
content: [
`淇变箰閮細${r.club_id || this.data.myClubId}`,
`鎺掑簭锛?{r.sort_label || ''}`,
`俱乐部:${r.club_id || this.data.myClubId}`,
`排序:${r.sort_label || ''}`,
...lines,
r.description || '',
].filter(Boolean).join('\n'),
@@ -284,13 +287,13 @@ Page({
})
const body = res?.data || {}
if (body.code === 200) {
wx.showToast({ title: `宸查鍙?楼${claim.amount}`, icon: 'success' })
wx.showToast({ title: `已领取 ¥${claim.amount}`, icon: 'success' })
await this.fetchRewardInfo()
} else {
wx.showToast({ title: body.msg || '棰嗗彇澶辫触', icon: 'none' })
wx.showToast({ title: body.msg || '领取失败', icon: 'none' })
}
} catch (e) {
wx.showToast({ title: '棰嗗彇澶辫触', icon: 'none' })
wx.showToast({ title: '领取失败', icon: 'none' })
} finally {
this.setData({ claimLoading: false })
}
@@ -314,7 +317,7 @@ Page({
const meta = ACTIVE_ROLE_META[role]
const app = getApp()
const uid = raw.yonghuid || raw.uid || ''
const nicheng = raw.nicheng || raw.nick || (role === 'dashou' ? '鎵撴墜' : role === 'guanshi' ? '' : role === 'zuzhang' ? '缁勯暱' : role === 'shangjia' ? '鍟嗗' : '鐢ㄦ埛')
const nicheng = raw.nicheng || raw.nick || (role === 'dashou' ? '打手' : role === 'guanshi' ? '管事' : role === 'zuzhang' ? '组长' : role === 'shangjia' ? '商家' : '用户')
const def = getDefaultAvatar(app)
const avatar = buildAvatar(raw.touxiang || raw.avatar, app) || def
const metrics = meta.metrics.map((m) => {
@@ -345,7 +348,7 @@ Page({
clubName: raw.club_name || raw.club_id || '',
rewardAmount: raw.reward_amount || 0,
mainType,
mainPrefix: mainType === 'money' ? '' : '',
mainPrefix: mainType === 'money' ? '¥' : '',
mainValue,
mainLabel: (IS_XZJ ? meta.sortLabel : (raw.metric_label || meta.sortLabel)),
metrics,
@@ -387,7 +390,7 @@ Page({
restList: merged.slice(3),
})
} catch (e) {
console.warn('濂栧姳淇伅鍔犺浇澶辫触', e)
console.warn('奖励信息加载失败', e)
}
},
@@ -432,7 +435,7 @@ Page({
if (!Array.isArray(list)) list = []
const role = currentRole
// 鍚嶆椤哄簭瀹屽叏娌跨敤鍚庣杩斿洖锛屽墠绔笉鍋?sort / 閲嶆帓
// 名次顺序完全沿用后端返回,前端不做 sort / 重排
let normalized = list.slice(0, 50).map((item, i) => this.normalizeItem(item, i, role))
if (rewardFromRank) {
normalized = this.applyRewardToList(normalized, rewardFromRank, 'club')
@@ -450,8 +453,8 @@ Page({
await this.fetchRewardInfo()
}
} catch (e) {
console.error('鎺掕姒滃姞杞藉け璐?, e)
wx.showToast({ title: '鍔犺浇澶辫触', icon: 'none' })
console.error('排行榜加载失败', e)
wx.showToast({ title: '加载失败', icon: 'none' })
this.setData({ rankList: [], topThree: [], restList: [], isEmpty: true, isLoading: false })
}
},

View File

@@ -2,10 +2,10 @@
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
},
"navigationBarTitleText": "排行榜",
"navigationBarBackgroundColor": "#0d1117",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "排行榜",
"navigationBarBackgroundColor": "#FFE566",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"backgroundColor": "#0d1117",
"backgroundTextStyle": "light"
"backgroundColor": "#FFE566",
"backgroundTextStyle": "dark"
}

View File

@@ -1,30 +1,47 @@
<view class="rank-page">
<image class="page-bg" src="{{imgUrls.pageBg}}" mode="aspectFill" />
<view class="top-bar">
<view class="top-row">
<text class="page-title">{{roleMeta.title}}</text>
<view class="refresh-wrap" bindtap="onRefreshTap">
<image class="refresh-ico" src="{{imgUrls.refreshIcon}}" mode="aspectFit" />
</view>
</view>
<scroll-view scroll-x class="role-scroll" enhanced show-scrollbar="{{false}}">
<view class="role-line">
<!-- 顶区:黄底 + 白 Tab + 时间筛选 -->
<view class="header-zone">
<view class="role-tabs">
<view
wx:for="{{roleList}}" wx:key="key"
class="role-chip {{currentRole === item.key ? 'on' : ''}}"
class="role-tab {{currentRole === item.key ? 'active' : ''}}"
data-role="{{item.key}}" bindtap="onRoleTap"
>{{item.label}}</view>
>
<text class="role-tab-text">{{item.label}}</text>
<view class="role-tab-line" wx:if="{{currentRole === item.key}}"></view>
</view>
</scroll-view>
<view class="date-grid">
</view>
<scroll-view scroll-x class="date-scroll" enhanced show-scrollbar="{{false}}">
<view class="date-line">
<view
wx:for="{{dateList}}" wx:key="key"
class="date-chip {{currentDate === item.key ? 'on' : ''}}"
class="date-tab {{currentDate === item.key ? 'active' : ''}}"
data-date="{{item.key}}" bindtap="onDateTap"
>{{item.label}}</view>
</view>
</scroll-view>
<view class="scope-row">
<!-- 临时隐藏集团总榜,恢复时取消 wx:if 注释 -->
<view wx:if="{{showJituanScope}}" class="scope-tab {{fanwei === 'jituan' ? 'active' : ''}}" data-fanwei="jituan" bindtap="onScopeTap">集团总榜</view>
<view class="scope-tab {{fanwei === 'club' ? 'active' : ''}}" data-fanwei="club" bindtap="onScopeTap">本俱乐部榜</view>
<view class="refresh-btn" bindtap="onRefreshTap">
<image class="refresh-ico" src="{{imgUrls.refreshIcon}}" mode="aspectFit" />
</view>
</view>
<text class="sort-tip">{{currentDate}} · 按{{sortLabel}} · TOP50</text>
<view class="reward-banner" wx:if="{{rewardInfo && rewardInfo.scheme_enabled}}" bindtap="onShowRules">
<view class="reward-banner-main">
<text class="reward-tag">🏆 {{rewardInfo.title || '排行榜奖励'}}</text>
<text class="reward-club">俱乐部 {{rewardInfo.club_id || myClubId}}</text>
<text class="reward-status" wx:if="{{rewardInfo.period_status === 'open'}}">进行中 · 结束后可领</text>
<text class="reward-status pending" wx:elif="{{rewardInfo.my_claim}}">待领取 ¥{{rewardInfo.my_claim.amount}}</text>
<text class="reward-status claimed" wx:elif="{{rewardInfo.my_claimed}}">已领取 ¥{{rewardInfo.my_claimed.amount}}</text>
</view>
<text class="reward-link">规则 </text>
</view>
</view>
<view class="state-wrap" wx:if="{{isLoading}}">
@@ -38,97 +55,85 @@
</view>
<scroll-view wx:else scroll-y class="body-scroll" enhanced show-scrollbar="{{false}}">
<!-- 前三名领奖台 -->
<view class="podium-zone" wx:if="{{topThree.length > 0}}">
<view class="podium-row">
<!-- 前三名 · 纯 CSS 领奖台 -->
<view class="podium-panel" wx:if="{{topThree.length > 0}}">
<view class="podium-cols">
<!-- 榜二 -->
<view class="p-col p-second" wx:if="{{topThree[1]}}">
<view class="p-badge badge-silver"><text>2</text></view>
<view class="p-avatar-wrap wrap-silver">
<view class="podium-col col-2" wx:if="{{topThree[1]}}">
<view class="crown crown-silver">👑</view>
<view class="avatar-ring ring-silver">
<image class="p-avatar" src="{{topThree[1].avatar || defaultAvatar}}" mode="aspectFill" data-zone="top" data-index="1" binderror="onAvatarError" />
</view>
<text class="p-name">{{topThree[1].nicheng}}</text>
<text class="p-uid">ID {{topThree[1].uid}}</text>
<view class="p-amount">
<text class="p-money">¥{{topThree[1].mainValue}}</text>
<text class="p-label">{{topThree[1].mainLabel}}</text>
</view>
<text class="p-amount">{{topThree[1].mainPrefix}}{{topThree[1].mainValue}}</text>
<view class="p-metrics">
<text class="p-metric" wx:for="{{topThree[1].metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
</view>
<view class="p-pedestal pedestal-silver"></view>
<text class="p-reward" wx:if="{{topThree[1].rewardAmount > 0}}">奖 ¥{{topThree[1].rewardAmount}}</text>
<view class="pedestal pedestal-2"></view>
</view>
<!-- 榜一 -->
<view class="p-col p-first" wx:if="{{topThree[0]}}">
<view class="p-badge badge-first"><text>1</text></view>
<view class="p-avatar-wrap wrap-first">
<view class="podium-col col-1" wx:if="{{topThree[0]}}">
<view class="crown crown-gold">👑</view>
<view class="avatar-ring ring-gold">
<image class="p-avatar p-avatar-lg" src="{{topThree[0].avatar || defaultAvatar}}" mode="aspectFill" data-zone="top" data-index="0" binderror="onAvatarError" />
</view>
<text class="p-name p-name-first">{{topThree[0].nicheng}}</text>
<text class="p-uid">ID {{topThree[0].uid}}</text>
<view class="p-amount">
<text class="p-money p-money-first">¥{{topThree[0].mainValue}}</text>
<text class="p-name p-name-lg">{{topThree[0].nicheng}}</text>
<text class="p-label">{{topThree[0].mainLabel}}</text>
</view>
<text class="p-amount p-amount-lg">{{topThree[0].mainPrefix}}{{topThree[0].mainValue}}</text>
<view class="p-metrics">
<text class="p-metric" wx:for="{{topThree[0].metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
</view>
<view class="p-pedestal pedestal-first"></view>
<text class="p-reward" wx:if="{{topThree[0].rewardAmount > 0}}">奖 ¥{{topThree[0].rewardAmount}}</text>
<view class="pedestal pedestal-1"></view>
</view>
<!-- 榜三 -->
<view class="p-col p-third" wx:if="{{topThree[2]}}">
<view class="p-badge badge-bronze"><text>3</text></view>
<view class="p-avatar-wrap wrap-bronze">
<view class="podium-col col-3" wx:if="{{topThree[2]}}">
<view class="crown crown-bronze">👑</view>
<view class="avatar-ring ring-bronze">
<image class="p-avatar" src="{{topThree[2].avatar || defaultAvatar}}" mode="aspectFill" data-zone="top" data-index="2" binderror="onAvatarError" />
</view>
<text class="p-name">{{topThree[2].nicheng}}</text>
<text class="p-uid">ID {{topThree[2].uid}}</text>
<view class="p-amount">
<text class="p-money">¥{{topThree[2].mainValue}}</text>
<text class="p-label">{{topThree[2].mainLabel}}</text>
</view>
<text class="p-amount">{{topThree[2].mainPrefix}}{{topThree[2].mainValue}}</text>
<view class="p-metrics">
<text class="p-metric" wx:for="{{topThree[2].metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
</view>
<view class="p-pedestal pedestal-bronze"></view>
<text class="p-reward" wx:if="{{topThree[2].rewardAmount > 0}}">奖 ¥{{topThree[2].rewardAmount}}</text>
<view class="pedestal pedestal-3"></view>
</view>
</view>
</view>
<!-- 4-50 名 -->
<view class="list-wrap" wx:if="{{restList.length > 0}}">
<view class="list-title-row">
<text class="list-title">完整榜单</text>
<text class="list-range">第 4 - {{rankList.length}} 名</text>
</view>
<view class="list-card" wx:for="{{restList}}" wx:key="mingci">
<image class="list-card-bg" src="{{imgUrls.cardBg}}" mode="aspectFill" />
<view class="list-card-body">
<view class="lc-rank"><text>{{item.mingci}}</text></view>
<image class="lc-avatar" src="{{item.avatar || defaultAvatar}}" mode="aspectFill" data-zone="rest" data-index="{{index}}" binderror="onAvatarError" />
<view class="lc-info">
<text class="lc-name">{{item.nicheng}}</text>
<text class="lc-uid">ID {{item.uid}}</text>
<view class="lc-tags">
<text class="lc-tag" wx:for="{{item.metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
</view>
</view>
<view class="lc-score">
<text class="lc-money">¥{{item.mainValue}}</text>
<text class="lc-label">{{item.mainLabel}}</text>
<!-- 4 名以后列表 -->
<view class="list-panel" wx:if="{{restList.length > 0}}">
<view class="list-row" wx:for="{{restList}}" wx:key="mingci">
<text class="li-rank">{{item.mingci}}</text>
<image class="li-avatar" src="{{item.avatar || defaultAvatar}}" mode="aspectFill" data-zone="rest" data-index="{{index}}" binderror="onAvatarError" />
<text class="li-name">{{item.nicheng}}</text>
<view class="li-right">
<text class="li-amount">{{item.mainPrefix}}{{item.mainValue}}</text>
<text class="li-label">{{item.mainLabel}}</text>
<view class="li-tags">
<text class="li-tag" wx:for="{{item.metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
</view>
<text class="li-reward" wx:if="{{item.rewardAmount > 0}}">奖¥{{item.rewardAmount}}</text>
</view>
</view>
</view>
<view class="bottom-gap"></view>
</scroll-view>
<view class="claim-bar" wx:if="{{showClaimBar && rewardInfo.my_claim}}">
<view class="claim-text">
<text class="claim-title">🎉 第{{rewardInfo.my_claim.mingci}}名 · 待领取</text>
<text class="claim-amount">¥{{rewardInfo.my_claim.amount}}</text>
</view>
<button class="claim-btn" loading="{{claimLoading}}" bindtap="onClaimTap">立即领取</button>
</view>
</view>
<global-notification id="global-notification" />

View File

@@ -1,290 +1,434 @@
page {
background: #0d1117;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif;
background: #ffe566;
color: #333;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
}
/* 主题色 */
/* 选中/金额: #6BA3F7 浅蓝: #8EB8FF 银: #A8B4C4 铜: #C4A882 */
.rank-page {
min-height: 100vh;
position: relative;
background: linear-gradient(180deg, #ffe566 0%, #ffe566 420rpx, #fff8dc 520rpx, #ffffff 620rpx);
}
.page-bg {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 0;
}
/* ========== 顶栏 ========== */
.top-bar {
/* ========== 顶区 ========== */
.header-zone {
position: relative;
z-index: 10;
padding: 16rpx 28rpx 20rpx;
padding-bottom: 8rpx;
}
.top-row {
.role-tabs {
display: flex;
align-items: stretch;
background: #ffffff;
margin: 0 0 0;
padding: 0 8rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.role-tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24rpx 0 18rpx;
position: relative;
}
.role-tab-text {
font-size: 28rpx;
color: #666666;
line-height: 1.2;
}
.role-tab.active .role-tab-text {
color: #1a1a1a;
font-weight: 700;
font-size: 30rpx;
}
.role-tab-line {
position: absolute;
bottom: 8rpx;
left: 50%;
transform: translateX(-50%);
width: 48rpx;
height: 6rpx;
border-radius: 6rpx;
background: #3d2914;
}
.date-scroll {
width: 100%;
white-space: nowrap;
padding: 20rpx 0 8rpx;
}
.date-line {
display: inline-flex;
padding: 0 20rpx;
gap: 8rpx;
}
.date-tab {
display: inline-block;
padding: 10rpx 22rpx;
font-size: 26rpx;
color: rgba(0, 0, 0, 0.45);
border-radius: 8rpx;
}
.date-tab.active {
color: #1a1a1a;
font-weight: 700;
position: relative;
}
.date-tab.active::after {
content: '';
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
width: 36rpx;
height: 4rpx;
border-radius: 4rpx;
background: #3d2914;
}
.scope-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
gap: 12rpx;
padding: 8rpx 24rpx 12rpx;
}
.page-title {
font-size: 38rpx;
font-weight: 700;
color: #fff;
.scope-tab {
padding: 8rpx 20rpx;
border-radius: 999rpx;
font-size: 22rpx;
color: rgba(0, 0, 0, 0.55);
background: rgba(255, 255, 255, 0.55);
border: 1rpx solid rgba(0, 0, 0, 0.06);
}
.refresh-wrap {
width: 60rpx; height: 60rpx;
.scope-tab.active {
color: #1a1a1a;
font-weight: 600;
background: #ffffff;
border-color: rgba(0, 0, 0, 0.12);
}
.refresh-btn {
margin-left: auto;
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: rgba(255,255,255,0.14);
background: rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
}
.refresh-ico {
width: 32rpx; height: 32rpx;
opacity: 0.8;
width: 28rpx;
height: 28rpx;
opacity: 0.65;
}
.role-scroll {
width: 100%;
white-space: nowrap;
margin-bottom: 16rpx;
.reward-banner {
margin: 0 24rpx 12rpx;
padding: 18rpx 22rpx;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.85);
border: 1rpx solid rgba(255, 193, 7, 0.35);
display: flex;
align-items: center;
justify-content: space-between;
}
.role-line {
display: inline-flex;
gap: 12rpx;
.reward-banner-main {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.role-chip {
display: inline-block;
padding: 12rpx 28rpx;
border-radius: 999rpx;
font-size: 26rpx;
color: rgba(255,255,255,0.65);
background: rgba(20, 26, 40, 0.75);
border: 1rpx solid rgba(255,255,255,0.12);
}
.role-chip.on {
color: #fff;
font-weight: 600;
background: rgba(107, 163, 247, 0.45);
border-color: rgba(107, 163, 247, 0.65);
box-shadow: 0 0 20rpx rgba(107, 163, 247, 0.2);
}
.date-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10rpx;
margin-bottom: 12rpx;
}
.date-chip {
text-align: center;
padding: 14rpx 0;
border-radius: 14rpx;
font-size: 24rpx;
color: rgba(255,255,255,0.65);
background: rgba(20, 26, 40, 0.72);
border: 1rpx solid rgba(255,255,255,0.08);
}
.date-chip.on {
color: #A8CCFF;
font-weight: 600;
background: rgba(107, 163, 247, 0.35);
border-color: rgba(107, 163, 247, 0.55);
}
.sort-tip {
font-size: 22rpx;
color: rgba(255,255,255,0.3);
}
.reward-tag { font-size: 26rpx; font-weight: 600; color: #d48806; }
.reward-club { font-size: 22rpx; color: #666; }
.reward-status { font-size: 22rpx; color: #999; }
.reward-status.pending { color: #52c41a; font-weight: 600; }
.reward-status.claimed { color: #8c8c8c; }
.reward-link { font-size: 24rpx; color: #d48806; }
/* ========== 状态 ========== */
.state-wrap {
position: relative;
z-index: 5;
display: flex;
flex-direction: column;
align-items: center;
padding: 140rpx 0;
color: rgba(255,255,255,0.45);
padding: 120rpx 0;
color: rgba(0, 0, 0, 0.45);
font-size: 28rpx;
}
.loader {
width: 48rpx; height: 48rpx;
border: 3rpx solid rgba(255,255,255,0.12);
border-top-color: #6BA3F7;
width: 48rpx;
height: 48rpx;
border: 3rpx solid rgba(0, 0, 0, 0.08);
border-top-color: #ffb800;
border-radius: 50%;
animation: spin 0.7s linear infinite;
margin-bottom: 20rpx;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.state-img {
width: 160rpx; height: 160rpx;
width: 160rpx;
height: 160rpx;
margin-bottom: 20rpx;
opacity: 0.65;
opacity: 0.5;
}
/* ========== 滚动 ========== */
/* ========== 滚动 ========== */
.body-scroll {
position: relative;
z-index: 5;
height: calc(100vh - 300rpx);
height: calc(100vh - 280rpx);
}
/* ========== 领奖台(纯 CSS ========== */
.podium-panel {
margin: 8rpx 24rpx 24rpx;
padding: 28rpx 12rpx 0;
border-radius: 28rpx;
background: rgba(14, 18, 30, 0.88);
border: 1rpx solid rgba(255, 255, 255, 0.1);
/* ========== 领奖台 ========== */
.podium-zone {
padding: 16rpx 16rpx 0;
}
.podium-cols {
.podium-row {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 10rpx;
gap: 6rpx;
}
.p-col {
.podium-col {
flex: 1;
max-width: 220rpx;
max-width: 230rpx;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.p-first {
max-width: 250rpx;
margin-bottom: 16rpx;
.col-1 {
max-width: 260rpx;
margin-bottom: 10rpx;
}
.p-badge {
width: 44rpx; height: 44rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 14rpx;
font-size: 24rpx;
font-weight: 800;
}
.p-first .p-badge {
width: 52rpx; height: 52rpx;
.crown {
font-size: 28rpx;
line-height: 1;
margin-bottom: 6rpx;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.15));
}
.badge-first {
color: #fff;
background: linear-gradient(145deg, #6BA3F7, #4A7FD4);
box-shadow: 0 4rpx 16rpx rgba(107, 163, 247, 0.4);
.col-1 .crown {
font-size: 34rpx;
}
.badge-silver {
color: #fff;
background: linear-gradient(145deg, #A8B4C4, #7A8899);
box-shadow: 0 4rpx 12rpx rgba(168, 180, 196, 0.25);
}
.badge-bronze {
color: #fff;
background: linear-gradient(145deg, #C4A882, #9A7858);
box-shadow: 0 4rpx 12rpx rgba(196, 168, 130, 0.25);
}
.p-avatar-wrap {
.avatar-ring {
border-radius: 50%;
padding: 4rpx;
margin-bottom: 12rpx;
padding: 6rpx;
margin-bottom: 10rpx;
background: #ffffff;
}
.wrap-first {
padding: 5rpx;
background: linear-gradient(145deg, #8EB8FF, #5B8FD8);
box-shadow: 0 6rpx 24rpx rgba(107, 163, 247, 0.3);
.ring-gold {
padding: 8rpx;
box-shadow: 0 0 0 4rpx #ffd700, 0 8rpx 20rpx rgba(255, 183, 0, 0.35);
}
.wrap-silver {
background: linear-gradient(145deg, #C8D0DA, #909AA8);
.ring-silver {
box-shadow: 0 0 0 4rpx #b8c4d4, 0 6rpx 16rpx rgba(120, 140, 180, 0.25);
}
.wrap-bronze {
background: linear-gradient(145deg, #D4B896, #A88860);
.ring-bronze {
box-shadow: 0 0 0 4rpx #cd9a5a, 0 6rpx 16rpx rgba(205, 127, 50, 0.25);
}
.p-avatar {
width: 100rpx; height: 100rpx;
width: 96rpx;
height: 96rpx;
border-radius: 50%;
display: block;
background: #1a2030;
background: #f0f0f0;
}
.p-avatar-lg {
width: 120rpx; height: 120rpx;
width: 116rpx;
height: 116rpx;
}
.p-name {
font-size: 26rpx;
font-weight: 600;
color: rgba(255,255,255,0.92);
color: #1a1a1a;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
margin-bottom: 8rpx;
}
.p-name-first {
.p-name-lg {
font-size: 28rpx;
color: #fff;
}
.p-uid {
margin-top: 4rpx;
font-size: 20rpx;
color: rgba(255,255,255,0.35);
}
.p-amount {
margin: 12rpx 0 10rpx;
text-align: center;
}
.p-money {
display: block;
font-size: 30rpx;
font-weight: 700;
color: #8EB8FF;
letter-spacing: -0.5rpx;
}
.p-money-first {
font-size: 34rpx;
color: #A8CCFF;
}
.p-label {
display: block;
font-size: 22rpx;
color: #999999;
line-height: 1.2;
}
.p-amount {
font-size: 34rpx;
font-weight: 800;
color: #ffb800;
line-height: 1.3;
margin-top: 2rpx;
}
.p-amount-lg {
font-size: 40rpx;
}
.p-sub-label {
font-size: 22rpx;
color: #999999;
margin-top: 6rpx;
}
.p-sub-value {
font-size: 28rpx;
font-weight: 700;
color: #ffb800;
line-height: 1.2;
}
.p-sub-value-lg {
font-size: 32rpx;
}
.p-reward {
margin-top: 4rpx;
font-size: 20rpx;
color: rgba(255,255,255,0.35);
color: #d48806;
font-weight: 600;
}
.pedestal {
width: 92%;
margin-top: 14rpx;
border-radius: 16rpx 16rpx 0 0;
background: linear-gradient(180deg, #ffe08a 0%, #ffd04d 100%);
box-shadow: inset 0 2rpx 0 rgba(255, 255, 255, 0.5);
}
.pedestal-1 {
height: 88rpx;
background: linear-gradient(180deg, #ffe08a 0%, #ffc933 100%);
}
.pedestal-2 {
height: 64rpx;
background: linear-gradient(180deg, #ffe08a 0%, #ffd04d 100%);
}
.pedestal-3 {
height: 48rpx;
background: linear-gradient(180deg, #ffe08a 0%, #ffd04d 100%);
}
/* ========== 列表区(白底) ========== */
.list-panel {
margin-top: 8rpx;
background: #ffffff;
border-radius: 32rpx 32rpx 0 0;
padding: 24rpx 28rpx 0;
min-height: 400rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.04);
}
.list-row {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f3f3f3;
}
.list-row:last-child {
border-bottom: none;
}
.li-rank {
width: 44rpx;
flex-shrink: 0;
font-size: 32rpx;
font-weight: 700;
color: #333333;
text-align: center;
}
.li-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
flex-shrink: 0;
margin: 0 16rpx 0 8rpx;
background: #f0f0f0;
}
.li-name {
flex: 1;
min-width: 0;
font-size: 28rpx;
font-weight: 500;
color: #1a1a1a;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.li-right {
flex-shrink: 0;
text-align: right;
margin-left: 12rpx;
}
.li-amount {
display: block;
font-size: 30rpx;
font-weight: 700;
color: #1a1a1a;
line-height: 1.3;
}
.li-orders {
display: block;
margin-top: 4rpx;
font-size: 24rpx;
color: #999999;
}
.li-reward {
display: block;
margin-top: 4rpx;
font-size: 20rpx;
color: #d48806;
}
.sort-tip {
display: block;
padding: 0 24rpx 8rpx;
font-size: 22rpx;
color: rgba(0, 0, 0, 0.45);
}
.p-metrics {
@@ -292,176 +436,86 @@ page {
flex-wrap: wrap;
gap: 6rpx;
justify-content: center;
margin-bottom: 16rpx;
min-height: 36rpx;
margin: 8rpx 0 10rpx;
max-width: 100%;
}
.p-metric {
font-size: 18rpx;
color: rgba(255,255,255,0.55);
background: rgba(255,255,255,0.1);
font-size: 20rpx;
color: #888;
background: rgba(255, 255, 255, 0.65);
padding: 4rpx 10rpx;
border-radius: 8rpx;
}
/* 底座 · 圆角柱体,非方块 */
.p-pedestal {
width: 88%;
border-radius: 20rpx 20rpx 0 0;
position: relative;
}
.p-pedestal::before {
content: '';
position: absolute;
top: 0; left: 10%; right: 10%;
height: 2rpx;
background: rgba(255,255,255,0.15);
border-radius: 2rpx;
}
.pedestal-first {
height: 100rpx;
background: linear-gradient(180deg, rgba(107,163,247,0.55) 0%, rgba(107,163,247,0.22) 100%);
border: 1rpx solid rgba(107,163,247,0.35);
border-bottom: none;
}
.pedestal-silver {
height: 72rpx;
background: linear-gradient(180deg, rgba(168,180,196,0.45) 0%, rgba(168,180,196,0.18) 100%);
border: 1rpx solid rgba(168,180,196,0.28);
border-bottom: none;
}
.pedestal-bronze {
height: 56rpx;
background: linear-gradient(180deg, rgba(196,168,130,0.45) 0%, rgba(196,168,130,0.18) 100%);
border: 1rpx solid rgba(196,168,130,0.28);
border-bottom: none;
}
/* ========== 列表 ========== */
.list-wrap {
padding: 0 24rpx;
}
.list-title-row {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 8rpx 8rpx 20rpx;
}
.list-title {
font-size: 30rpx;
font-weight: 700;
color: rgba(255,255,255,0.85);
}
.list-range {
font-size: 22rpx;
color: rgba(255,255,255,0.3);
}
.list-card {
position: relative;
margin-bottom: 14rpx;
border-radius: 18rpx;
overflow: hidden;
border: 1rpx solid rgba(255,255,255,0.05);
}
.list-card-bg {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 0;
}
.list-card-body {
position: relative;
z-index: 1;
display: flex;
align-items: center;
padding: 20rpx 22rpx;
background: rgba(12, 16, 28, 0.92);
backdrop-filter: blur(8px);
}
.lc-rank {
width: 48rpx;
flex-shrink: 0;
text-align: center;
}
.lc-rank text {
font-size: 32rpx;
font-weight: 700;
color: rgba(255,255,255,0.18);
}
.lc-avatar {
width: 80rpx; height: 80rpx;
border-radius: 50%;
flex-shrink: 0;
margin: 0 16rpx 0 6rpx;
border: 2rpx solid rgba(107, 163, 247, 0.2);
background: #1a2030;
}
.lc-info { flex: 1; min-width: 0; }
.lc-name {
.li-label {
display: block;
font-size: 28rpx;
font-weight: 600;
color: rgba(255,255,255,0.92);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.lc-uid {
display: block;
margin-top: 4rpx;
margin-top: 2rpx;
font-size: 22rpx;
color: rgba(255,255,255,0.32);
color: #999;
}
.lc-tags {
.li-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-top: 8rpx;
gap: 6rpx;
justify-content: flex-end;
margin-top: 6rpx;
max-width: 280rpx;
}
.lc-tag {
.li-tag {
font-size: 20rpx;
color: rgba(255,255,255,0.5);
background: rgba(255,255,255,0.1);
padding: 2rpx 10rpx;
color: #888;
background: #f5f5f5;
padding: 2rpx 8rpx;
border-radius: 6rpx;
}
.lc-score {
flex-shrink: 0;
text-align: right;
margin-left: 10rpx;
.bottom-gap {
height: 160rpx;
}
.lc-money {
display: block;
font-size: 28rpx;
/* ========== 领取栏 ========== */
.claim-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom));
background: #ffffff;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1rpx solid #f0f0f0;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
}
.claim-text {
display: flex;
flex-direction: column;
}
.claim-title {
font-size: 26rpx;
color: #333;
}
.claim-amount {
font-size: 36rpx;
font-weight: 700;
color: #8EB8FF;
color: #ffb800;
}
.lc-label {
display: block;
margin-top: 2rpx;
font-size: 20rpx;
color: rgba(255,255,255,0.32);
.claim-btn {
margin: 0;
padding: 0 40rpx;
height: 72rpx;
line-height: 72rpx;
font-size: 28rpx;
background: linear-gradient(135deg, #ffc107, #ffb300);
color: #1a1a1a;
border-radius: 999rpx;
border: none;
}
.bottom-gap { height: 48rpx; }

View File

@@ -0,0 +1,368 @@
// pages/fighter-recharge/fighter-deposit.js — 平台履约金/保证金独立页
import request from '../../utils/request';
import PopupService from '../../services/popupService.js';
import { ensurePhoneAuth } from '../../utils/phone-auth.js';
import { ICON_KEYS, resolveConfiguredIcon, resolveMiniappIcon, ICON_FOLDER, refreshPindaoConfig } from '../../utils/miniapp-icons.js';
import { resolveAvatarFromStorage, onAvatarImageError } from '../../utils/avatar.js';
const app = getApp();
const DEFAULT_RULES = [
'履约金有效期为永久有效',
'超过15天未接单可联系客服申请退还',
'缴纳后可优先接锁单/平台单等待遇',
];
const DEFAULT_BENEFITS = [
{ label: '平台订单优先', key: ICON_KEYS.FIGHTER_DEPOSIT_BENEFIT_1 },
{ label: '快人一步', key: ICON_KEYS.FIGHTER_DEPOSIT_BENEFIT_2 },
{ label: '优质接单', key: ICON_KEYS.FIGHTER_DEPOSIT_BENEFIT_3 },
{ label: '解锁平台单', key: ICON_KEYS.FIGHTER_DEPOSIT_BENEFIT_4 },
];
Page({
data: {
imgUrls: { pageBg: '', hero: '' },
avatarUrl: '',
nickname: '',
uid: '',
yajin: 0,
yajinDisplay: '0.00',
yajinActive: false,
depositRules: DEFAULT_RULES,
depositBenefits: DEFAULT_BENEFITS,
agreed: false,
payAmountLabel: '',
yajinAmount: '100',
quickAmounts: ['100', '500', '1000', '5000'],
showAmountModal: false,
showPayMethodModal: false,
showBalanceModal: false,
showConfirmModal: false,
balanceOptions: [],
selectedBalanceId: null,
selectedBalanceInfo: null,
currentYajinAmount: null,
currentDingdanid: '',
payLoading: false,
loadingText: '处理中...',
},
onLoad(options) {
this._jumpParams = options || {};
this.initImages();
this.loadUserInfo();
},
onHide() {
this.selectComponent('#popupNotice')?.cleanup?.();
},
async onShow() {
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
if (!phoneOk) return;
this.registerNotification();
this.loadYajin();
this.refreshConfiguredIcons();
PopupService.checkAndShow(this, 'dashouchongzhi');
const preset = this._jumpParams?.presetAmount || this._jumpParams?.requiredYajin;
if (preset && parseFloat(preset) > 0) {
this.setData({ yajinAmount: String(parseInt(preset, 10)) });
}
},
registerNotification() {
const comp = this.selectComponent('#global-notification');
if (comp?.showNotification) {
app.globalData.globalNotification = {
show: (data) => comp.showNotification(data),
hide: () => comp.hideNotification(),
};
}
},
initImages() {
const fb = (key) => `${ICON_FOLDER}/${key}.png`;
const depositBenefits = DEFAULT_BENEFITS.map((item) => ({
label: item.label,
iconUrl: resolveMiniappIcon(app, item.key, fb(item.key)),
}));
this.setData({
imgUrls: {
pageBg: resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_DEPOSIT_BG),
hero: resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_DEPOSIT_HERO),
},
depositBenefits,
});
},
async refreshConfiguredIcons() {
try {
await refreshPindaoConfig(app);
} catch (e) {
console.warn('refreshConfiguredIcons failed', e);
}
this.initImages();
this.loadUserInfo();
},
loadUserInfo() {
const gd = app.globalData || {};
this.setData({
avatarUrl: resolveAvatarFromStorage(app),
nickname: gd.dashouNicheng || gd.nicheng || '打手',
uid: wx.getStorageSync('uid') || gd.uid || '',
});
},
onAvatarError() {
onAvatarImageError(this, 'avatarUrl', app);
},
loadYajin() {
const yajin = parseFloat(app.globalData.yajin || 0) || 0;
this.setData({
yajin,
yajinDisplay: yajin.toFixed(2),
yajinActive: yajin > 0,
});
},
toggleAgree() {
this.setData({ agreed: !this.data.agreed });
},
onTapPay() {
if (!this.data.agreed) {
wx.showToast({ title: '请先阅读并同意缴纳规则', icon: 'none' });
return;
}
this.setData({ showAmountModal: true });
},
hideAmountModal() {
this.setData({ showAmountModal: false });
},
onYajinInput(e) {
let v = (e.detail.value || '').replace(/[^\d]/g, '');
if (v) {
const n = parseInt(v, 10);
if (n > 10000) v = '10000';
if (n < 1) v = '1';
}
this.setData({ yajinAmount: v });
},
setQuickAmount(e) {
this.setData({ yajinAmount: e.currentTarget.dataset.amount });
},
onConfirmAmount() {
const amount = this.data.yajinAmount;
if (!amount || amount < 1 || amount > 10000) {
wx.showToast({ title: '请输入1-10000元', icon: 'none' });
return;
}
this.hideAmountModal();
this.setData({
currentYajinAmount: amount,
payAmountLabel: `(¥ ${amount}`,
showPayMethodModal: true,
});
},
hidePayMethodModal() {
this.setData({ showPayMethodModal: false });
},
onPayMethodSelect(e) {
const method = e.currentTarget.dataset.method;
this.hidePayMethodModal();
if (method === 'wx') {
this.setData({ yajinAmount: this.data.currentYajinAmount }, () => this.handleYajinPay());
} else {
this.fetchBalanceOptions();
}
},
async fetchBalanceOptions() {
this.showLoading('获取抵扣信息...');
try {
const res = await request({
url: '/shangpin/czhqdy',
method: 'POST',
data: {
leixing: 2,
yajin_jine: parseInt(this.data.currentYajinAmount, 10),
},
});
if (res.data.code === 200) {
this.setData({ balanceOptions: res.data.data || [], showBalanceModal: true });
} else {
wx.showToast({ title: res.data.message || '获取失败', icon: 'none' });
}
} catch (err) {
wx.showToast({ title: '网络错误', icon: 'none' });
} finally {
this.hideLoading();
}
},
hideBalanceModal() {
this.setData({ showBalanceModal: false });
},
onBalanceSelect(e) {
const id = e.currentTarget.dataset.id;
const selected = this.data.balanceOptions.find((o) => o.id === id);
if (!selected) return;
this.setData({
selectedBalanceId: id,
selectedBalanceInfo: selected,
showConfirmModal: true,
showBalanceModal: false,
});
},
hideConfirmModal() {
this.setData({ showConfirmModal: false });
},
async confirmBalancePay() {
const { selectedBalanceId, currentYajinAmount } = this.data;
if (!selectedBalanceId) return;
this.hideConfirmModal();
this.showLoading('抵扣支付中...');
try {
const res = await request({
url: '/shangpin/dsqrgmdh',
method: 'POST',
data: {
leixing: 2,
shenfen_id: selectedBalanceId,
yajin_jine: parseInt(currentYajinAmount, 10),
},
});
if (res.data.code === 200) {
const data = res.data.data || {};
if (data.yajin !== undefined) app.globalData.yajin = data.yajin;
this.loadYajin();
wx.showToast({ title: '缴纳成功', icon: 'success' });
} else {
wx.showToast({ title: res.data.message || '购买失败', icon: 'none' });
}
} catch (err) {
wx.showToast({ title: '网络错误', icon: 'none' });
} finally {
this.hideLoading();
}
},
confirmWechatPurchase() {
return new Promise((resolve) => {
wx.showModal({
title: '确认微信支付',
content: '您即将通过微信支付缴纳履约金。购买成功后不可随意退款,请确认无误后再继续。',
confirmText: '确认购买',
cancelText: '再想想',
success: (res) => resolve(!!res.confirm),
fail: () => resolve(false),
});
});
},
async handleYajinPay() {
const amount = this.data.yajinAmount;
if (!amount || amount < 1 || amount > 10000) {
wx.showToast({ title: '请输入1-10000元', icon: 'none' });
return;
}
if (!(await this.confirmWechatPurchase())) return;
this.showLoading('发起支付中...');
try {
const res = await request({
url: '/shangpin/yajingoumai',
method: 'POST',
data: { jine: parseInt(amount, 10) },
});
if (res.data.code === 200) {
this.setData({ currentDingdanid: res.data.dingdanid });
await this.wechatPay(res.data.payParams);
} else {
wx.showToast({ title: res.data.message || '支付发起失败', icon: 'none' });
}
} catch (err) {
wx.showToast({ title: '支付失败', icon: 'none' });
} finally {
this.hideLoading();
}
},
wechatPay(payParams) {
return new Promise((resolve, reject) => {
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType || 'MD5',
paySign: payParams.paySign,
success: async () => {
wx.showToast({ title: '支付成功!确认中...', icon: 'none' });
try {
await this.startPolling();
resolve();
} catch (e) {
reject(e);
}
},
fail: async (res) => {
wx.showToast({ title: res.errMsg?.includes('cancel') ? '已取消支付' : '支付失败', icon: 'none' });
try {
await request({
url: '/shangpin/yajinshibai',
method: 'POST',
data: { dingdanid: this.data.currentDingdanid },
});
} catch (e) { /* ignore */ }
reject(new Error(res.errMsg));
},
});
});
},
async startPolling() {
const dingdanid = this.data.currentDingdanid;
if (!dingdanid) return;
this.showLoading('确认支付结果...');
for (let i = 0; i < 10; i++) {
try {
const res = await request({
url: '/shangpin/yajinlunxun',
method: 'POST',
data: { dingdanid },
});
if (res.data.code === 200) {
if (res.data.yajin !== undefined) app.globalData.yajin = res.data.yajin;
this.loadYajin();
this.hideLoading();
wx.showToast({ title: '履约金缴纳成功', icon: 'success' });
return;
}
} catch (e) { /* retry */ }
await new Promise((r) => setTimeout(r, 2000));
}
this.hideLoading();
wx.showToast({ title: '确认超时,请稍后查看余额', icon: 'none' });
},
showLoading(text) {
this.setData({ payLoading: true, loadingText: text || '处理中...' });
},
hideLoading() {
this.setData({ payLoading: false });
},
stopPropagation() {},
});

View File

@@ -0,0 +1,10 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification",
"popup-notice": "/components/popup-notice/popup-notice"
},
"navigationBarTitleText": "平台履约金",
"navigationBarBackgroundColor": "#FFF9F0",
"navigationBarTextStyle": "black",
"backgroundColor": "#FFF9F0"
}

View File

@@ -0,0 +1,133 @@
<!-- 平台履约金 / 保证金充值页 -->
<view class="deposit-page">
<image wx:if="{{imgUrls.pageBg}}" class="page-bg" src="{{imgUrls.pageBg}}" mode="widthFix" />
<view class="page-bg-fallback" wx:else></view>
<view class="page-body">
<view class="user-row">
<image class="user-avatar" src="{{avatarUrl}}" mode="aspectFill" binderror="onAvatarError" />
<view class="user-info">
<text class="user-name">{{nickname || '打手'}}</text>
<text class="user-id">会员ID: {{uid || '--'}}</text>
</view>
</view>
<view class="deposit-card">
<view class="deposit-card-left">
<text class="deposit-card-title">平台接单履约金</text>
<view class="deposit-price-row">
<text class="deposit-currency">¥</text>
<text class="deposit-price">{{yajinDisplay}}</text>
</view>
<text class="deposit-validity">有效期:永久有效</text>
</view>
<view class="deposit-card-right">
<view class="icon-clip icon-clip-deposit" wx:if="{{imgUrls.hero}}">
<image class="icon-clip-img" src="{{imgUrls.hero}}" mode="aspectFill" />
</view>
<view class="deposit-status">{{yajinActive ? '已缴纳' : '未激活'}}</view>
</view>
<view class="deposit-shimmer"></view>
</view>
<view class="section-card">
<text class="section-title">履约金规则</text>
<view class="rule-list">
<view wx:for="{{depositRules}}" wx:key="index" class="rule-item">
<text class="rule-num">{{index + 1}}.</text>
<text class="rule-text">{{item}}</text>
</view>
</view>
</view>
<view class="section-card">
<text class="section-title">平台履约金权益</text>
<view class="benefit-grid">
<view wx:for="{{depositBenefits}}" wx:key="label" class="benefit-item">
<view wx:if="{{item.iconUrl}}" class="icon-clip icon-clip-deposit-benefit">
<image class="icon-clip-img" src="{{item.iconUrl}}" mode="aspectFill" />
</view>
<text>{{item.label}}</text>
</view>
</view>
</view>
</view>
<view class="footer-bar">
<view class="agree-row" bindtap="toggleAgree">
<view class="agree-check {{agreed ? 'agree-check--on' : ''}}">{{agreed ? '✓' : ''}}</view>
<text class="agree-text">已阅读并同意</text>
<text class="agree-link">《平台履约金缴纳规则》</text>
</view>
<view class="pay-btn {{agreed ? '' : 'pay-btn--disabled'}}" bindtap="onTapPay">
<text>缴纳履约金{{payAmountLabel}}</text>
<view class="pay-btn-shimmer"></view>
</view>
</view>
</view>
<!-- 金额输入弹窗 -->
<view class="modal-mask" wx:if="{{showAmountModal}}" bindtap="hideAmountModal">
<view class="modal-panel" catchtap="stopPropagation">
<text class="modal-title">缴纳履约金</text>
<text class="modal-sub">支持 1-10000 元整数</text>
<view class="amount-input-wrap">
<text class="amount-prefix">¥</text>
<input class="amount-input" type="digit" value="{{yajinAmount}}" bindinput="onYajinInput" focus="{{showAmountModal}}" />
</view>
<view class="quick-row">
<view wx:for="{{quickAmounts}}" wx:key="*this" class="quick-chip {{yajinAmount == item ? 'active' : ''}}" bindtap="setQuickAmount" data-amount="{{item}}">{{item}}元</view>
</view>
<view class="modal-actions">
<view class="modal-btn cancel" bindtap="hideAmountModal">取消</view>
<view class="modal-btn confirm" bindtap="onConfirmAmount">下一步</view>
</view>
</view>
</view>
<!-- 支付方式 -->
<view class="modal-mask" wx:if="{{showPayMethodModal}}" bindtap="hidePayMethodModal">
<view class="modal-panel" catchtap="stopPropagation">
<text class="modal-title">选择支付方式</text>
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="wx">
<text class="pay-method-name">微信支付</text>
<text class="pay-method-desc">安全快捷</text>
</view>
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="balance">
<text class="pay-method-name">余额抵扣</text>
<text class="pay-method-desc">使用佣金或分红</text>
</view>
</view>
</view>
<view class="modal-mask" wx:if="{{showBalanceModal}}" bindtap="hideBalanceModal">
<view class="modal-panel" catchtap="stopPropagation">
<text class="modal-title">选择抵扣身份</text>
<view wx:for="{{balanceOptions}}" wx:key="id" class="balance-item" bindtap="onBalanceSelect" data-id="{{item.id}}">
<text>{{item.jieshao}}</text>
<text class="balance-need">需¥{{item.xuyao}}</text>
</view>
</view>
</view>
<view class="modal-mask" wx:if="{{showConfirmModal}}" bindtap="hideConfirmModal">
<view class="modal-panel" catchtap="stopPropagation">
<text class="modal-title">确认抵扣</text>
<view class="confirm-row"><text>身份</text><text>{{selectedBalanceInfo.jieshao}}</text></view>
<view class="confirm-row"><text>金额</text><text>¥{{selectedBalanceInfo.xuyao}}</text></view>
<view class="modal-actions">
<view class="modal-btn cancel" bindtap="hideConfirmModal">取消</view>
<view class="modal-btn confirm" bindtap="confirmBalancePay">确认支付</view>
</view>
</view>
</view>
<view class="loading-mask" wx:if="{{payLoading}}">
<view class="loading-box">
<view class="loading-spinner"></view>
<text>{{loadingText}}</text>
</view>
</view>
<global-notification id="global-notification" />
<popup-notice id="popupNotice" />

View File

@@ -0,0 +1,279 @@
@import '../../styles/xym-recharge-shared.wxss';
.deposit-page {
min-height: 100vh;
background: #fff9f0;
padding-bottom: calc(220rpx + env(safe-area-inset-bottom));
position: relative;
}
.page-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 0;
}
.page-bg-fallback {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 360rpx;
background: linear-gradient(180deg, #ffe082 0%, #fff9f0 100%);
z-index: 0;
}
.page-body {
position: relative;
z-index: 1;
padding: 24rpx 30rpx 0;
}
.user-row {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.user-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
margin-right: 20rpx;
background: #eee;
}
.user-name {
display: block;
font-size: 32rpx;
font-weight: 700;
color: #222;
}
.user-id {
display: block;
margin-top: 6rpx;
font-size: 24rpx;
color: #999;
}
.deposit-card {
position: relative;
overflow: hidden;
border-radius: 24rpx;
padding: 32rpx 28rpx;
background: linear-gradient(135deg, #fff8e1 0%, #ffe0b2 100%);
display: flex;
justify-content: space-between;
align-items: stretch;
margin-bottom: 24rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 183, 77, 0.25);
}
.deposit-card-left {
flex: 1;
min-width: 0;
}
.deposit-card-title {
font-size: 30rpx;
font-weight: 700;
color: #492f00;
}
.deposit-price-row {
display: flex;
align-items: baseline;
margin: 16rpx 0 8rpx;
}
.deposit-currency {
font-size: 36rpx;
font-weight: 700;
color: #222;
margin-right: 4rpx;
}
.deposit-price {
font-size: 64rpx;
font-weight: 800;
color: #222;
line-height: 1;
}
.deposit-validity {
font-size: 24rpx;
color: #947641;
}
.deposit-card-right {
position: relative;
width: 200rpx;
display: flex;
align-items: center;
justify-content: center;
}
.icon-clip {
overflow: hidden;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.5);
}
.icon-clip-img {
width: 100%;
height: 100%;
display: block;
}
.icon-clip-deposit {
width: 180rpx;
height: 180rpx;
border-radius: 40rpx;
}
.icon-clip-deposit-benefit {
width: 36rpx;
height: 36rpx;
border-radius: 14rpx;
}
.deposit-status {
position: absolute;
right: 0;
bottom: 0;
padding: 6rpx 16rpx;
border-radius: 8rpx;
background: rgba(0, 0, 0, 0.08);
font-size: 22rpx;
color: #666;
}
.deposit-shimmer {
position: absolute;
inset: 0;
background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.45) 50%, transparent 60%);
animation: card-shimmer 3s infinite;
pointer-events: none;
}
.section-card {
background: #fff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.section-title {
display: block;
text-align: center;
font-size: 30rpx;
font-weight: 700;
color: #333;
margin-bottom: 20rpx;
}
.rule-item {
display: flex;
gap: 8rpx;
margin-bottom: 12rpx;
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.benefit-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx 16rpx;
}
.benefit-item {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 26rpx;
color: #555;
}
.footer-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom));
background: rgba(255, 249, 240, 0.96);
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
z-index: 10;
}
.agree-row {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-bottom: 16rpx;
font-size: 24rpx;
color: #666;
}
.agree-check {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid #ccc;
margin-right: 10rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 20rpx;
color: #fff;
}
.agree-check--on {
background: #f5a623;
border-color: #f5a623;
}
.agree-link {
color: #e6a23c;
}
.pay-btn {
position: relative;
overflow: hidden;
height: 96rpx;
border-radius: 48rpx;
background: linear-gradient(90deg, #fae04d, #ffc0a3);
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 700;
color: #492f00;
box-shadow: 0 8rpx 20rpx rgba(245, 166, 35, 0.35);
}
.pay-btn--disabled {
opacity: 0.55;
}
.pay-btn-shimmer {
position: absolute;
inset: 0;
background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.5) 50%, transparent 60%);
animation: btn-shimmer 2.5s infinite;
pointer-events: none;
}
@keyframes card-shimmer {
0% { transform: translateX(-120%); }
100% { transform: translateX(120%); }
}
@keyframes btn-shimmer {
0% { transform: translateX(-120%); }
100% { transform: translateX(120%); }
}

View File

@@ -1,72 +1,57 @@
// pages/dashou-chongzhi/index.js
// pages/fighter-recharge/fighter-recharge.js
import request from '../../utils/request';
import PopupService from '../../services/popupService.js';
import { ensurePhoneAuth } from '../../utils/phone-auth.js';
import { ICON_KEYS, resolveConfiguredIcon, resolveMiniappIcon, ICON_FOLDER, refreshPindaoConfig } from '../../utils/miniapp-icons.js';
import { resolveAvatarFromStorage, onAvatarImageError } from '../../utils/avatar.js';
const app = getApp();
const PLATFORM_ADVANTAGE_META = [
{ key: ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_1, title: '订单丰富', desc: '平台单、商家单多种类型' },
{ key: ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_2, title: '俱乐部专属', desc: '按俱乐部独立配置权益' },
{ key: ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_3, title: '客服支持', desc: '遇到问题随时联系客服' },
{ key: ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_4, title: '安全可靠', desc: '资金与订单平台保障' },
];
Page({
data: {
// ========== 全局数据 ==========
clubmber: [], // 会员列表(注意:全局变量中是 clumber这里保持原样
yajin: 0, // 押金
jifen: 0, // 积分(注意:全局变量中是 jinfen
jifenProgress: 0, // 积分进度条角度
// ========== 会员商品列表 ==========
huiyuanList: [], // 从后端获取的会员列表
currentHuiyuanIndex: 0, // 当前选中的会员索引
currentHuiyuan: {}, // 当前选中的会员详情
// ========== 押金充值相关 ==========
showYajinModal: false, // 显示押金弹窗
yajinAmount: '100', // 押金金额
// ========== 支付相关 ==========
payLoading: false, // 支付加载状态
imgUrls: { pageBg: '' },
avatarUrl: '',
nickname: '',
uid: '',
memberBadge: '会员',
agreed: false,
memberBenefits: [],
platformAdvantages: [],
clubmber: [],
jifen: 0,
huiyuanList: [],
currentHuiyuanIndex: 0,
currentHuiyuan: {},
payLoading: false,
loadingText: '支付中...',
// ========== 订单ID记录 ==========
currentDingdanid: '', // 当前操作的订单ID
// ========== 计算高度 ==========
huiyuanListHeight: 120,
// ========== 会员详情规则 ==========
currentDingdanid: '',
detailRules: [],
// ========== 新增:跳转参数处理 ==========
// 用于接收从其他页面跳转过来的参数,决定是否自动滚动
jumpParams: {},
// ========== 新增:支付方式选择 ==========
showPayMethodModal: false, // 支付方式选择弹窗
currentBuyType: null, // 当前购买类型1会员 2押金 3积分
currentHuiyuanId: null, // 当前选中的会员ID用于会员购买
currentBuyIsTrial: false, // 正式/体验:仅体验禁余额抵扣
currentYajinAmount: null, // 当前押金金额(用于押金购买)
// ========== 新增:余额抵扣身份选择 ==========
showBalanceModal: false, // 余额抵扣身份选择弹窗
balanceOptions: [], // 后端返回的身份列表
selectedBalanceId: null, // 选中的身份ID
// ========== 新增:余额抵扣确认弹窗 ==========
showConfirmModal: false, // 余额抵扣确认弹窗
selectedBalanceInfo: null, // 选中的身份详情(用于确认弹窗)
// ========== 新增:会员详情弹窗 ==========
showPayMethodModal: false,
currentBuyType: null,
currentHuiyuanId: null,
currentBuyIsTrial: false,
showBalanceModal: false,
balanceOptions: [],
selectedBalanceId: null,
showConfirmModal: false,
selectedBalanceInfo: null,
showMemberModal: false,
currentMemberDetail: null,
},
// ========== 生命周期函数 ==========
onLoad(options) {
//console.log('页面加载,跳转参数:', options);
// ✅ 新增:保存跳转参数
this.setData({
jumpParams: options || {}
});
this.setData({ jumpParams: options || {} });
this.initImages();
this.loadUserInfo();
this.initPage();
},
@@ -86,6 +71,7 @@ onHide() {
this.registerNotificationComponent();
this.loadFromGlobalData();
this.checkAndRefreshData();
this.refreshConfiguredIcons();
// 调用统一弹窗组件检查并展示公告
PopupService.checkAndShow(this, 'dashouchongzhi');
@@ -112,16 +98,139 @@ onHide() {
// ========== 初始化页面 ==========
async initPage() {
// 从全局数据加载
this.loadFromGlobalData();
// 获取会员商品列表
await this.fetchHuiyuanList();
// 计算会员列表高度
this.calculateHuiyuanHeight();
},
initImages() {
this.setData({
imgUrls: {
pageBg: resolveConfiguredIcon(app, ICON_KEYS.FIGHTER_RECHARGE_VIP_BG),
},
platformAdvantages: this.buildPlatformAdvantages(),
});
},
async refreshConfiguredIcons() {
try {
await refreshPindaoConfig(app);
} catch (e) {
console.warn('refreshConfiguredIcons failed', e);
}
this.initImages();
const list = this.data.huiyuanList || [];
const idx = this.data.currentHuiyuanIndex || 0;
if (list.length) {
this.applyCurrentHuiyuan(list[idx] || {}, idx);
}
this.loadUserInfo();
},
_icon(key, fallbackRel) {
return resolveMiniappIcon(app, key, fallbackRel);
},
buildPlatformAdvantages() {
const fb = (name) => `${ICON_FOLDER}/${name}.png`;
const keys = [
ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_1,
ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_2,
ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_3,
ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_4,
];
return PLATFORM_ADVANTAGE_META.map((item, i) => ({
title: item.title,
desc: item.desc,
iconUrl: this._icon(keys[i], fb(keys[i])),
}));
},
loadUserInfo() {
const gd = app.globalData || {};
this.setData({
avatarUrl: resolveAvatarFromStorage(app),
nickname: gd.dashouNicheng || gd.nicheng || '打手',
uid: wx.getStorageSync('uid') || gd.uid || '',
});
},
onAvatarError() {
onAvatarImageError(this, 'avatarUrl', app);
},
resolveOssUrl(path) {
if (!path) return '';
if (path.startsWith('http')) return path;
return (app.globalData.ossImageUrl || '') + path;
},
buildMemberBenefits(huiyuan = {}) {
const name = huiyuan.mingzi || '会员';
const formalPrice = huiyuan.jiage || '0';
const trialPrice = huiyuan.trial_price || '0';
const fb = (name) => `${ICON_FOLDER}/${name}.png`;
const meta = [
{
key: ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_1,
title: '充值立得',
desc: `开通${name}正式版 ¥${formalPrice},享优先接单`,
},
{
key: ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_2,
title: '体验尝鲜',
desc: huiyuan.can_buy_trial
? `体验版 ¥${trialPrice}/${huiyuan.trial_days || 0}终身1次`
: '正式会员支持余额抵扣',
},
{
key: ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_3,
title: '专属特权',
desc: '解锁平台单、优质接单等待遇',
},
];
return meta.map((item) => ({
title: item.title,
desc: item.desc,
iconUrl: this._icon(item.key, fb(item.key)),
}));
},
applyCurrentHuiyuan(huiyuan, index) {
this.setData({
currentHuiyuanIndex: index,
currentHuiyuan: huiyuan,
detailRules: this.buildDetailRules(huiyuan),
memberBenefits: this.buildMemberBenefits(huiyuan),
memberBadge: huiyuan.mingzi || '会员',
});
},
toggleAgree() {
this.setData({ agreed: !this.data.agreed });
},
goToDeposit() {
wx.navigateTo({ url: '/pages/fighter-recharge/fighter-deposit' });
},
onTapTrial(e) {
if (!this.data.agreed) {
wx.showToast({ title: '请先阅读并同意会员协议', icon: 'none' });
return;
}
this.onBuyHuiyuanClick(e);
},
onTapFormal(e) {
if (!this.data.agreed) {
wx.showToast({ title: '请先阅读并同意会员协议', icon: 'none' });
return;
}
this.onBuyHuiyuanClick(e);
},
stopPropagation() {},
// ========== 从全局变量加载数据 ==========
loadFromGlobalData() {
const app = getApp();
@@ -130,37 +239,12 @@ onHide() {
// ✅ 重要:保持字段对应关系
// 全局变量中是 clumber页面中是 clubmber
// 全局变量中是 jinfen页面中是 jifen
this.setData({
clubmber: globalData.clumber || [],
yajin: globalData.yajin || 0,
jifen: globalData.jinfen || 0 // ✅ 注意:这里从 jinfen 读取
});
// 计算积分进度条
this.calculateJifenProgress();
},
// ========== 计算积分进度条角度 ==========
calculateJifenProgress() {
const jifen = this.data.jifen || 0;
const progress = (jifen / 10) * 360; // 满分10分
this.setData({
jifenProgress: progress
});
},
// ========== 计算会员列表高度 ==========
calculateHuiyuanHeight() {
const clubmber = this.data.clubmber || [];
const itemHeight = 100; // 每个会员标签高度
const maxHeight = 300; // 最大高度3个会员
let height = clubmber.length * itemHeight;
height = Math.min(height, maxHeight);
const rawClubmber = globalData.clumber || [];
const clubmber = rawClubmber.filter((c) => c && c.huiyuanid);
this.setData({
huiyuanListHeight: height
clubmber: clubmber,
jifen: globalData.jinfen || 0,
});
},
@@ -174,17 +258,15 @@ onHide() {
});
if (res.data.code === 200) {
const huiyuanList = res.data.data || [];
// 处理数据,标记已购买的会员
const processedList = this.processHuiyuanList(huiyuanList);
const currentHuiyuan = processedList[0] || {};
this.setData({
huiyuanList: processedList,
currentHuiyuan,
detailRules: this.buildDetailRules(currentHuiyuan)
});
const huiyuanList = this.processHuiyuanList(res.data.data || []);
let index = 0;
const targetId = this.data.jumpParams.huiyuanId;
if (targetId) {
const found = huiyuanList.findIndex((h) => String(h.id) === String(targetId));
if (found >= 0) index = found;
}
this.applyCurrentHuiyuan(huiyuanList[index] || {}, index);
this.setData({ huiyuanList });
} else {
wx.showToast({
title: res.data.message || '获取会员列表失败',
@@ -211,7 +293,9 @@ onHide() {
return {
...item,
isBought: !!boughtItem,
buyInfo: boughtItem || null
buyInfo: boughtItem || null,
cardImageUrl: this.resolveOssUrl(item.card_image),
cardBgUrl: this.resolveOssUrl(item.card_bg),
};
});
},
@@ -220,43 +304,27 @@ onHide() {
onSwiperChange(e) {
const current = e.detail.current;
const huiyuanList = this.data.huiyuanList || [];
const currentHuiyuan = huiyuanList[current] || {};
this.setData({
currentHuiyuanIndex: current,
currentHuiyuan,
detailRules: this.buildDetailRules(currentHuiyuan)
});
this.applyCurrentHuiyuan(huiyuanList[current] || {}, current);
},
// ========== 手动选择会员 ==========
selectHuiyuan(e) {
const index = e.currentTarget.dataset.index;
const huiyuanList = this.data.huiyuanList || [];
const currentHuiyuan = huiyuanList[index] || {};
this.setData({
currentHuiyuanIndex: index,
currentHuiyuan,
detailRules: this.buildDetailRules(currentHuiyuan)
});
this.applyCurrentHuiyuan(huiyuanList[index] || {}, index);
},
buildDetailRules(huiyuan = {}) {
const formalDays = huiyuan.formal_days || 30;
const rules = [
`正式会员有效期${formalDays},从购买当天开始计算`,
'会员期间享受优先接单特权',
'支持随时续费,未过期续费天数叠加,已过期从今天重新计算'
`有效期${formalDays}`,
'可接多种订单类型',
'支持续费,天数叠加',
'正式会员支持余额抵扣',
];
if (huiyuan.can_buy_trial) {
rules.push(`体验会员终身仅可购买1次${huiyuan.trial_days || 0}天),体验期仅限制佣金提现`);
rules[3] = `体验版¥${huiyuan.trial_price}/${huiyuan.trial_days}`;
}
if (huiyuan.trial_enabled && !huiyuan.can_buy_trial) {
rules.push('您已使用过该会员的体验资格');
}
rules.push('体验会员仅支持微信支付;正式会员可使用佣金、分红或押金抵扣');
return rules;
return rules.slice(0, 4);
},
// ========== 格式化到期时间 ==========
@@ -310,109 +378,10 @@ onHide() {
// ========== 处理自动滚动 ==========
handleAutoScroll() {
const { jumpParams } = this.data;
if (!jumpParams || !jumpParams.needScroll) return;
if (!jumpParams || jumpParams.scrollTo !== 'jifen') return;
setTimeout(() => {
let scrollTop = 0;
if (jumpParams.scrollTo === 'bottom') {
scrollTop = 2000;
} else if (jumpParams.scrollTo === 'member') {
return;
}
if (scrollTop > 0) {
wx.pageScrollTo({
scrollTop: scrollTop,
duration: 300
});
}
}, 800);
},
// ========== 押金充值相关 ==========
showYajinModal() {
this.setData({
showYajinModal: true,
yajinAmount: '100'
});
},
hideYajinModal() {
this.setData({
showYajinModal: false
});
},
onYajinInput(e) {
let value = e.detail.value;
value = value.replace(/[^\d]/g, '');
if (value) {
const num = parseInt(value);
if (num < 1) value = '1';
if (num > 10000) value = '10000';
}
this.setData({
yajinAmount: value
});
},
setYajinAmount(e) {
const amount = e.currentTarget.dataset.amount;
this.setData({
yajinAmount: amount
});
},
async handleYajinPay() {
const amount = this.data.yajinAmount;
if (!amount || amount < 1 || amount > 10000) {
wx.showToast({
title: '请输入1-10000元的金额',
icon: 'none'
});
return;
}
this.showLoading('发起支付中...');
try {
const res = await request({
url: '/shangpin/yajingoumai',
method: 'POST',
data: {
jine: parseInt(amount)
}
});
if (res.data.code === 200) {
const payParams = res.data.payParams;
const dingdanid = res.data.dingdanid;
this.setData({
currentDingdanid: dingdanid
});
await this.wechatPay(payParams, 'yajin');
} else {
wx.showToast({
title: res.data.message || '支付发起失败',
icon: 'none'
});
}
} catch (error) {
console.error('押金支付失败:', error);
wx.showToast({
title: '支付失败,请重试',
icon: 'none'
});
} finally {
this.hideLoading();
}
wx.pageScrollTo({ selector: '#jifen-section', duration: 300 });
}, 600);
},
// ========== 积分充值相关 ==========
@@ -455,19 +424,8 @@ onHide() {
return;
}
if (isTrial) {
const { currentHuiyuan, huiyuanList } = this.data;
const target = (currentHuiyuan && currentHuiyuan.id === huiyuanId)
? currentHuiyuan
: (huiyuanList || []).find(item => item.id === huiyuanId);
if (target && !target.can_buy_trial) {
wx.showToast({
title: '您已使用过该会员的体验资格',
icon: 'none'
});
return;
}
}
const confirmed = await this.confirmWechatPurchase('huiyuan');
if (!confirmed) return;
this.showLoading('发起会员支付中...');
@@ -524,6 +482,9 @@ onHide() {
return;
}
const confirmed = await this.confirmWechatPurchase('jifen');
if (!confirmed) return;
this.showLoading('发起积分支付中...');
try {
@@ -834,12 +795,33 @@ onHide() {
return new Promise(resolve => setTimeout(resolve, ms));
},
/** 微信支付下单前确认(仅微信购买会员/押金/积分) */
confirmWechatPurchase(kind) {
const labelMap = {
huiyuan: '会员',
yajin: '押金',
jifen: '积分',
};
const label = labelMap[kind] || '服务';
return new Promise((resolve) => {
wx.showModal({
title: '确认微信支付',
content: `您即将通过微信支付购买${label}。请点击「确认购买」后才会发起支付;购买成功后不可随意退款,请确认无误后再继续。`,
confirmText: '确认购买',
cancelText: '再想想',
success: (res) => resolve(!!res.confirm),
fail: () => resolve(false),
});
});
},
// ========== 新增功能(余额抵扣等) ==========
onBuyHuiyuanClick(e) {
const huiyuanId = e.currentTarget.dataset.id;
const isTrial = e.currentTarget.dataset.trial === true || e.currentTarget.dataset.trial === 'true';
if (isTrial) {
this.setData({ currentBuyIsTrial: true });
this.handleHuiyuanBuy(e);
return;
}
@@ -848,25 +830,7 @@ onHide() {
currentBuyType: 1,
currentHuiyuanId: huiyuanId,
currentBuyIsTrial: false,
showPayMethodModal: true
});
},
onYajinClick() {
this.showYajinModal();
},
onYajinConfirm() {
const amount = this.data.yajinAmount;
if (!amount || amount < 1 || amount > 10000) {
wx.showToast({ title: '请输入1-10000元的金额', icon: 'none' });
return;
}
this.hideYajinModal();
this.setData({
currentBuyType: 2,
currentYajinAmount: amount,
showPayMethodModal: true
showPayMethodModal: true,
});
},
@@ -877,29 +841,20 @@ onHide() {
// 🔥 修改点2选择支付方式时再次校验积分状态防止异步过程中积分变化
onPayMethodSelect(e) {
const method = e.currentTarget.dataset.method;
const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data;
const { currentBuyType, currentHuiyuanId, jifen } = this.data;
this.hidePayMethodModal();
// 如果是积分购买,且积分已满,直接拒绝
if (currentBuyType === 3 && jifen >= 10) {
wx.showToast({
title: '积分已满,无需补充',
icon: 'none'
});
wx.showToast({ title: '积分已满,无需补充', icon: 'none' });
return;
}
if (method === 'wx') {
if (currentBuyType === 1) {
this.handleHuiyuanBuy({
currentTarget: { dataset: { id: currentHuiyuanId, trial: 'false' } }
});
} else if (currentBuyType === 2) {
this.setData({ yajinAmount: currentYajinAmount }, () => {
this.handleYajinPay();
currentTarget: { dataset: { id: currentHuiyuanId, trial: this.data.currentBuyIsTrial ? 'true' : 'false' } },
});
} else if (currentBuyType === 3) {
// ✅ 调用已修正的积分支付函数
this.handleJifenBuy({ currentTarget: { dataset: { disabled: 'false' } } });
}
} else if (method === 'balance') {
@@ -909,7 +864,7 @@ onHide() {
// 🔥 修改点3请求余额抵扣选项前校验积分状态
async fetchBalanceOptions() {
const { currentBuyType, currentHuiyuanId, currentYajinAmount, currentBuyIsTrial, jifen } = this.data;
const { currentBuyType, currentHuiyuanId, currentBuyIsTrial, jifen } = this.data;
if (currentBuyType === 1 && currentBuyIsTrial) {
wx.showToast({ title: '体验会员仅支持微信支付', icon: 'none' });
@@ -934,9 +889,8 @@ onHide() {
data: {
leixing: currentBuyType,
huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined,
is_trial: currentBuyType === 1 ? false : undefined,
yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined,
}
is_trial: currentBuyType === 1 ? currentBuyIsTrial : undefined,
},
});
if (res.data.code === 200) {
const options = res.data.data || [];
@@ -977,7 +931,7 @@ onHide() {
},
async confirmBalancePay() {
const { selectedBalanceId, currentBuyType, currentHuiyuanId, currentYajinAmount, currentBuyIsTrial } = this.data;
const { selectedBalanceId, currentBuyType, currentHuiyuanId, currentBuyIsTrial } = this.data;
if (!selectedBalanceId) return;
this.hideConfirmModal();
@@ -992,14 +946,13 @@ onHide() {
shenfen_id: selectedBalanceId,
huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined,
is_trial: currentBuyType === 1 ? currentBuyIsTrial : undefined,
yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined,
}
},
});
if (res.data.code === 200) {
const responseData = res.data.data || {};
await this.updateAfterPayment(
currentBuyType === 1 ? 'huiyuan' : (currentBuyType === 2 ? 'yajin' : 'jifen'),
currentBuyType === 1 ? 'huiyuan' : 'jifen',
currentBuyType === 1 ? currentHuiyuanId : null,
responseData
);

View File

@@ -1,12 +1,10 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
"global-notification": "/components/global-notification/global-notification",
"popup-notice": "/components/popup-notice/popup-notice"
},
"navigationBarTitleText": "充值中心",
"navigationBarBackgroundColor": "#0a0e2a",
"navigationBarTextStyle": "white",
"backgroundColor": "#0a0e2a",
"backgroundTextStyle": "light",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50
}
"navigationBarTitleText": "VIP",
"navigationBarBackgroundColor": "#FFF9F0",
"navigationBarTextStyle": "black",
"backgroundColor": "#FFF9F0"
}

View File

@@ -1,528 +1,210 @@
<!--pages/dashou-chongzhi/index.wxml-->
<view class="page-container">
<!-- ❌ 删除自定义导航栏区块 -->
<!-- VIP 会员 + 积分充值页 -->
<view class="vip-page">
<image wx:if="{{imgUrls.pageBg}}" class="page-bg" src="{{imgUrls.pageBg}}" mode="widthFix" />
<view class="page-bg-fallback" wx:else></view>
<!-- ======================= -->
<!-- VIP会员区域 - 放在上面 -->
<!-- ======================= -->
<view class="vip-section">
<view class="section-header">
<view class="header-decoration">
<view class="dec-line"></view>
<view class="dec-diamond"></view>
<text class="dec-text">VIP会员充值</text>
<view class="dec-diamond"></view>
<view class="dec-line"></view>
<view class="page-body">
<view class="user-row">
<image class="user-avatar" src="{{avatarUrl}}" mode="aspectFill" binderror="onAvatarError" />
<view class="user-info">
<text class="user-name">{{nickname || '打手'}}</text>
<text class="user-id">会员ID: {{uid || '--'}}</text>
</view>
<text class="section-subtitle">选择你的会员</text>
<view class="member-badge">{{memberBadge}}</view>
</view>
<!-- 会员卡片滑动区域 -->
<swiper
class="vip-swiper"
current="{{currentHuiyuanIndex}}"
circular="{{true}}"
previous-margin="120rpx"
next-margin="120rpx"
duration="500"
easing-function="easeOutCubic"
circular="{{huiyuanList.length > 1}}"
previous-margin="40rpx"
next-margin="40rpx"
duration="400"
bindchange="onSwiperChange">
<swiper-item wx:for="{{huiyuanList}}" wx:key="id">
<view class="vip-card {{index === currentHuiyuanIndex ? 'active' : ''}}"
data-index="{{index}}" bindtap="selectHuiyuan">
<!-- 机甲风格边框 -->
<view class="card-frame">
<view class="frame-corner tl"></view>
<view class="frame-corner tr"></view>
<view class="frame-corner bl"></view>
<view class="frame-corner br"></view>
<view class="frame-line top"></view>
<view class="frame-line right"></view>
<view class="frame-line bottom"></view>
<view class="frame-line left"></view>
<view class="vip-card {{index === currentHuiyuanIndex ? 'vip-card--active' : ''}}" data-index="{{index}}" bindtap="selectHuiyuan">
<image wx:if="{{item.cardBgUrl}}" class="vip-card-bg-img" src="{{item.cardBgUrl}}" mode="aspectFill" />
<view class="vip-card-bg" wx:else></view>
<view class="vip-card-inner">
<view class="vip-card-left">
<view class="vip-title-row">
<text class="vip-name">{{item.mingzi}}</text>
<view class="vip-status">{{item.isBought ? '已激活' : '未激活'}}</view>
</view>
<!-- 卡片内容 -->
<view class="card-content">
<view class="vip-badge" wx:if="{{item.isBought}}">
<text class="badge-text">已拥有</text>
<view class="vip-price-row">
<text class="vip-currency">¥</text>
<text class="vip-price">{{item.jiage}}</text>
</view>
<view class="vip-title">
<text class="title-text">{{item.mingzi}}</text>
<view class="title-underline"></view>
<text class="vip-validity">有效期 {{item.formal_days || 30}} 天</text>
</view>
<view class="vip-price">
<text class="price-symbol">¥</text>
<text class="price-number">{{item.jiage}}</text>
<text class="price-unit">/{{item.formal_days || 30}}天</text>
<view class="vip-card-right">
<view class="icon-clip icon-clip-vip" wx:if="{{item.cardImageUrl}}">
<image class="icon-clip-img" src="{{item.cardImageUrl}}" mode="aspectFill" />
</view>
<view class="vip-features">
<view class="feature-item" wx:for="{{item.features || ['特权功能', '专属标识', '优先接单']}}"
wx:key="index">
<image class="feature-icon" src="/static/images/check-tech.png"></image>
<text class="feature-text">{{item}}</text>
<view class="vip-logo-fallback" wx:else>
<text class="vip-logo-text">VIP</text>
</view>
</view>
</view>
<!-- 选中状态指示器 -->
<view class="card-indicator" wx:if="{{index === currentHuiyuanIndex}}">
<view class="indicator-triangle"></view>
<view class="indicator-glow"></view>
</view>
<view class="vip-shimmer"></view>
</view>
</swiper-item>
</swiper>
<!-- 会员详细介绍 -->
<view class="vip-detail">
<view class="detail-header">
<view class="detail-title">
<image class="detail-icon" src="/static/images/info-tech.png"></image>
<text class="title-text">{{currentHuiyuan.mingzi}} - 会员详情</text>
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">{{currentHuiyuan.mingzi || '会员'}}规则</text>
</view>
<view class="detail-tech"></view>
</view>
<view class="detail-content">
<scroll-view class="detail-scroll" scroll-y>
<text class="detail-text">{{currentHuiyuan.jieshao || '加载中...'}}</text>
<view class="detail-rules">
<view class="rule-item" wx:for="{{detailRules}}" wx:key="index">
<view class="rule-number">{{index + 1}}</view>
<scroll-view class="rules-scroll" scroll-x enable-flex>
<view class="rules-row">
<view wx:for="{{detailRules}}" wx:key="index" class="rule-card">
<view class="rule-num">{{index + 1}}</view>
<text class="rule-text">{{item}}</text>
</view>
</view>
</scroll-view>
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">{{currentHuiyuan.mingzi || '会员'}}专属权益</text>
</view>
<view class="benefits-row">
<view wx:for="{{memberBenefits}}" wx:key="title" class="benefit-card">
<view wx:if="{{item.iconUrl}}" class="icon-clip icon-clip-benefit">
<image class="icon-clip-img" src="{{item.iconUrl}}" mode="aspectFill" />
</view>
<text class="benefit-title">{{item.title}}</text>
<text class="benefit-desc">{{item.desc}}</text>
</view>
</view>
<!-- 购买区域 - 点击按钮先弹出支付方式选择 -->
<view class="buy-section">
<view class="buy-info">
<view class="price-display">
<!-- 🔥 将“机甲能源”改为“价格” -->
<text class="price-label">价格:</text>
<text class="price-value">¥{{currentHuiyuan.jiage}}</text>
<text class="price-days">/{{currentHuiyuan.formal_days || 30}}天</text>
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">平台优势</text>
</view>
<view class="advantage-grid">
<view wx:for="{{platformAdvantages}}" wx:key="title" class="advantage-item">
<view wx:if="{{item.iconUrl}}" class="icon-clip icon-clip-advantage">
<image class="icon-clip-img" src="{{item.iconUrl}}" mode="aspectFill" />
</view>
<text class="advantage-title">{{item.title}}</text>
<text class="advantage-desc">{{item.desc}}</text>
</view>
<text class="buy-desc" wx:if="{{currentHuiyuan.isBought && currentHuiyuan.buyInfo}}">
已激活,<text class="expire-date">{{currentHuiyuan.buyInfo.daoqi}}</text>后到期
</text>
<text class="buy-desc" wx:else>正式会员支持微信或余额抵扣</text>
<text class="buy-desc trial-hint" wx:if="{{currentHuiyuan.can_buy_trial}}">
体验版仅微信支付 ¥{{currentHuiyuan.trial_price}}/{{currentHuiyuan.trial_days}}天终身1次
</text>
</view>
<view class="buy-action">
<view class="tech-buy-btn {{currentHuiyuan.isBought ? 'renew' : ''}}"
bindtap="onBuyHuiyuanClick" data-id="{{currentHuiyuan.id}}" data-trial="false">
<view class="buy-btn-bg"></view>
<view class="buy-btn-glow"></view>
<text class="buy-btn-text">
{{currentHuiyuan.isBought ? '正式续费' : '购买正式版'}}
</text>
<view class="buy-btn-energy">
<view class="energy-dot"></view>
<view class="energy-dot"></view>
<view class="energy-dot"></view>
<view id="jifen-section" class="jifen-section">
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">积分充值</text>
</view>
<view class="jifen-card">
<view class="jifen-left">
<text class="jifen-label">当前积分</text>
<text class="jifen-value">{{jifen}}</text>
<text class="jifen-tip" wx:if="{{jifen < 10}}">积分不足将影响接单与提现</text>
<text class="jifen-tip jifen-tip--ok" wx:else>积分充足</text>
</view>
<view class="jifen-btn {{jifen >= 10 ? 'jifen-btn--disabled' : ''}}" bindtap="onJifenClick" data-disabled="{{jifen >= 10}}">
{{jifen >= 10 ? '已满' : '补充积分'}}
</view>
</view>
</view>
<view class="owned-section" wx:if="{{clubmber.length > 0}}">
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">我的会员</text>
</view>
<view wx:for="{{clubmber}}" wx:key="huiyuanid" class="owned-item" data-item="{{item}}" bindtap="showMemberDetail">
<text class="owned-name">{{item.huiyuanming}}</text>
<text class="owned-expire">到期 {{formatDaoqiTime(item.daoqi)}}</text>
</view>
</view>
</view>
<view class="footer-bar">
<view class="deposit-banner" bindtap="goToDeposit">
<text class="deposit-banner-text">缴纳履约金,快人一步</text>
<text class="deposit-banner-link">去缴纳 ></text>
</view>
<view class="agree-row" bindtap="toggleAgree">
<view class="agree-check {{agreed ? 'agree-check--on' : ''}}">{{agreed ? '✓' : ''}}</view>
<text class="agree-text">已阅读并同意</text>
<text class="agree-link">《{{currentHuiyuan.mingzi || '会员'}}协议》</text>
</view>
<view class="buy-row">
<view
wx:if="{{currentHuiyuan.can_buy_trial}}"
class="tech-buy-btn trial-buy-btn"
bindtap="onBuyHuiyuanClick"
class="buy-btn buy-btn--outline"
bindtap="onTapTrial"
data-id="{{currentHuiyuan.id}}"
data-trial="true"
>
<view class="buy-btn-bg"></view>
<text class="buy-btn-text">购买体验版</text>
</view>
</view>
</view>
</view>
</view>
<!-- ======================= -->
<!-- 三卡片区域 - 放在下面 -->
<!-- ======================= -->
<view class="tech-grid">
<!-- 会员区域 - 高度限制最多3个超出可滚动 -->
<view class="tech-card huiyuan-card">
<view class="card-header">
<view class="card-title">
<text class="title-text">我的会员</text>
<view class="title-text">👑</view>
</view>
<view class="card-badge">{{clubmber.length}}</view>
</view>
<scroll-view
class="huiyuan-scroll"
scroll-y
enable-flex
style="height: {{huiyuanListHeight}}rpx">
<view wx:for="{{clubmber}}" wx:key="huiyuanid" class="huiyuan-item" data-item="{{item}}" bindtap="showMemberDetail">
<view class="huiyuan-tag tech-tag">
<view class="tag-triangle"></view>
<text class="tag-name">{{item.huiyuanming}}</text>
<text class="tag-time">到期: {{formatDaoqiTime(item.daoqi)}}</text>
<view class="tag-glow"></view>
</view>
</view>
<view wx:if="{{clubmber.length === 0}}" class="empty-huiyuan">
<image class="empty-icon" src="/static/images/empty-member.png"></image>
<text class="empty-text">暂无会员</text>
</view>
</scroll-view>
<view class="card-footer">
<text class="footer-text">共{{clubmber.length}}个会员</text>
<view class="footer-line"></view>
</view>
</view>
<!-- 押金区域 - 点击充值按钮弹出押金输入弹窗 -->
<view class="tech-card yajin-card">
<view class="card-header">
<view class="card-title">
<text class="title-text">我的押金</text>
<view class="title-text">💰</view>
</view>
<view class="card-corner"></view>
</view>
<view class="yajin-display">
<view class="amount-container">
<text class="amount-symbol">¥</text>
<text class="amount-number">{{yajin}}</text>
</view>
<text class="amount-unit">可用余额</text>
</view>
<view class="tech-line"></view>
<view class="action-container">
<view class="tech-btn yajin-btn" bindtap="onYajinClick">
<view class="btn-bg"></view>
<text class="btn-text">立即充值</text>
<view class="btn-arrow" style="display:none;">➤</view>
<view class="btn-glow"></view>
</view>
</view>
<view class="card-corner corner-bottom"></view>
</view>
<!-- 积分区域 - 点击补充按钮弹出支付方式选择 -->
<view class="tech-card jifen-card">
<view class="card-header">
<view class="card-title">
<text class="title-text">我的积分</text>
<view class="title-text">⭐</view>
</view>
</view>
<view class="jifen-display">
<view class="circle-progress">
<view class="circle-bg"></view>
<view class="circle-mask" style="transform: rotate({{jifenProgress}}deg);"></view>
<view class="circle-fill" style="transform: rotate({{jifenProgress}}deg);"></view>
<view class="circle-center">
<text class="jifen-value">{{jifen}}</text>
<text class="jifen-label">积分</text>
</view>
<view class="circle-dots">
<view class="circle-dot" wx:for="{{10}}" wx:key="index"
style="transform: rotate({{index * 36}}deg);"></view>
</view>
</view>
<view class="jifen-status">
<text class="status-text" wx:if="{{jifen < 10}}">积分不足,影响接单,提现权限</text>
<text class="status-text full" wx:else>积分充足,无需补充</text>
</view>
</view>
<view class="action-container">
<view class="tech-btn jifen-btn" bindtap="onJifenClick" data-disabled="{{jifen >= 10}}">
<view class="btn-bg"></view>
<text class="btn-text">{{jifen >= 10 ? '积分已满' : '立即补充'}}</text>
<view class="btn-energy" wx:if="{{jifen < 10}}"></view>
<view class="btn-glow" wx:if="{{jifen < 10}}"></view>
</view>
</view>
</view>
</view>
<!-- ======================= -->
<!-- 押金充值弹窗(原有,确认事件改为 onYajinConfirm -->
<!-- ======================= -->
<view class="tech-modal" wx:if="{{showYajinModal}}">
<view class="modal-backdrop" bindtap="hideYajinModal"></view>
<view class="modal-container">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">押金充值</text>
<text class="modal-subtitle">充值保证金</text>
<view class="modal-close" bindtap="hideYajinModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="input-section">
<view class="input-label">
<image class="label-icon" src="/static/images/money-tech.png"></image>
<text class="label-text">充值金额 (1-10000元)</text>
</view>
<view class="input-container">
<view class="input-prefix">¥</view>
<input
class="yajin-input"
type="digit"
placeholder="输入充值金额"
placeholder-class="input-placeholder"
value="{{yajinAmount}}"
bindinput="onYajinInput"
maxlength="5"
focus="{{showYajinModal}}"/>
<view class="input-underline"></view>
</view>
<text class="input-tip">支持1-10000元整数充值</text>
</view>
<view class="quick-section">
<text class="quick-title">快速选择</text>
<view class="quick-buttons">
<view
class="quick-btn {{yajinAmount == '100' ? 'active' : ''}}"
bindtap="setYajinAmount"
data-amount="100">
<text class="quick-text">100元</text>
data-trial="true">
体验会员 (¥{{currentHuiyuan.trial_price}})
</view>
<view
class="quick-btn {{yajinAmount == '500' ? 'active' : ''}}"
bindtap="setYajinAmount"
data-amount="500">
<text class="quick-text">500元</text>
</view>
<view
class="quick-btn {{yajinAmount == '1000' ? 'active' : ''}}"
bindtap="setYajinAmount"
data-amount="1000">
<text class="quick-text">1000元</text>
</view>
<view
class="quick-btn {{yajinAmount == '5000' ? 'active' : ''}}"
bindtap="setYajinAmount"
data-amount="5000">
<text class="quick-text">5000元</text>
class="buy-btn buy-btn--primary {{currentHuiyuan.can_buy_trial ? '' : 'buy-btn--full'}}"
bindtap="onTapFormal"
data-id="{{currentHuiyuan.id}}"
data-trial="false">
{{currentHuiyuan.isBought ? '续费正式会员' : '升级正式会员'}} (¥{{currentHuiyuan.jiage}})
<view class="buy-btn-shimmer"></view>
</view>
</view>
</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel-btn" bindtap="hideYajinModal">
<text class="btn-text">取消</text>
</view>
<view class="modal-divider"></view>
<view class="modal-btn confirm-btn" bindtap="onYajinConfirm">
<text class="btn-text">确认支付</text>
<view class="btn-sparkle"></view>
</view>
</view>
</view>
</view>
<!-- ====== 支付方式选择弹窗 ====== -->
<view class="tech-modal" wx:if="{{showPayMethodModal}}">
<view class="modal-backdrop" bindtap="hidePayMethodModal"></view>
<view class="modal-container small">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">选择支付方式</text>
<view class="modal-close" bindtap="hidePayMethodModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="pay-method-list">
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="wx">
<image class="method-icon" src="/static/images/wechat-pay.png"></image>
<text class="method-name">微信支付</text>
<text class="method-desc">安全快捷</text>
</view>
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="balance">
<image class="method-icon" src="/static/images/balance-pay.png"></image>
<text class="method-name">余额抵扣</text>
<text class="method-desc">使用佣金或分红</text>
</view>
</view>
</view>
</view>
</view>
<!-- ====== 余额抵扣身份选择弹窗 ====== -->
<view class="tech-modal" wx:if="{{showBalanceModal}}">
<view class="modal-backdrop" bindtap="hideBalanceModal"></view>
<view class="modal-container">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">选择抵扣身份</text>
<view class="modal-close" bindtap="hideBalanceModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="balance-list">
<view wx:for="{{balanceOptions}}" wx:key="id" class="balance-item"
data-id="{{item.id}}" bindtap="onBalanceSelect">
<view class="balance-info">
<text class="balance-name">{{item.jieshao}}</text>
<text class="balance-amount">需¥{{item.xuyao}}</text>
</view>
<view class="balance-arrow">➤</view>
</view>
</view>
</view>
</view>
</view>
<!-- ====== 余额抵扣确认弹窗 ====== -->
<view class="tech-modal" wx:if="{{showConfirmModal}}">
<view class="modal-backdrop" bindtap="hideConfirmModal"></view>
<view class="modal-container">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">确认抵扣</text>
<view class="modal-close" bindtap="hideConfirmModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="confirm-detail">
<view class="detail-row">
<text class="detail-label">抵扣身份:</text>
<text class="detail-value">{{selectedBalanceInfo && selectedBalanceInfo.jieshao}}</text>
</view>
<view class="detail-row">
<text class="detail-label">所需金额:</text>
<text class="detail-value">¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}}</text>
</view>
<view class="detail-row">
<text class="detail-label">实际扣除:</text>
<text class="detail-value">¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}}</text>
</view>
<view class="detail-tip">确认后将从您的余额中扣除</view>
</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel-btn" bindtap="hideConfirmModal">
<text class="btn-text">取消</text>
</view>
<view class="modal-divider"></view>
<view class="modal-btn confirm-btn" bindtap="confirmBalancePay">
<text class="btn-text">确认支付</text>
<view class="btn-sparkle"></view>
</view>
</view>
</view>
</view>
<!-- ====== 会员详情弹窗 ====== -->
<view class="tech-modal" wx:if="{{showMemberModal}}">
<view class="modal-backdrop" bindtap="hideMemberModal"></view>
<view class="modal-container">
<view class="modal-corner tl"></view>
<view class="modal-corner tr"></view>
<view class="modal-corner bl"></view>
<view class="modal-corner br"></view>
<view class="modal-header">
<text class="modal-title">会员详情</text>
<view class="modal-close" bindtap="hideMemberModal">
<text class="close-text">✕</text>
</view>
</view>
<view class="modal-body">
<view class="member-detail">
<view class="detail-row">
<text class="detail-label">会员名称:</text>
<text class="detail-value">{{currentMemberDetail && currentMemberDetail.huiyuanming}}</text>
</view>
<view class="detail-row">
<text class="detail-label">到期时间:</text>
<text class="detail-value">{{currentMemberDetail && currentMemberDetail.daoqi}}</text>
</view>
<view class="detail-row">
<text class="detail-label">剩余天数:</text>
<text class="detail-value">{{formatRemainTime(currentMemberDetail && currentMemberDetail.daoqi)}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- ❌ 删除“进入提示弹窗”整个区块 -->
<!-- ======================= -->
<!-- 支付加载动画(原有) -->
<!-- ======================= -->
<view class="tech-loading" wx:if="{{payLoading}}">
<view class="loading-backdrop"></view>
<view class="loading-content">
<view class="loading-spinner">
<view class="spinner-ring ring-1"></view>
<view class="spinner-ring ring-2"></view>
<view class="spinner-ring ring-3"></view>
<view class="spinner-core"></view>
<view class="spinner-energy"></view>
</view>
<text class="loading-text">{{loadingText}}</text>
<text class="loading-subtext">正在连接支付系统...</text>
</view>
</view>
<!-- ======================= -->
<!-- 底部机甲装饰 -->
<!-- ======================= -->
<view class="tech-footer">
<view class="footer-line"></view>
<view class="footer-text">星阙网络 © 科技</view>
</view>
</view>
<!-- 通知组件 -->
<global-notification id="global-notification" />
<!-- 支付方式 -->
<view class="modal-mask" wx:if="{{showPayMethodModal}}" bindtap="hidePayMethodModal">
<view class="modal-panel" catchtap="stopPropagation">
<text class="modal-title">选择支付方式</text>
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="wx">
<text class="pay-method-name">微信支付</text>
<text class="pay-method-desc">安全快捷</text>
</view>
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="balance">
<text class="pay-method-name">余额抵扣</text>
<text class="pay-method-desc">使用佣金或分红</text>
</view>
</view>
</view>
<!-- 🆕 统一弹窗组件 -->
<view class="modal-mask" wx:if="{{showBalanceModal}}" bindtap="hideBalanceModal">
<view class="modal-panel" catchtap="stopPropagation">
<text class="modal-title">选择抵扣身份</text>
<view wx:for="{{balanceOptions}}" wx:key="id" class="balance-item" bindtap="onBalanceSelect" data-id="{{item.id}}">
<text>{{item.jieshao}}</text>
<text class="balance-need">需¥{{item.xuyao}}</text>
</view>
</view>
</view>
<view class="modal-mask" wx:if="{{showConfirmModal}}" bindtap="hideConfirmModal">
<view class="modal-panel" catchtap="stopPropagation">
<text class="modal-title">确认抵扣</text>
<view class="confirm-row"><text>身份</text><text>{{selectedBalanceInfo.jieshao}}</text></view>
<view class="confirm-row"><text>金额</text><text>¥{{selectedBalanceInfo.xuyao}}</text></view>
<view class="modal-actions">
<view class="modal-btn cancel" bindtap="hideConfirmModal">取消</view>
<view class="modal-btn confirm" bindtap="confirmBalancePay">确认支付</view>
</view>
</view>
</view>
<view class="modal-mask" wx:if="{{showMemberModal}}" bindtap="hideMemberModal">
<view class="modal-panel" catchtap="stopPropagation">
<text class="modal-title">会员详情</text>
<view class="confirm-row"><text>名称</text><text>{{currentMemberDetail.huiyuanming}}</text></view>
<view class="confirm-row"><text>到期</text><text>{{currentMemberDetail.daoqi}}</text></view>
<view class="confirm-row"><text>剩余</text><text>{{formatRemainTime(currentMemberDetail.daoqi)}}</text></view>
</view>
</view>
<view class="loading-mask" wx:if="{{payLoading}}">
<view class="loading-box">
<view class="loading-spinner"></view>
<text>{{loadingText}}</text>
</view>
</view>
<global-notification id="global-notification" />
<popup-notice id="popupNotice" />

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,25 @@
// pages/dashouzhongxin/dashouzhongxin.js
import { createPage, request, isRoleStatusActive, isCenterPageActive, syncRoleStatuses, syncProfileFields, syncGroupFields, ensureRoleOnCenterPage, clearCacheAndEnterNormal, lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole, parseSceneOptions } from '../../utils/base-page.js'
import { resolveAvatarUrl } from '../../utils/avatar.js'
import { fetchMyZhidingOrders, processZhidingList, filterZhidingBannerList } from '../../utils/zhiding-order.js'
import { openPrivateChat, buildInviterPeerId } from '../../utils/im-user.js'
import { isStaffMode, getStaffContext } from '../../utils/staff-api.js'
import { ensurePhoneAuth } from '../../utils/phone-auth.js'
import {
ICON_KEYS,
resolveMiniappIcon,
refreshPindaoConfig,
getPindaoConfig,
resolvePindaoImages,
warmupPindaoConfig,
PINDAO_ICON_FALLBACK,
defaultIconRel,
ICON_FOLDER,
} from '../../utils/miniapp-icons.js'
import { getDefaultAvatarUrl } from '../../utils/avatar.js'
const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC'
const app = getApp()
Page(createPage({
data: {
@@ -20,8 +34,7 @@ Page(createPage({
shangjiaCertified: false,
totalAssets: '0.00',
assetBreakdown: [],
pendingAuthList: [],
showAuthSection: false,
moreFuncList: [],
inviteCode: '',
guanshiInviteCode: '',
zuzhangInviteCode: '',
@@ -43,6 +56,7 @@ Page(createPage({
zuzhangInviterCache: null,
chenghaoList: [],
guanshiChenghaoList: [],
identityTagList: [],
gszhstatus: '',
yaoqingzongshu: 0,
fenyongzonge: '0.00',
@@ -58,7 +72,6 @@ Page(createPage({
tongguoZongshu: 0,
pendingShenheCount: 0,
layoutSparse: false,
scrollRefreshing: false,
rankBoardList: [
{ type: 'dashou', name: '接单员榜', sub: '接单成交', icon: '', shapeClass: 'rank-shape-left', themeClass: 'rank-theme-blue' },
{ type: 'guanshi', name: '管事榜', sub: '邀请收入', icon: '', shapeClass: 'rank-shape-right', themeClass: 'rank-theme-gold' },
@@ -68,95 +81,69 @@ Page(createPage({
statusBar: 20,
navBar: 44,
pindaoVisible: false,
pindaoTitle: '频道',
pindaoChannelNo: '',
examEnabled: false,
examPassed: false,
pindaoImages: [],
scrollViewRefreshing: false,
showRechargeBanners: false,
showPindaoEntry: true,
userBg: '',
qiepianBanner: '',
ksIcon: '',
penaltyTipIcon: '',
followExpanded: false,
followClass: '',
pendingPenaltyCount: 0,
myZhidingList: [],
myZhidingCount: 0,
orderNavXym: [],
tradeNavXym: [],
inviteNavXym: [],
},
_buildImgUrls(ossImageUrl) {
const dsBase = ossImageUrl + 'beijing/dashouduan/';
const iconBase = ossImageUrl + 'beijing/dashouduan/dashoutubiaobeijing/';
const gsBase = ossImageUrl + 'beijing/guanshiduan/';
const khBase = ossImageUrl + 'beijing/kaohe/';
const app = getApp();
const icon = (key, fallback) => resolveMiniappIcon(app, key, fallback || defaultIconRel(key));
return {
pageBg: dsBase + 'page-bg.jpg',
userCardBg: dsBase + 'user-card-bg.png',
vipCardBg: dsBase + 'vip-card-bg.jpg',
assetPanelBg: dsBase + 'asset-panel-bg.png',
rankHubBg: dsBase + 'rank-hub-bg.png',
rankTileBg: dsBase + 'rank-tile-bg.png',
funcPanelBg: dsBase + 'func-panel-bg.png',
listBg: dsBase + 'list-card-bg.png',
topBg: iconBase + 'top-bg.jpg',
cardBg: iconBase + 'card-bg.jpg',
iconRefresh: gsBase + 'icon-refresh.png',
iconCopy: gsBase + 'icon-copy.png',
iconVip: dsBase + 'icon-vip.png',
iconArrowLight: dsBase + 'icon-arrow-light.png',
iconSectionAsset: dsBase + 'icon-section-asset.png',
iconSectionRank: dsBase + 'icon-section-rank.png',
iconSectionFunc: dsBase + 'icon-section-func.png',
iconSectionGuanshi: dsBase + 'icon-section-guanshi.png',
iconSectionZuzhang: dsBase + 'icon-section-zuzhang.png',
iconSectionKaohe: dsBase + 'icon-section-kaohe.png',
iconSectionMore: dsBase + 'icon-section-more.png',
iconRankDashou: dsBase + 'rank-dashou.png',
iconRankGuanshi: dsBase + 'rank-guanshi.png',
iconRankZuzhang: dsBase + 'rank-zuzhang.png',
iconRankShangjia: dsBase + 'rank-shangjia.png',
iconGame: iconBase + 'icon-game.jpg',
iconRecharge: iconBase + 'icon-recharge.jpg',
iconOrders: iconBase + 'icon-orders.jpg',
iconPunish: iconBase + 'icon-punish.jpg',
iconRank: iconBase + 'icon-rank.jpg',
iconInvite: gsBase + 'icon-invite.png',
iconMedal: iconBase + 'icon-medal.jpg',
iconEdit: iconBase + 'icon-edit.jpg',
iconBook: iconBase + 'icon-book.jpg',
iconArrow: gsBase + 'icon-arrow.png',
iconSub: gsBase + 'icon-sub.png',
iconRecord: gsBase + 'icon-record.png',
iconPoster: gsBase + 'icon-poster.png',
iconWithdraw: gsBase + 'icon-withdraw.png',
iconContact: gsBase + 'icon-contact.png',
avatarDefault: ossImageUrl + 'avatar/default.jpg',
iconClear: ossImageUrl + 'beijing/tubiao/grzx_qingchu.jpg',
iconSwitch: '/images/_exit.png',
iconKefu: ossImageUrl + 'beijing/tubiao/grzx_kefu.jpg',
iconKuaishou: ossImageUrl + 'beijing/tubiao/grzx_guanzhualong.jpg',
iconKaoheDafen: khBase + 'daofen.png',
iconKaoheJilu: khBase + 'jilu.png',
iconKaoheZhongxin: khBase + 'zhongxin.png',
iconShangjia: ossImageUrl + 'beijing/tubiao/grzx_shangjia.jpg',
iconDashouAuth: ossImageUrl + 'beijing/tubiao/grzx_dashou.jpg',
iconGuanshiAuth: ossImageUrl + 'beijing/tubiao/grzx_guanshi.jpg',
iconZuzhangAuth: ossImageUrl + 'beijing/tubiao/grzx_zuzhang.jpg',
kefuBannerBg: dsBase + 'kefu-banner-bg.png',
totalAssetBg: dsBase + 'total-asset-bg.png',
authPanelBg: dsBase + 'auth-panel-bg.png',
bannerVip: 'https://bintao-1308403501.cos.ap-guangzhou.myqcloud.com/uploads/images/20260512123427531240452.png',
bannerDeposit: 'https://bintao-1308403501.cos.ap-guangzhou.myqcloud.com/uploads/images/202605121234283091d3980.png',
iconRefresh: icon(ICON_KEYS.ICON_REFRESH, 'beijing/guanshiduan/icon-refresh.png'),
iconCopy: icon(ICON_KEYS.FIGHTER_MINE_COPY, 'beijing/guanshiduan/icon-copy.png'),
iconEdit: icon(ICON_KEYS.FIGHTER_MINE_EDIT, 'beijing/dashouduan/dashoutubiaobeijing/icon-edit.jpg'),
iconPunish: icon(ICON_KEYS.FIGHTER_MINE_PUNISH, 'beijing/dashouduan/dashoutubiaobeijing/icon-punish.jpg'),
iconMedal: icon(ICON_KEYS.FIGHTER_MINE_MEDAL, 'beijing/dashouduan/dashoutubiaobeijing/icon-medal.jpg'),
iconBook: icon(ICON_KEYS.FIGHTER_MINE_BOOK, 'beijing/dashouduan/dashoutubiaobeijing/icon-book.jpg'),
iconRank: icon(ICON_KEYS.FIGHTER_MINE_RANK, 'beijing/dashouduan/dashoutubiaobeijing/icon-rank.jpg'),
iconInvite: icon(ICON_KEYS.FIGHTER_MINE_INVITE, 'beijing/guanshiduan/icon-invite.png'),
iconContact: icon(ICON_KEYS.FIGHTER_MINE_CONTACT, 'beijing/guanshiduan/icon-contact.png'),
iconKefu: icon(ICON_KEYS.FIGHTER_MINE_KEFU, 'beijing/tubiao/grzx_kefu.jpg'),
iconPindao: icon(ICON_KEYS.MINE_PINDAO, PINDAO_ICON_FALLBACK),
iconKaoheDafen: icon(ICON_KEYS.FIGHTER_MINE_KAOHE_DAFEN, 'beijing/kaohe/daofen.png'),
iconKaoheJilu: icon(ICON_KEYS.FIGHTER_MINE_KAOHE_JILU, 'beijing/kaohe/jilu.png'),
iconKaoheZhongxin: icon(ICON_KEYS.FIGHTER_MINE_KAOHE_ZHONGXIN, 'beijing/kaohe/zhongxin.png'),
iconShangjia: icon(ICON_KEYS.FIGHTER_MINE_SHANGJIA, 'beijing/tubiao/grzx_shangjia.jpg'),
iconDashouAuth: icon(ICON_KEYS.FIGHTER_MINE_DASHOU_AUTH, 'beijing/tubiao/grzx_dashou.jpg'),
iconClear: icon(ICON_KEYS.FIGHTER_MINE_CLEAR, 'beijing/tubiao/grzx_qingchu.jpg'),
iconSwitch: icon(ICON_KEYS.FIGHTER_MINE_SWITCH, `${ICON_FOLDER}/fighter_mine_switch.png`),
iconRecord: icon(ICON_KEYS.FIGHTER_MINE_RECORD, 'beijing/guanshiduan/icon-record.png'),
bannerVip: icon(ICON_KEYS.FIGHTER_RECHARGE_MEMBER, `${ICON_FOLDER}/fighter_recharge_member.png`),
bannerDeposit: icon(ICON_KEYS.FIGHTER_RECHARGE_DEPOSIT, `${ICON_FOLDER}/fighter_recharge_deposit.png`),
avatarDefault: getDefaultAvatarUrl(app),
};
},
_applyRankBoardIcons(imgUrls) {
const iconMap = {
dashou: imgUrls.iconRankDashou,
guanshi: imgUrls.iconRankGuanshi,
zuzhang: imgUrls.iconRankZuzhang,
shangjia: imgUrls.iconRankShangjia,
};
return (this.data.rankBoardList || []).map((item) => ({
...item,
icon: iconMap[item.type] || imgUrls.iconRank,
}));
},
onLoad(options) {
const app = getApp();
const sys = wx.getSystemInfoSync();
const ossImageUrl = app.globalData.ossImageUrl || '';
const imgUrls = this._buildImgUrls(ossImageUrl);
const nav = this._buildNavLists(app);
this.setData({
imgUrls,
rankBoardList: this._applyRankBoardIcons(imgUrls),
...nav,
statusBar: sys.statusBarHeight || 20,
navBar: (sys.statusBarHeight || 20) + 44,
});
@@ -180,11 +167,60 @@ Page(createPage({
}
}
this.checkColdStartPopup('dashouduan');
this.syncConfiguredAssets();
},
_buildNavLists(app) {
const icon = (key) => resolveMiniappIcon(app, key, defaultIconRel(key));
return {
orderNavXym: [
{ name: '待完成', statusKey: 'jinxingzhong', icon: icon(ICON_KEYS.FIGHTER_ORDER_PENDING), badge: 0, showBadge: false },
{ name: '待结算', statusKey: 'jiesuanzhong', icon: icon(ICON_KEYS.FIGHTER_ORDER_SETTLING), badge: 0, showBadge: false },
{ name: '已完成', statusKey: 'yiwancheng', icon: icon(ICON_KEYS.FIGHTER_ORDER_DONE), badge: 0, showBadge: false },
{ name: '已关闭', statusKey: 'yituikuan', icon: icon(ICON_KEYS.FIGHTER_ORDER_CLOSED), badge: 0, showBadge: false },
],
tradeNavXym: [
{ name: '佣金记录', type: 'commission', icon: icon(ICON_KEYS.FIGHTER_TRADE_COMMISSION) },
{ name: '提现记录', type: 'withdraw', icon: icon(ICON_KEYS.FIGHTER_TRADE_WITHDRAW) },
{ name: '结算记录', type: 'settle', icon: icon(ICON_KEYS.FIGHTER_TRADE_SETTLE) },
{ name: '扣款记录', type: 'deduct', icon: icon(ICON_KEYS.FIGHTER_TRADE_DEDUCT) },
{ name: '充值记录', type: 'recharge', icon: icon(ICON_KEYS.FIGHTER_TRADE_RECHARGE) },
],
inviteNavXym: [
{ name: '我的下级', action: 'subordinate', icon: icon(ICON_KEYS.FIGHTER_INVITE_SUBORDINATE) },
{ name: '推广海报', action: 'poster', icon: icon(ICON_KEYS.FIGHTER_INVITE_POSTER) },
{ name: '邀请码', action: 'inviteCode', icon: icon(ICON_KEYS.FIGHTER_INVITE_CODE) },
],
ksIcon: icon(ICON_KEYS.FIGHTER_KUAISHOU_CASH),
qiepianBanner: icon(ICON_KEYS.FIGHTER_MINE_QIEPIAN),
penaltyTipIcon: icon(ICON_KEYS.FIGHTER_MINE_PENALTY_TIP),
};
},
syncConfiguredAssets() {
const app = getApp();
const ossImageUrl = app.globalData.ossImageUrl || '';
const imgUrls = this._buildImgUrls(ossImageUrl);
const nav = this._buildNavLists(app);
const bannerVip = imgUrls.bannerVip;
const bannerDeposit = imgUrls.bannerDeposit;
this.setData({
imgUrls,
...nav,
showRechargeBanners: !!(bannerVip || bannerDeposit),
// showPindaoEntry: ... || this.data.isKaoheguan 临时隐藏考核官
showPindaoEntry: this.data.isDashou || this.data.isGuanshi || this.data.isZuzhang,
}, () => {
this._buildMoreFuncList();
});
},
async onShow() {
if (!app.globalData._dashouPhoneChecked) {
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
if (!phoneOk) return;
app.globalData._dashouPhoneChecked = true;
}
migrateLegacyCenterRole(getApp());
lockPrimaryRole('dashou', getApp());
@@ -196,11 +232,15 @@ Page(createPage({
this.setData({ inviterCache, zuzhangInviterCache });
this.registerNotificationComponent();
warmupPindaoConfig(app);
this.checkRoleStatuses();
this.applyCachedProfileFromGlobal();
this.syncConfiguredAssets();
this.loadPenaltyCount();
if (isCenterPageActive(this, 'isDashou', 'dashoustatus')) {
if (!this.data.isDashou) this.setData({ isDashou: true });
ensureRoleOnCenterPage(this, 'dashou');
this.loadExamStatus();
this.loadMyZhidingOrders();
} else if (isRoleStatusActive(wx.getStorageSync('guanshistatus')) ||
isRoleStatusActive(wx.getStorageSync('zuzhangstatus')) ||
isRoleStatusActive(wx.getStorageSync('kaoheguanstatus'))) {
@@ -221,6 +261,7 @@ Page(createPage({
}, 300);
}
}
if (app.startImWhenReady) app.startImWhenReady();
},
checkRoleStatuses() {
@@ -237,7 +278,7 @@ Page(createPage({
layoutSparse: subRoleCount === 0,
}, () => {
this._updateTotalAssets();
this._updatePendingAuth();
this._buildMoreFuncList();
});
if (isDashou) {
const uid = wx.getStorageSync('uid') || '';
@@ -263,97 +304,87 @@ Page(createPage({
total += this._parseMoney(d.ketixian);
breakdown.push({ label: '组长余额', amount: d.ketixian || '0.00' });
}
if (d.isKaoheguan) {
total += this._parseMoney(d.kaoheYue);
breakdown.push({ label: '考核余额', amount: d.kaoheYue || '0.00' });
}
// 临时隐藏考核官余额展示,恢复时取消注释
// if (d.isKaoheguan) {
// total += this._parseMoney(d.kaoheYue);
// breakdown.push({ label: '考核余额', amount: d.kaoheYue || '0.00' });
// }
this.setData({ totalAssets: total.toFixed(2), assetBreakdown: breakdown });
},
/** 打手端:已认证不展示;商家始终第一位 */
_updatePendingAuth() {
/** 更多功能:常用优先,按身份动态展示 */
_buildMoreFuncList() {
const d = this.data;
const img = d.imgUrls || {};
const list = [];
list.push({
type: 'shangjia',
name: '商家',
icon: img.iconShangjia,
tag: d.shangjiaCertified ? '进入商家端' : '去认证',
done: d.shangjiaCertified,
});
list.push({
type: 'staff',
name: '商家客服',
icon: img.iconKefu || img.iconShangjia,
tag: isStaffMode() ? '进入工作台' : '去入驻',
done: isStaffMode(),
});
if (!d.isGuanshi) {
list.push({ type: 'guanshi', name: '管事', icon: img.iconGuanshiAuth, tag: '去认证' });
}
if (!d.isZuzhang) {
list.push({ type: 'zuzhang', name: '组长', icon: img.iconZuzhangAuth, tag: '去认证' });
}
if (!d.isDashou) {
list.push({ type: 'dashou', name: '接单员', icon: img.iconDashouAuth, tag: '去认证' });
}
this.setData({
pendingAuthList: list,
showAuthSection: list.length > 0,
});
},
applyCachedProfileFromGlobal() {
const app = getApp();
const g = app.globalData;
const guanshi = g.guanshi || {};
const patch = {
dashouNicheng: g.dashouNicheng || this.data.dashouNicheng || '',
zhanghaoStatus: g.zhanghaoStatus ?? this.data.zhanghaoStatus,
yongjin: g.yongjin || this.data.yongjin || '0.00',
zonge: g.zonge || this.data.zonge || '0.00',
yajin: g.yajin || this.data.yajin || '0.00',
chenghao: g.chenghao || this.data.chenghao || '',
jinfen: g.jinfen || this.data.jinfen || '0',
clumber: g.clumber || this.data.clumber || [],
chengjiaoliang: g.chengjiaoliang || this.data.chengjiaoliang || '0',
zaixianZhuangtai: g.zaixianZhuangtai ?? this.data.zaixianZhuangtai ?? 0,
dashouzhuangtai: g.dashouzhuangtai ?? this.data.dashouzhuangtai,
gszhstatus: guanshi.gszhstatus || this.data.gszhstatus || '',
yaoqingzongshu: guanshi.yaoqingzongshu ?? this.data.yaoqingzongshu ?? 0,
fenyongzonge: guanshi.fenyongzonge || this.data.fenyongzonge || '0.00',
fenyongtixian: guanshi.fenyongtixian || this.data.fenyongtixian || '0.00',
const push = (action, name, icon, extra = {}) => {
list.push({ action, name, icon, ...extra });
};
this.setData(patch, () => this._updateTotalAssets());
if (d.isZuzhang) {
push('goToYaoqingGuanshi', '邀请管事', img.iconInvite);
}
push('contactInviter', '联系管事', img.iconInvite);
if (d.isGuanshi) {
push('contactZuzhang', '联系组长', img.iconContact);
}
push('goToKefu', '在线客服', img.iconKefu);
push('onTapShangjiaAuth', d.shangjiaCertified ? '进入商家端' : '商家认证', img.iconShangjia);
push('onTapStaffAuth', isStaffMode() ? '商家客服工作台' : '商家客服入驻', img.iconKefu || img.iconShangjia);
if (d.showPindaoEntry) {
push('openPindaoModal', '频道', img.iconPindao);
}
push('goToMyPunishment', '我的处罚', img.iconPunish);
// push('goToKaohe', '考核金牌', img.iconMedal); // 临时隐藏,恢复时取消注释
push('goToDashouExam', '接单考试', img.iconBook);
push('goToRanking', '总排行榜', img.iconRank, { rankType: 'dashou' });
// 会员记录 / 分红记录已合并至「佣金记录」页 Tab 切换
// 临时隐藏考核官入口,恢复时取消注释
// if (d.isKaoheguan) {
// push('goToKaoheDafen', '考核打分', img.iconKaoheDafen);
// push('goToKaoheJilu', '考核记录', img.iconKaoheJilu);
// push('goToKaoheZhongxin', '考核中心', img.iconKaoheZhongxin);
// }
if (!d.isDashou) {
push('goToAuth', '接单员认证', img.iconDashouAuth, { authType: 'dashou' });
}
push('goToRules', '用户规则', img.iconBook);
push('switchToNormal', '返回点单端', img.iconSwitch);
push('clearCache', '退出登录', img.iconClear);
this.setData({ moreFuncList: list });
},
async onPullDownRefresh() {
if (this._pullRefreshing) return;
this._pullRefreshing = true;
this.setData({ scrollRefreshing: true });
try {
this.checkRoleStatuses();
await Promise.allSettled([
this._fetchDashouInfoSilent(),
this.fetchChenghaoList(),
this._fetchGuanshiInfoSilent(),
this.fetchGuanshiChenghaoList(),
this._fetchZuzhangInfoSilent(),
this._fetchKaoheguanInfoSilent(),
]);
this.checkRoleStatuses();
wx.showToast({ title: '已更新', icon: 'success', duration: 1200 });
} catch (e) {
wx.showToast({ title: '刷新失败', icon: 'none' });
} finally {
this._pullRefreshing = false;
this.setData({ scrollRefreshing: false });
onMoreFuncTap(e) {
const { action, authType, rankType } = e.currentTarget.dataset;
if (action === 'goToAuth' && authType) {
this.goToAuth({ currentTarget: { dataset: { type: authType } } });
return;
}
if (action === 'goToRanking') {
this.goToRanking({ currentTarget: { dataset: { type: rankType || 'dashou' } } });
return;
}
if (action === 'onTapStaffAuth') {
this.onTapAuthItem({ currentTarget: { dataset: { type: 'staff' } } });
return;
}
if (action === 'onTapShangjiaAuth') {
this.onTapShangjiaAuth();
return;
}
if (typeof this[action] === 'function') {
this[action]();
}
},
async refreshAllInfo(showToast = true) {
this.throttledRefresh(async () => {
if (this._profileRefreshing) return;
this._profileRefreshing = true;
this.setData({ isLoading: true });
try {
this.checkRoleStatuses();
@@ -364,6 +395,8 @@ Page(createPage({
this.fetchGuanshiChenghaoList(),
this._fetchZuzhangInfoSilent(),
this._fetchKaoheguanInfoSilent(),
this.loadIdentityTags(),
this.loadMyZhidingOrders(),
]);
this.checkRoleStatuses();
const dashouOk = results[0].status === 'fulfilled';
@@ -379,9 +412,9 @@ Page(createPage({
wx.showToast({ title: '刷新失败', icon: 'none' });
}
} finally {
this._profileRefreshing = false;
this.setData({ isLoading: false });
}
}, 3000);
},
refreshDashouInfo() {
@@ -392,6 +425,34 @@ Page(createPage({
this.refreshAllInfo(true);
},
onPullDownRefresh() {
this.setData({ scrollViewRefreshing: true });
Promise.all([
this.refreshAllInfo(false),
this.loadExamStatus(),
]).finally(() => {
this.setData({ scrollViewRefreshing: false });
wx.stopPullDownRefresh();
});
},
async loadIdentityTags() {
try {
const res = await request({ url: '/jituan/identity-tag/my-tags', method: 'POST' });
if (res && res.data && (res.data.code === 0 || res.data.code === 200)) {
const d = res.data.data || {};
const merged = [
...(d.dashou || []),
...(d.zuzhang || []),
...(d.guanshi || []),
];
this.setData({ identityTagList: merged });
}
} catch (e) {
/* 静默 */
}
},
async _fetchDashouInfoSilent() {
const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' });
if (res && res.data.code == 200) {
@@ -685,12 +746,15 @@ Page(createPage({
goToInviteUser() { wx.navigateTo({ url: '/pages/invite-fighter/invite-fighter' }); },
goToMySubordinate() { wx.navigateTo({ url: '/pages/my-fighter/my-fighter' }); },
goToRechargeRecord() { wx.navigateTo({ url: '/pages/recharge-log/recharge-log' }); },
goToRechargeRecord() { wx.navigateTo({ url: '/pages/recharge-log/recharge-log?tab=guanshi' }); },
goToCommissionLog(tab = 'dashou') {
wx.navigateTo({ url: `/pages/recharge-log/recharge-log?tab=${tab}` });
},
goToGuanshiRanking() {
wx.navigateTo({ url: '/pages/fighter-rank/fighter-rank?type=guanshi' });
},
goToPromoPoster() { wx.navigateTo({ url: '/pages/poster/poster' }); },
goToFenhongJilu() { wx.navigateTo({ url: '/pages/leader-bonus-log/leader-bonus-log' }); },
goToFenhongJilu() { wx.navigateTo({ url: '/pages/recharge-log/recharge-log?tab=zuzhang' }); },
goToYaoqingGuanshi() { wx.navigateTo({ url: '/pages/invite-manager/invite-manager' }); },
goToGuanshiAuthPage() { wx.navigateTo({ url: '/pages/verify/verify?type=guanshi' }); },
@@ -737,7 +801,7 @@ Page(createPage({
}
},
// 联系邀请人
// 联系管事(邀请人
async contactInviter() {
wx.showLoading({ title: '获取邀请人...', mask: true });
try {
@@ -1105,15 +1169,174 @@ Page(createPage({
}
wx.navigateTo({ url: '/pages/verify/verify?type=shangjia' });
},
goToReceiveOrder() { wx.navigateTo({ url: '/pages/accept-order/accept-order' }) },
goToMyOrders() { wx.navigateTo({ url: '/pages/fighter-orders/fighter-orders' }) },
goToReceiveOrder() { wx.switchTab({ url: '/pages/accept-order/accept-order' }) },
async loadMyZhidingOrders() {
if (!isCenterPageActive(this, 'isDashou', 'dashoustatus')) return;
try {
const raw = await fetchMyZhidingOrders();
const uid = this.data.uid || wx.getStorageSync('uid');
const list = filterZhidingBannerList(processZhidingList(raw, getApp(), uid));
this.setData({ myZhidingList: list, myZhidingCount: list.length });
} catch (e) {
console.warn('fighter loadMyZhidingOrders', e);
}
},
goToZhidingFromMine() {
const app = getApp();
const first = (this.data.myZhidingList || [])[0];
if (first && first.dingdan_id) {
app.globalData._acceptOrderHighlight = first.dingdan_id;
}
wx.switchTab({
url: '/pages/accept-order/accept-order',
fail: () => wx.showToast({ title: '跳转失败', icon: 'none' }),
});
},
goToDashouExam() { wx.navigateTo({ url: '/pages/dashou-exam/dashou-exam' }) },
goToMyOrders() { wx.switchTab({ url: '/pages/fighter-orders/fighter-orders' }) },
goToOrdersWithStatus(e) {
const statusKey = e.currentTarget.dataset.status;
if (statusKey) {
getApp().globalData._fighterOrdersStatusKey = statusKey;
}
wx.switchTab({ url: '/pages/fighter-orders/fighter-orders' });
},
goToAllOrders() {
getApp().globalData._fighterOrdersStatusKey = 'all';
wx.switchTab({ url: '/pages/fighter-orders/fighter-orders' });
},
goToTradeNav(e) {
const type = e.currentTarget.dataset.type;
if (type === 'commission') {
this.goToCommissionLog('dashou');
return;
}
if (type === 'recharge') {
if (this.data.isGuanshi) {
this.goToCommissionLog('guanshi');
} else {
this.onComingSoon();
}
return;
}
if (type === 'withdraw') {
wx.navigateTo({ url: '/pages/withdraw/withdraw-records' });
return;
}
if (type === 'deduct') {
wx.navigateTo({ url: '/pages/fighter-ledger/fighter-ledger?type=deduct' });
return;
}
wx.navigateTo({ url: `/pages/fighter-ledger/fighter-ledger?type=${type}` });
},
goToWalletDetail() {
this.goToCommissionLog('dashou');
},
onInviteNavTap(e) {
if (!this.data.isGuanshi) {
this.goToGuanshiAuthPage();
return;
}
const action = e.currentTarget.dataset.action;
if (action === 'subordinate') {
this.goToMySubordinate();
return;
}
if (action === 'poster') {
this.goToPromoPoster();
return;
}
if (action === 'inviteCode') {
this.goToInviteUser();
}
},
onComingSoon() {
wx.showToast({ title: '该功能待开发', icon: 'none' });
},
toggleFollowKuaishou() {
const next = !this.data.followExpanded;
this.setData({ followExpanded: next, followClass: next ? 'expanded' : '' });
},
onQiepianTap() {
this.onComingSoon();
},
async loadPenaltyCount() {
if (!this.data.isDashou && !this.data.isGuanshi) return;
try {
const res = await request({ url: '/yonghu/cffktjhq', method: 'POST' });
const body = res?.data;
if (body && (body.code === 200 || body.code === 0) && body.data) {
const d = body.data;
const cnt = Number(d.daichuli_fakuan || d.fakuan_wait || d.pending || 0);
this.setData({ pendingPenaltyCount: cnt > 0 ? cnt : 0 });
}
} catch (e) {
/* ignore */
}
},
goToMyPunishment() { wx.navigateTo({ url: '/pages/penalty/penalty/penalty' }) },
goToRecharge() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' }) },
async loadExamStatus() {
try {
const res = await request({ url: '/jituan/dashou-exam/status', method: 'POST' });
const body = res?.data;
if (body && (body.code === 200 || body.code === 0) && body.data) {
const d = body.data;
this.setData({
examEnabled: !!d.exam_enabled,
examPassed: !!d.exam_passed,
});
}
} catch (e) { /* 静默 */ }
},
goToRecharge() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' }); },
goToRechargeMember() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' }); },
goToRechargeDeposit() { wx.navigateTo({ url: '/pages/fighter-recharge/fighter-deposit' }); },
goToRanking(e) {
const type = e?.currentTarget?.dataset?.type || 'dashou';
wx.navigateTo({ url: `/pages/fighter-rank/fighter-rank?type=${type}` });
},
goToGuanzhuKs() { wx.navigateTo({ url: '/pages/manager-assign/manager-assign' }) },
goToGuanzhuKs() {
wx.navigateTo({ url: '/pages/manager-assign/manager-assign' });
},
openPindaoModal() {
const app = getApp();
const cfg = getPindaoConfig(app);
const images = resolvePindaoImages(app);
this.setData({
pindaoVisible: true,
pindaoTitle: cfg.title || '频道',
pindaoChannelNo: cfg.channel_no || '',
pindaoImages: images,
});
refreshPindaoConfig(app).then(({ cfg: latest, images: latestImages }) => {
if (!this.data.pindaoVisible) return;
this.setData({
pindaoTitle: latest.title || '频道',
pindaoChannelNo: latest.channel_no || '',
pindaoImages: latestImages,
});
}).catch(() => {});
},
closePindaoModal() {
this.setData({ pindaoVisible: false });
},
goToKaoheDafen() { wx.navigateTo({ url: '/pages/assess-score/assess-score' }) },
goToKaoheJilu() { wx.navigateTo({ url: '/pages/assess-log/assess-log' }) },
goToKaoheZhongxin() { wx.navigateTo({ url: '/pages/assess-center/assess-center' }) },

View File

@@ -2,10 +2,11 @@
"usingComponents": {
"global-notification": "/components/global-notification/global-notification",
"chenghao-tag": "/components/chenghao-tag/chenghao-tag",
"tab-bar": "/tab-bar/index"
"tab-bar": "/tab-bar/index",
"pindao-modal": "/components/pindao-modal/pindao-modal"
},
"navigationStyle": "custom",
"backgroundColor": "#f5f3ff",
"backgroundColor": "#fff8e1",
"backgroundTextStyle": "dark",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50

View File

@@ -14,7 +14,7 @@
<view class="btn-bg unreg-btn" bindtap="onRegister"><text class="unreg-btn-text">立即注册</text></view>
<view class="unreg-btn-secondary" bindtap="quickRegisterAsPlatform"><text>平台直签注册</text></view>
<view class="unreg-link" bindtap="switchToNormal"><text>返回点单端</text></view>
<view class="unreg-link" bindtap="clearCache"><text>清除缓存,重新登录</text></view>
<view class="unreg-link" bindtap="clearCache"><text>退出登录</text></view>
</view>
</view>
<tab-bar/>
@@ -23,14 +23,12 @@
<!-- 已注册 -->
<block wx:else>
<view class="user-page">
<view class="user-page xym-user">
<view class="page-header">
<view class="status-bar" style="height:{{statusBar}}px"></view>
<view class="nav-bar" style="height:{{navBar - statusBar}}px"><text class="nav-title">我的</text></view>
<view class="nav-bar" style="height:{{navBar - statusBar}}px">
<text class="nav-title">我的</text>
</view>
<view class="refresh-float" bindtap="onTapRefresh">
<image class="refresh-float-ico" src="{{imgUrls.iconRefresh}}" mode="aspectFit"/>
</view>
<scroll-view
@@ -41,11 +39,10 @@
refresher-enabled="{{true}}"
refresher-threshold="80"
refresher-default-style="black"
refresher-background="#f5f3ff"
refresher-triggered="{{scrollRefreshing}}"
refresher-background="#fff8e1"
refresher-triggered="{{scrollViewRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
>
<!-- 用户信息 -->
<view class="user-info flexb">
<view class="flex">
<image class="avatar" src="{{avatarUrl || imgUrls.avatarDefault}}" mode="aspectFill" bindtap="previewAvatar"/>
@@ -57,20 +54,23 @@
<text class="role-tag">接单员</text>
<text class="role-tag" wx:if="{{isGuanshi}}">管事</text>
<text class="role-tag" wx:if="{{isZuzhang}}">组长</text>
<text class="role-tag" wx:if="{{isKaoheguan}}">考核</text>
<!-- 临时隐藏考核身份标签,恢复时取消注释 -->
<!-- <text class="role-tag" wx:if="{{isKaoheguan}}">考核</text> -->
</view>
</view>
<view class="sn-row" bindtap="copyUid">
<view class="sn-row flex" bindtap="copyUid">
<text class="sn">ID: {{uid || '--'}}</text>
<image class="copy-ico" src="{{imgUrls.iconCopy}}" mode="aspectFit"/>
</view>
</view>
</view>
<view class="user-header-actions">
<image class="header-action-ico" src="{{imgUrls.iconRefresh}}" mode="aspectFit" bindtap="onTapRefresh"/>
<image class="setting-ico" src="{{imgUrls.iconEdit}}" mode="aspectFit" bindtap="goToModifyInfo"/>
</view>
</view>
<!-- 称号 -->
<view class="badge-zone" wx:if="{{chenghaoList.length > 0 || (isGuanshi && guanshiChenghaoList.length > 0)}}">
<view class="badge-zone" wx:if="{{chenghaoList.length > 0 || (isGuanshi && guanshiChenghaoList.length > 0) || identityTagList.length > 0}}">
<scroll-view scroll-x class="badge-scroll" wx:if="{{chenghaoList.length > 0}}">
<view class="badge-tag-wrap" wx:for="{{chenghaoList}}" wx:key="index">
<chenghao-tag mingcheng="{{item.mingcheng}}" texiaoJson="{{item.texiao_json}}"/>
@@ -81,11 +81,15 @@
<chenghao-tag mingcheng="{{item.mingcheng}}" texiaoJson="{{item.texiao_json}}"/>
</view>
</scroll-view>
<scroll-view scroll-x class="badge-scroll identity-tag-scroll" wx:if="{{identityTagList.length > 0}}">
<view class="badge-tag-wrap" wx:for="{{identityTagList}}" wx:key="id">
<chenghao-tag mingcheng="{{item.mingcheng}}" texiaoJson="{{item.texiao_json}}"/>
</view>
</scroll-view>
</view>
<!-- 佣金/资产卡(逍遥梦 wallet-card -->
<view class="wallet-card flexb">
<view class="wallet-left">
<view bindtap="goToWalletDetail">
<view class="w-label">我的佣金(元)</view>
<view class="w-num">¥{{totalAssets}}</view>
<view class="w-sub">可提现 ¥{{yongjin || '0.00'}} · 查看</view>
@@ -96,13 +100,19 @@
</view>
</view>
<!-- 会员充值 + 保证金(均跳转同一充值页) -->
<view class="banner-row flex">
<image class="banner-img" src="{{imgUrls.bannerVip}}" mode="widthFix" bindtap="goToRecharge"/>
<image class="banner-img" src="{{imgUrls.bannerDeposit}}" mode="widthFix" bindtap="goToRecharge"/>
<view class="banner-row flex" wx:if="{{showRechargeBanners}}">
<image wx:if="{{imgUrls.bannerVip}}" class="banner-img" src="{{imgUrls.bannerVip}}" mode="widthFix" bindtap="goToRechargeMember"/>
<image wx:if="{{imgUrls.bannerDeposit}}" class="banner-img" src="{{imgUrls.bannerDeposit}}" mode="widthFix" bindtap="goToRechargeDeposit"/>
</view>
<view class="recharge-text-row flex" wx:else>
<view class="recharge-text-btn" catchtap="goToRechargeMember">充值会员</view>
<view class="recharge-text-btn" catchtap="goToRechargeDeposit">充值保证金</view>
</view>
<view class="qiepian-wrap" bindtap="onQiepianTap">
<image class="qiepian-banner" src="{{qiepianBanner}}" mode="widthFix"/>
</view>
<!-- 资产统计(逍遥梦 freeze-row -->
<view class="freeze-row flexa">
<view class="freeze-col flexmc">
<text class="freeze-num">{{jinfen || '0'}}</text>
@@ -118,125 +128,77 @@
</view>
</view>
<!-- 角色扩展统计(从左往右排列,不均分撑满) -->
<view class="stats-bar" wx:if="{{isGuanshi || isZuzhang || isKaoheguan}}">
<view class="stat-cell" wx:if="{{isGuanshi}}"><text class="stat-val">{{yaoqingzongshu || 0}}</text><text class="stat-lbl">邀请总数</text></view>
<view class="stat-cell" wx:if="{{isGuanshi}}"><text class="stat-val">{{yichongzhiDashou || 0}}</text><text class="stat-lbl">已充值</text></view>
<view class="stat-cell" wx:if="{{isGuanshi}}"><text class="stat-val">{{fenyongzonge || '0.00'}}</text><text class="stat-lbl">分成总额</text></view>
<view class="stat-cell" wx:if="{{isGuanshi}}"><text class="stat-val">{{zhuanhualv || 0}}%</text><text class="stat-lbl">有效率</text></view>
<view class="stat-cell" wx:if="{{isZuzhang}}"><text class="stat-val">{{guanshiCount || 0}}</text><text class="stat-lbl">邀请管事</text></view>
<view class="stat-cell" wx:if="{{isZuzhang}}"><text class="stat-val">{{fenhongZonge || '0.00'}}</text><text class="stat-lbl">分红总额</text></view>
<view class="stat-cell" wx:if="{{isKaoheguan}}"><text class="stat-val">{{shenheZongshu || 0}}</text><text class="stat-lbl">审核总数</text></view>
<view class="stat-cell" wx:if="{{isKaoheguan}}"><text class="stat-val">{{tongguoZongshu || 0}}</text><text class="stat-lbl">通过总数</text></view>
<view class="stat-cell" wx:if="{{isKaoheguan}}"><text class="stat-val">{{pendingShenheCount || 0}}</text><text class="stat-lbl">待审核</text></view>
<view wx:if="{{pendingPenaltyCount > 0}}" class="tip-bar flexb" bindtap="goToMyPunishment">
<view class="flex">
<image class="tip-ico" src="{{penaltyTipIcon}}" mode="aspectFit"/>
<text>你有{{pendingPenaltyCount}}条罚单待处理, 前往处理</text>
</view>
<text class="tip-arrow"></text>
</view>
<!-- 接单服务(对应逍遥梦「我的订单」四宫格) -->
<view class="panel shadow">
<view class="panel-title flexb"><text class="lg">接单服务</text></view>
<view class="order-nav flexa">
<view class="nav-item flexmc" bindtap="goToReceiveOrder">
<image class="nav-icon" src="{{imgUrls.iconGame}}" mode="aspectFit"/><text class="sm">抢单大厅</text>
<view class="my-box shadow order-nav-box">
<view class="panel-title flexb" bindtap="goToAllOrders">
<text class="lg">我的订单</text>
<text class="muted">全部订单 </text>
</view>
<view class="nav-item flexmc" bindtap="goToMyOrders">
<image class="nav-icon" src="{{imgUrls.iconOrders}}" mode="aspectFit"/><text class="sm">我的订单</text>
<view class="order-nav">
<view wx:for="{{orderNavXym}}" wx:key="name" class="nav-item" data-status="{{item.statusKey}}" bindtap="goToOrdersWithStatus">
<view class="icon-wrap">
<view wx:if="{{item.showBadge && item.badge > 0}}" class="nav-badge">{{item.badge}}</view>
<image class="nav-icon" src="{{item.icon}}" mode="aspectFit"/>
</view>
<view class="nav-item flexmc" bindtap="goToMyPunishment">
<image class="nav-icon" src="{{imgUrls.iconPunish}}" mode="aspectFit"/><text class="sm">我的处罚</text>
</view>
<view class="nav-item flexmc" bindtap="goToKaohe">
<image class="nav-icon" src="{{imgUrls.iconMedal}}" mode="aspectFit"/><text class="sm">考核金牌</text>
<text class="sm">{{item.name}}</text>
</view>
</view>
</view>
<!-- 荣耀排行榜(横向滑动,省空间) -->
<view class="rank-hub panel shadow">
<view class="panel-title flexb">
<text class="lg">荣耀排行榜</text>
<text class="rank-hub-tip">TOP50</text>
<view class="my-box shadow trade-box">
<view class="section-hd">交易明细</view>
<view class="trade-list">
<view wx:for="{{tradeNavXym}}" wx:key="name" class="trade-item" data-type="{{item.type}}" bindtap="goToTradeNav">
<image class="trade-icon" src="{{item.icon}}" mode="aspectFit"/>
<text class="sm">{{item.name}}</text>
</view>
<scroll-view scroll-x class="rank-hub-scroll" enhanced show-scrollbar="{{false}}">
<view class="rank-hub-track">
</view>
</view>
<view class="my-box shadow invite-box">
<view class="section-hd">好友邀请</view>
<view class="invite-row">
<view wx:for="{{inviteNavXym}}" wx:key="name" class="invite-item" data-action="{{item.action}}" bindtap="onInviteNavTap">
<image class="invite-icon" src="{{item.icon}}" mode="aspectFit"/>
<text class="sm">{{item.name}}</text>
</view>
</view>
</view>
<view class="my-box shadow more-box">
<view class="section-hd">更多功能</view>
<view class="func-list">
<view
wx:for="{{rankBoardList}}"
wx:key="type"
class="rank-tile {{item.themeClass}}"
bindtap="goToRanking"
data-type="{{item.type}}"
wx:for="{{moreFuncList}}"
wx:key="action"
class="func-item-xym"
bindtap="onMoreFuncTap"
data-action="{{item.action}}"
data-auth-type="{{item.authType}}"
data-rank-type="{{item.rankType}}"
>
<view class="rank-tile-inner">
<image class="rank-tile-icon" src="{{item.icon}}" mode="aspectFit"/>
<view class="rank-tile-text">
<text class="rank-tile-name">{{item.name}}</text>
<text class="rank-tile-sub">{{item.sub}}</text>
</view>
<text class="rank-tile-go">进入</text>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 管事功能 -->
<view class="panel shadow" wx:if="{{isGuanshi}}">
<view class="panel-title"><text class="lg">管事功能</text></view>
<view class="func-grid">
<view class="func-item flexmc" bindtap="goToInviteUser"><image class="func-icon" src="{{imgUrls.iconInvite}}" mode="aspectFit"/><text class="func-txt">邀请用户</text></view>
<view class="func-item flexmc" bindtap="goToMySubordinate"><image class="func-icon" src="{{imgUrls.iconSub}}" mode="aspectFit"/><text class="func-txt">我的下级</text></view>
<view class="func-item flexmc" bindtap="goToRechargeRecord"><image class="func-icon" src="{{imgUrls.iconRecord}}" mode="aspectFit"/><text class="func-txt">会员记录</text></view>
<view class="func-item flexmc" bindtap="goToPromoPoster"><image class="func-icon" src="{{imgUrls.iconPoster}}" mode="aspectFit"/><text class="func-txt">推广海报</text></view>
<view class="func-item flexmc" bindtap="contactZuzhang"><image class="func-icon" src="{{imgUrls.iconContact}}" mode="aspectFit"/><text class="func-txt">联系组长</text></view>
</view>
</view>
<!-- 组长功能 -->
<view class="panel shadow" wx:if="{{isZuzhang}}">
<view class="panel-title"><text class="lg">组长功能</text></view>
<view class="func-grid">
<view class="func-item flexmc" bindtap="goToFenhongJilu"><image class="func-icon" src="{{imgUrls.iconRecord}}" mode="aspectFit"/><text class="func-txt">分红记录</text></view>
<view class="func-item flexmc" bindtap="goToYaoqingGuanshi"><image class="func-icon" src="{{imgUrls.iconInvite}}" mode="aspectFit"/><text class="func-txt">邀请管事</text></view>
</view>
</view>
<!-- 考核官功能 -->
<view class="panel shadow" wx:if="{{isKaoheguan}}">
<view class="panel-title"><text class="lg">考核官功能</text></view>
<view class="func-grid">
<view class="func-item flexmc" bindtap="goToKaoheDafen"><image class="func-icon" src="{{imgUrls.iconKaoheDafen}}" mode="aspectFit"/><text class="func-txt">考核打分</text></view>
<view class="func-item flexmc" bindtap="goToKaoheJilu"><image class="func-icon" src="{{imgUrls.iconKaoheJilu}}" mode="aspectFit"/><text class="func-txt">考核记录</text></view>
<view class="func-item flexmc" bindtap="goToKaoheZhongxin"><image class="func-icon" src="{{imgUrls.iconKaoheZhongxin}}" mode="aspectFit"/><text class="func-txt">考核中心</text></view>
<view class="func-item flexmc" bindtap="goToKaoheWithdraw"><image class="func-icon" src="{{imgUrls.iconWithdraw}}" mode="aspectFit"/><text class="func-txt">考核提现</text></view>
</view>
</view>
<!-- 身份认证 -->
<view class="panel shadow" wx:if="{{showAuthSection}}">
<view class="panel-title"><text class="lg">身份认证</text></view>
<view class="func-grid">
<view class="func-item flexmc" wx:for="{{pendingAuthList}}" wx:key="type" bindtap="onTapAuthItem" data-type="{{item.type}}">
<image class="func-icon" src="{{item.icon}}" mode="aspectFit"/>
<text class="func-txt">{{item.name}}</text>
<text class="func-tag {{item.done ? 'func-tag-done' : ''}}">{{item.tag}}</text>
<view class="func-icon-wrap"><image class="func-icon-xym" src="{{item.icon}}" mode="aspectFit"/></view>
<text class="func-txt-xym">{{item.name}}</text>
</view>
</view>
</view>
<!-- 更多功能 -->
<view class="panel shadow">
<view class="panel-title"><text class="lg">更多功能</text></view>
<view class="func-grid">
<view class="func-item flexmc" bindtap="goToKefu"><image class="func-icon" src="{{imgUrls.iconKefu}}" mode="aspectFit"/><text class="func-txt">在线客服</text></view>
<view class="func-item flexmc" bindtap="goToGuanzhuKs"><image class="func-icon" src="{{imgUrls.iconKuaishou}}" mode="aspectFit"/><text class="func-txt">关注快手</text></view>
<view class="func-item flexmc" bindtap="contactInviter"><image class="func-icon" src="{{imgUrls.iconInvite}}" mode="aspectFit"/><text class="func-txt">联系邀请人</text></view>
<view class="func-item flexmc" bindtap="goToRules"><image class="func-icon" src="{{imgUrls.iconBook}}" mode="aspectFit"/><text class="func-txt">用户规则</text></view>
<view class="func-item flexmc" bindtap="switchToNormal"><image class="func-icon" src="{{imgUrls.iconSwitch}}" mode="aspectFit"/><text class="func-txt">返回点单端</text></view>
<view class="func-item flexmc" bindtap="clearCache"><image class="func-icon" src="{{imgUrls.iconClear}}" mode="aspectFit"/><text class="func-txt">清除缓存</text></view>
</view>
</view>
<view class="load-tip">— 已加载全部 —</view>
<view class="content-bottom"></view>
</scroll-view>
<view class="follow-kuaishou {{followClass}}" bindtap="toggleFollowKuaishou">
<image class="ks-icon" src="{{ksIcon}}" mode="aspectFit"/>
<text wx:if="{{followExpanded}}" class="ks-text" catchtap="goToGuanzhuKs">关注快手</text>
</view>
<tab-bar/>
</view>
</block>
@@ -247,3 +209,10 @@
<global-notification id="global-notification"/>
<popup-notice id="popupNotice"/>
<pindao-modal
visible="{{pindaoVisible}}"
title="{{pindaoTitle}}"
channelNo="{{pindaoChannelNo}}"
images="{{pindaoImages}}"
bind:close="closePindaoModal"
/>

View File

@@ -1,7 +1,8 @@
/* 打手端个人中心 - 逍遥梦 UI逻辑不变 */
@import '../../styles/dashou-xym-user.wxss';
page {
background: #f5f3ff;
background: #fff8e1;
color: #333;
}
@@ -16,7 +17,7 @@ page {
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
background: linear-gradient(180deg, #c4b5fd 0%, #fff 28%, #f5f3ff 100%);
background: linear-gradient(180deg, #f7dc51 0%, #fff 28%, #fff8e1 100%);
position: relative;
}
@@ -24,6 +25,7 @@ page {
position: relative;
z-index: 2;
flex-shrink: 0;
background: linear-gradient(180deg, #f7dc51 0%, #ffd061 100%);
}
.status-bar,
@@ -40,6 +42,19 @@ page {
justify-content: center;
}
.nav-bar-actions {
position: relative;
justify-content: center;
}
.nav-refresh-ico {
position: absolute;
right: 24rpx;
width: 44rpx;
height: 44rpx;
padding: 8rpx;
}
.nav-title {
font-size: 34rpx;
font-weight: 700;
@@ -52,10 +67,6 @@ page {
padding-bottom: 0;
}
.content-bottom {
height: 12rpx;
}
.refresh-float {
position: fixed;
right: 24rpx;
@@ -112,7 +123,7 @@ page {
font-size: 20rpx;
padding: 2rpx 12rpx;
border-radius: 8rpx;
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
color: #492f00;
}
@@ -134,6 +145,20 @@ page {
opacity: 0.6;
}
.user-header-actions {
display: flex;
align-items: center;
gap: 16rpx;
flex-shrink: 0;
}
.header-action-ico {
width: 48rpx;
height: 48rpx;
padding: 6rpx;
flex-shrink: 0;
}
.setting-ico {
width: 53rpx;
height: 53rpx;
@@ -169,7 +194,7 @@ page {
margin: 15rpx 30rpx 0;
padding: 24rpx 20rpx;
border-radius: 26rpx;
background: #ede9fe;
background: #fef6d4;
align-items: center;
}
@@ -210,7 +235,7 @@ page {
font-weight: 700;
border: 2rpx solid #fff;
border-radius: 60rpx;
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
color: #492f00;
white-space: nowrap;
}
@@ -233,11 +258,28 @@ page {
margin-right: 2%;
}
.recharge-text-row {
margin: 20rpx 24rpx 0;
gap: 16rpx;
}
.recharge-text-btn {
flex: 1;
text-align: center;
padding: 22rpx 12rpx;
border-radius: 16rpx;
font-size: 28rpx;
font-weight: 600;
color: #492f00;
background: linear-gradient(180deg, #fff8e1, #ffe8b8);
border: 1rpx solid rgba(201, 169, 98, 0.45);
}
.freeze-row {
margin: 16rpx 30rpx 0;
padding: 20rpx 10rpx;
border-radius: 15rpx;
background: #ede9fe;
background: #fef6d4;
}
.freeze-col {
@@ -259,7 +301,7 @@ page {
}
.btn-bg {
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
border-radius: 30px;
color: #492f00;
}
@@ -300,39 +342,6 @@ page {
font-weight: 700;
}
.stats-bar {
margin: 12rpx 30rpx 0;
padding: 16rpx 16rpx 8rpx;
background: #fdfcfa;
border-radius: 16rpx;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-content: flex-start;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.04);
}
.stat-cell {
width: 25%;
padding: 8rpx 0 12rpx;
box-sizing: border-box;
text-align: center;
}
.stat-val {
display: block;
font-size: 28rpx;
font-weight: 700;
color: #222;
}
.stat-lbl {
display: block;
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
.panel {
margin: 20rpx 20rpx 12rpx;
background: #fdfcfa;
@@ -356,11 +365,35 @@ page {
.order-nav {
padding: 10rpx 0 20rpx;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
}
.nav-item {
width: 25%;
width: 20%;
box-sizing: border-box;
padding: 10rpx 0;
position: relative;
}
.exam-tag {
font-size: 20rpx;
margin-top: 4rpx;
padding: 2rpx 10rpx;
border-radius: 8rpx;
}
.exam-tag-pass {
color: #52c41a;
background: rgba(82, 196, 26, 0.12);
}
.exam-tag-pending {
color: #fa8c16;
background: rgba(250, 140, 16, 0.12);
}
.nav-icon {
@@ -405,7 +438,7 @@ page {
}
.func-tag-done {
color: #9333ea;
color: #c9a962;
}
.rank-hub-tip {
@@ -484,7 +517,7 @@ page {
}
.rank-theme-gold {
background: linear-gradient(135deg, #a8842a 0%, #c4b5fd 100%);
background: linear-gradient(135deg, #a8842a 0%, #f5d563 100%);
}
.rank-theme-gold .rank-tile-name,
@@ -498,7 +531,7 @@ page {
}
.rank-theme-orange {
background: linear-gradient(135deg, #7c3aed 0%, #c4b5fd 100%);
background: linear-gradient(135deg, #d4652a 0%, #f5a962 100%);
}
/* 未注册 */
@@ -576,7 +609,7 @@ page {
width: 60rpx;
height: 60rpx;
border: 4rpx solid rgba(0, 0, 0, 0.08);
border-top-color: #9333ea;
border-top-color: #ffd061;
border-radius: 50%;
animation: fighterSpin 0.8s linear infinite;
}

View File

@@ -69,20 +69,20 @@ page {
/* 火苗图标燃烧动画 */
.fire-icon {
animation: fireBurn 0.8s infinite alternate ease-in-out;
filter: drop-shadow(0 0 10rpx #9333ea);
filter: drop-shadow(0 0 10rpx #ffaa00);
}
@keyframes fireBurn {
0% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #9333ea); }
25% { transform: scale(1.1) rotate(2deg); filter: drop-shadow(0 0 20rpx #9333ea); }
50% { transform: scale(1.05) rotate(-2deg); filter: drop-shadow(0 0 30rpx #9333ea); }
75% { transform: scale(1.15) rotate(3deg); filter: drop-shadow(0 0 40rpx #9333ea); }
100% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #9333ea); }
0% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #ffaa00); }
25% { transform: scale(1.1) rotate(2deg); filter: drop-shadow(0 0 20rpx #ff5500); }
50% { transform: scale(1.05) rotate(-2deg); filter: drop-shadow(0 0 30rpx #ffaa00); }
75% { transform: scale(1.15) rotate(3deg); filter: drop-shadow(0 0 40rpx #ffaa00); }
100% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #ffaa00); }
}
.jinpai-title {
font-size: 40rpx;
font-weight: bold;
color: #9333ea;
text-shadow: 0 0 30rpx #9333ea, 0 0 60rpx #9333ea;
color: #ffaa00;
text-shadow: 0 0 30rpx #ffaa00, 0 0 60rpx #ffaa00;
letter-spacing: 2rpx;
}
.refresh-btn {
@@ -255,7 +255,7 @@ page {
display: flex;
align-items: center;
background: rgba(255,255,255,0.1);
border: 2rpx solid #9333ea;
border: 2rpx solid #ffaa00;
clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
padding: 15rpx 30rpx;
position: relative;
@@ -269,7 +269,7 @@ page {
.withdraw-text {
font-size: 32rpx;
font-weight: bold;
color: #9333ea;
color: #ffaa00;
}
.withdraw-glow {
position: absolute;
@@ -491,7 +491,7 @@ page {
content: '';
width: 100rpx;
height: 100rpx;
border: 4rpx solid #9333ea;
border: 4rpx solid #ffaa00;
border-radius: 50%;
border-bottom-color: transparent;
animation: spin 1.5s reverse infinite;

View File

@@ -2,7 +2,15 @@ const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { sendGroupMessage } from '../../utils/message-sender';
import { getLocalImUserId } from '../../utils/im-user.js';
import { resolveAvatarUrl } from '../../utils/avatar.js';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js';
import { getZhuangtaiText, isPairGroupId, normalizeGroupMessage, recordConversationOpen } from '../../utils/group-chat.js';
import request from '../../utils/request';
import {
fetchHistoryMessages,
getOldestHistoryTimestamp,
mergeHistoryMessages,
waitChatImReady,
} from '../../utils/chat-history.js';
Page({
data: {
@@ -11,6 +19,10 @@ Page({
groupAvatar: '',
orderId: '',
isCross: 0,
orderZhuangtai: null,
orderZhuangtaiText: '',
orderJieshao: '',
orderJine: '',
currentUser: null,
messages: [],
inputText: '',
@@ -30,7 +42,8 @@ Page({
pendingImage: '',
pendingImageFile: null,
keyboardHeight: 0,
bottomSafeHeight: 10
bottomSafeHeight: 140,
defaultAvatarUrl: '',
},
onLoad(options) {
let p = { groupId: '', groupName: '订单群聊', groupAvatar: '', orderId: '', isCross: 0,
@@ -41,37 +54,79 @@ Page({
p = { ...p, ...d };
} catch (e) {}
}
const orderIdStr = String(p.orderId || '').replace(/^group_/, '') ||
(p.groupId && !isPairGroupId(p.groupId) ? String(p.groupId).replace(/^group_/, '') : '');
const groupId = p.groupId
? String(p.groupId)
: (orderIdStr ? `group_${orderIdStr}` : '');
const zt = p.orderZhuangtai;
const defAvatar = getDefaultAvatarUrl(app);
this.setData({
groupId: p.groupId,
groupId,
groupName: p.groupName,
groupAvatar: p.groupAvatar,
orderId: p.orderId,
isCross: p.isCross
groupAvatar: resolveAvatarUrl(p.groupAvatar, app) || defAvatar,
orderId: orderIdStr,
isCross: p.isCross,
orderJieshao: p.orderDesc || p.orderJieshao || '',
orderJine: p.orderJine || '',
orderZhuangtai: zt,
orderZhuangtaiText: zt != null ? getZhuangtaiText(zt) : '',
defaultAvatarUrl: defAvatar,
});
wx.setNavigationBarTitle({ title: this.data.groupName });
if (p.currentUserId) {
this.setData({
currentUser: {
id: p.currentUserId,
name: p.currentUserName || `用户${p.currentUserId.replace(/^[A-Za-z]+/,'')}`,
avatar: resolveAvatarUrl(p.currentUserAvatar, app)
}
});
} else {
this.ensureCurrentUser(p);
},
ensureCurrentUser(p) {
p = p || {};
const localImId = getLocalImUserId(app);
const uid = wx.getStorageSync('uid') || '';
const userId = localImId || p.currentUserId || '';
if (!userId) {
this.initCurrentUser();
return;
}
const defAvatar = getDefaultAvatarUrl(app);
const rawAvatar = p.currentUserAvatar
|| wx.getStorageSync('touxiang')
|| app.globalData.currentUser?.avatar
|| '';
const name = p.currentUserName
|| app.globalData.currentUser?.name
|| wx.getStorageSync('nicheng')
|| app.globalData.dashouNicheng
|| app.globalData.nicheng
|| `用户${uid}`;
const avatar = resolveAvatarUrl(rawAvatar, app) || defAvatar;
const currentUser = { id: userId, name, avatar: avatar || defAvatar };
app.globalData.currentUser = { ...app.globalData.currentUser, ...currentUser };
this.setData({ currentUser });
},
getMyChatProfile() {
const cu = this.data.currentUser;
const defAvatar = getDefaultAvatarUrl(app);
if (cu && cu.id) {
return {
id: cu.id,
name: cu.name || `用户${wx.getStorageSync('uid') || ''}`,
avatar: resolveAvatarUrl(cu.avatar, app) || defAvatar,
};
}
const imId = getLocalImUserId(app);
const uid = wx.getStorageSync('uid') || '';
return {
id: imId || '',
name: wx.getStorageSync('nicheng') || app.globalData.nicheng || `用户${uid}`,
avatar: resolveAvatarFromStorage(app) || defAvatar,
};
},
initCurrentUser() {
const imId = getLocalImUserId(app);
const uid = wx.getStorageSync('uid');
this.setData({
currentUser: {
id: imId || ('Boss' + uid),
name: app.globalData.currentUser?.name || `用户${uid}`,
avatar: resolveAvatarUrl(wx.getStorageSync('touxiang'), app),
},
});
const profile = this.getMyChatProfile();
if (!profile.id) return;
this.setData({ currentUser: profile });
app.globalData.currentUser = { ...app.globalData.currentUser, ...profile };
},
buildImageFile(tempPath, size) {
@@ -84,14 +139,99 @@ Page({
normalizeMessage(msg) {
if (!msg) return msg;
msg = normalizeGroupMessage(msg);
if (!msg.payload) msg.payload = {};
if (msg.type === 'image') {
const p = msg.payload;
if (!p.url) p.url = p.thumbnail || p.thumb || p.imageUrl || '';
const defAvatar = getDefaultAvatarUrl(app);
const me = this.getMyChatProfile();
if (me.id && msg.senderId === me.id) {
msg.senderData = {
name: me.name,
avatar: me.avatar || defAvatar,
};
} else if (msg.senderData?.avatar) {
msg.senderData.avatar = resolveAvatarUrl(msg.senderData.avatar, app) || defAvatar;
} else if (msg.senderData) {
msg.senderData.avatar = defAvatar;
}
if (msg.type === 'image' && msg.payload?.url && !String(msg.payload.url).startsWith('http')) {
msg.payload.url = resolveAvatarUrl(msg.payload.url, app) || msg.payload.url;
}
return msg;
},
mapIdentityForApi() {
const role = app.globalData.currentRole || wx.getStorageSync('currentRole') || 'normal';
if (role === 'dashou') return 'dashou';
if (role === 'shangjia') return 'shangjia';
return 'normal';
},
async refreshLatestOrderBar() {
const { groupId, orderId } = this.data;
if (!groupId && !orderId) return;
try {
const res = await request({
url: '/dingdan/ltpddd',
method: 'POST',
data: {
groupId,
dingdan_id: orderId,
identityType: this.mapIdentityForApi(),
},
});
const body = res?.data || {};
const latest = body.data?.latest;
if (!latest) return;
this.setData({
orderId: latest.dingdan_id,
orderJieshao: latest.jieshao || '',
orderJine: latest.jine || '',
orderZhuangtai: latest.zhuangtai,
orderZhuangtaiText: getZhuangtaiText(latest.zhuangtai),
});
} catch (e) {
console.warn('刷新最新订单条失败', e);
}
},
async refreshOrderStatus() {
const { orderId } = this.data;
if (!orderId) return;
try {
const res = await request({
url: '/dingdan/ltddzt',
method: 'POST',
data: {
dingdan_id: orderId,
identityType: this.mapIdentityForApi(),
},
});
const body = res?.data || {};
const st = body.data;
if (!st) return;
this.setData({
orderJieshao: st.jieshao || this.data.orderJieshao,
orderJine: st.jine || this.data.orderJine,
orderZhuangtai: st.zhuangtai,
orderZhuangtaiText: getZhuangtaiText(st.zhuangtai),
});
} catch (e) {
console.warn('刷新订单状态失败', e);
}
},
startOrderStatusPoll() {
this.stopOrderStatusPoll();
this._orderPollTimer = setInterval(() => this.refreshOrderStatus(), 15000);
},
stopOrderStatusPoll() {
if (this._orderPollTimer) {
clearInterval(this._orderPollTimer);
this._orderPollTimer = null;
}
},
mergeSentMessage(localId, sent) {
if (!sent) {
this.updateMsg(localId, 'success');
@@ -112,54 +252,90 @@ Page({
},
onShow() {
const defAvatar = getDefaultAvatarUrl(app);
this.setData({
defaultAvatarUrl: defAvatar,
groupAvatar: resolveAvatarUrl(this.data.groupAvatar, app) || defAvatar,
});
this.ensureCurrentUser({});
if (this.data.groupId) {
if (!app.globalData.pageState) app.globalData.pageState = {};
app.globalData.pageState.lastOpenedConvId = this.data.groupId;
recordConversationOpen({ groupId: this.data.groupId });
}
this.refreshLatestOrderBar();
this.startOrderStatusPoll();
this.autoConnect();
},
onHide() {
if (this.data.groupId) {
recordConversationOpen({ groupId: this.data.groupId });
}
this.stopOrderStatusPoll();
this.clearAllListeners();
},
autoConnect() {
const targetUserId = getLocalImUserId(app);
onUnload() {
this.stopOrderStatusPoll();
},
async autoConnect() {
const targetUserId = this.data.currentUser?.id || getLocalImUserId(app);
if (!targetUserId) return;
const afterReady = async () => {
await this.subscribeGroupIfNeeded();
this.setupAllListeners();
await this.loadHistory(true);
this.markGroupMessageAsRead();
};
try {
const ok = await waitChatImReady(app, 12000);
if (ok) {
await afterReady();
return;
}
} catch (e) {
console.warn('[群聊] IM 连接失败', e);
}
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
if (currentUserId === targetUserId) {
this.subscribeGroupIfNeeded();
this.setupAllListeners();
this.loadHistory(true);
await afterReady();
return;
} else {
wx.goEasy.disconnect();
}
wx.goEasy.disconnect();
}
this.connectGoEasy(targetUserId);
},
connectGoEasy(userId) {
const { currentUser } = this.data;
if (!currentUser) return;
const me = this.getMyChatProfile();
if (!me.id) return;
const defAvatar = getDefaultAvatarUrl(app);
wx.goEasy.connect({
id: userId,
data: {
name: currentUser.name,
avatar: resolveAvatarUrl(currentUser.avatar, app),
name: me.name,
avatar: me.avatar || defAvatar,
},
onSuccess: () => {
this.subscribeGroupIfNeeded();
this.subscribeGroupIfNeeded().then(() => {
this.setupAllListeners();
this.loadHistory(true).then(()=>{
this.markGroupMessageAsRead
this.loadHistory(true);
this.markGroupMessageAsRead();
});
},
onFailed: (error) => {
if (error.code === 408) {
this.subscribeGroupIfNeeded();
this.subscribeGroupIfNeeded().then(() => {
this.setupAllListeners();
this.loadHistory(true);
});
} else {
wx.showToast({ title: '连接失败,请重试', icon: 'none' });
}
@@ -169,11 +345,16 @@ Page({
subscribeGroupIfNeeded() {
const { groupId } = this.data;
if (!groupId) return;
if (!groupId || !wx.goEasy?.im?.subscribeGroup) return Promise.resolve();
return new Promise((resolve) => {
wx.goEasy.im.subscribeGroup({
groupIds: [groupId],
onSuccess: () => {},
onFailed: () => {}
groupIds: [String(groupId)],
onSuccess: () => resolve(true),
onFailed: (error) => {
console.warn('subscribeGroup failed', error);
resolve(false);
},
});
});
},
@@ -197,55 +378,93 @@ Page({
onPullDownRefresh() {
if (this.data.loadingHistory) return;
if (!this.data.hasMore) {
wx.showToast({ title: '没有更早的消息了', icon: 'none' });
this.setData({ isRefreshing: false });
wx.stopPullDownRefresh();
return;
}
this.setData({ isRefreshing: true });
this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); });
this.loadHistory(false).finally(() => {
this.setData({ isRefreshing: false });
wx.stopPullDownRefresh();
});
},
loadHistory(refresh) {
if (!refresh && !this.data.hasMore) return Promise.resolve();
return new Promise((resolve) => {
this.setData({ loadingHistory: true });
const { groupId, lastTimestamp } = this.data;
const ts = refresh ? null : lastTimestamp;
wx.goEasy.im.history({
type: wx.GoEasy.IM_SCENE.GROUP,
id: groupId,
lastTimestamp: ts,
limit: 20,
onSuccess: (res) => {
let list = res.content || [];
list.sort((a, b) => a.timestamp - b.timestamp);
list.forEach((m, idx) => {
mapHistoryMessage(m, idx, list) {
m = this.normalizeMessage(m);
list[idx] = m;
m.formattedTime = formatDate(m.timestamp);
if (idx === 0) m.showTime = true;
else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5;
else m.showTime = (m.timestamp - list[idx - 1].timestamp) / 60000 > 5;
if (!m.senderData) m.senderData = { name: '未知用户', avatar: '' };
return m;
},
async fetchGroupHistoryPage(gid, lastTimestamp) {
return fetchHistoryMessages({
scene: wx.GoEasy.IM_SCENE.GROUP,
id: gid,
lastTimestamp,
limit: 20,
});
let final = refresh ? list : [...list, ...this.data.messages];
if (!refresh) {
for (let i = 0; i < final.length; i++) {
if (i === 0) final[i].showTime = true;
else final[i].showTime = (final[i].timestamp - final[i-1].timestamp) / 60000 > 5;
},
async loadHistory(refresh) {
if (!refresh && !this.data.hasMore) return;
if (this.data.loadingHistory) return;
if (!this.data.groupId) return;
this.setData({ loadingHistory: true });
try {
const ready = await waitChatImReady(app, 12000);
if (!ready) {
wx.showToast({ title: '消息服务未连接,请稍后重试', icon: 'none' });
return;
}
await this.subscribeGroupIfNeeded();
const ts = refresh
? null
: getOldestHistoryTimestamp(this.data.messages, this.data.lastTimestamp);
if (!refresh && ts == null) {
this.setData({ hasMore: false });
wx.showToast({ title: '没有更早的消息了', icon: 'none' });
return;
}
let raw = await this.fetchGroupHistoryPage(this.data.groupId, ts);
if (refresh && raw.length === 0 && isPairGroupId(this.data.groupId) && this.data.orderId) {
const legacyId = `group_${this.data.orderId}`;
if (legacyId !== this.data.groupId) {
raw = await this.fetchGroupHistoryPage(legacyId, ts);
}
}
const ids = new Set();
const unique = [];
for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } }
const merged = mergeHistoryMessages({
incoming: raw,
existing: this.data.messages,
refresh,
limit: 20,
mapMessage: (m, idx, list) => this.mapHistoryMessage(m, idx, list),
});
const update = {
messages: unique,
hasMore: list.length >= 20,
lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp,
loadingHistory: false
messages: merged.messages,
hasMore: merged.hasMore,
lastTimestamp: merged.lastTimestamp,
};
if (refresh) update.scrollToView = 'msg-bottom';
this.setData(update);
resolve();
},
onFailed: () => { this.setData({ loadingHistory: false }); resolve(); }
});
});
} catch (e) {
console.warn('[群聊] 历史消息加载失败', e);
if (!refresh) {
wx.showToast({ title: '加载历史消息失败', icon: 'none' });
}
} finally {
this.setData({ loadingHistory: false });
}
},
listenNewMsg() {
@@ -531,7 +750,9 @@ Page({
orderId: order.dingdan_id,
jieshao: order.jieshao,
jine: order.jine,
beizhu: order.beizhu
beizhu: order.beizhu,
zhuangtai: order.zhuangtai,
nicheng: order.nicheng,
};
sendGroupMessage({
msgData: {
@@ -555,6 +776,10 @@ Page({
},
onSuccess: (messageId, status) => {
that.updateMsg(messageId, status);
that.closeOrderSender();
if (status === 'success') {
wx.showToast({ title: '订单已发送', icon: 'success' });
}
}
});
},
@@ -566,5 +791,24 @@ Page({
this.setData({ detailText: text, showDetailModal: true });
},
onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); }
onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); },
onAvatarError(e) {
const def = getDefaultAvatarUrl(app);
const idx = e.currentTarget.dataset.index;
if (idx != null && idx !== '') {
this.setData({ [`messages[${idx}].senderData.avatar`]: def });
}
},
onSelfAvatarError() {
const def = getDefaultAvatarUrl(app);
if (this.data.currentUser) {
this.setData({ 'currentUser.avatar': def });
}
},
tapOrderBar() {
this.openOrderSender();
},
});

View File

@@ -1,6 +1,7 @@
{
"navigationBarTitleText": "群聊",
"usingComponents": {
"order-card": "/components/order-card/order-card"
"order-card": "/components/order-card/order-card",
"order-sender": "/components/order-sender/order-sender"
}
}

View File

@@ -1,18 +1,28 @@
<view class="chat-page">
<view class="order-status-bar" wx:if="{{orderId}}" bindtap="tapOrderBar">
<view class="order-status-main">
<view class="order-status-left">
<text class="order-status-id">订单 {{orderId}}</text>
<text class="order-status-price" wx:if="{{orderJine}}">¥{{orderJine}}</text>
</view>
<text class="order-status-tag" wx:if="{{orderZhuangtaiText}}">{{orderZhuangtaiText}}</text>
</view>
<text class="order-status-desc" wx:if="{{orderJieshao}}">{{orderJieshao}}</text>
<text class="order-status-hint">点击查看历史订单并发送</text>
</view>
<scroll-view class="chat-msg-list" scroll-y="true"
scroll-into-view="{{scrollToView}}"
refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 20rpx);">
style="top: {{orderId ? '120rpx' : '20rpx'}}; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 24rpx);">
<view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<block wx:for="{{messages}}" wx:key="messageId">
<block wx:for="{{messages}}" wx:key="messageId" wx:for-index="msgIdx">
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<!-- 对方头像及昵称 -->
<view wx:if="{{item.senderId !== currentUser.id}}" class="sender-info">
<image class="chat-avatar" src="{{item.senderData.avatar || '/images/default-avatar.png'}}" mode="aspectFill" />
<image class="chat-avatar" src="{{item.senderData.avatar || defaultAvatarUrl}}" mode="aspectFill" binderror="onAvatarError" data-index="{{msgIdx}}" />
<text class="sender-name">{{item.senderData.name || item.senderId}}</text>
</view>
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
@@ -27,15 +37,15 @@
<text class="chat-order-info">ID: {{item.payload.orderId}}</text>
<text class="chat-order-info">内容: {{item.payload.jieshao}}</text>
<text class="chat-order-info">金额: ¥{{item.payload.jine}}</text>
<text class="chat-order-info" wx:if="{{item.payload.beizhu}}">备注: {{item.payload.beizhu}}</text>
<view class="chat-order-info" style="color:#007aff;margin-top:8rpx">点击查看详情</view>
</view>
</view>
<!-- 自己的头像 -->
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar}}" mode="aspectFill" />
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar || defaultAvatarUrl}}" mode="aspectFill" binderror="onSelfAvatarError" />
</view>
</view>
</block>
<view id="msg-bottom" style="height:20rpx;"></view>
<view id="msg-bottom" style="height:56rpx;"></view>
</scroll-view>
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
@@ -88,5 +98,5 @@
</view>
</view>
<order-sender visible="{{showOrderSender}}" bind:send="onSendOrder" bind:close="closeOrderSender" />
<order-sender visible="{{showOrderSender}}" group-id="{{groupId}}" order-id="{{orderId}}" bind:send="onSendOrder" bind:close="closeOrderSender" />
</view>

View File

@@ -1,5 +1,67 @@
/* 使用全局 chat-* 样式 */
.order-status-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 11;
background: #fff;
padding: 16rpx 24rpx;
border-bottom: 1rpx solid #e8e8e8;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
}
.order-status-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.order-status-id {
font-size: 26rpx;
color: #333;
font-weight: 600;
}
.order-status-tag {
font-size: 22rpx;
color: #07c160;
background: #e8f8ee;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
.order-status-left {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.order-status-price {
font-size: 24rpx;
color: #ff6b00;
margin-top: 4rpx;
}
.order-status-hint {
font-size: 22rpx;
color: #007aff;
margin-top: 6rpx;
display: block;
}
.order-status-desc {
font-size: 24rpx;
color: #888;
margin-top: 8rpx;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 发送者昵称样式(群聊特有) */
.sender-info {
display: flex;

View File

@@ -0,0 +1,14 @@
/** 旧二维码兼容guanshiduan → 打手中心管事注册,逻辑不变 */
import { parseSceneOptions } from '../../utils/base-page.js';
Page({
onLoad(options) {
const parsed = parseSceneOptions(options || {});
const inviteCode = parsed.inviteCode || options.inviteCode || '';
let url = '/pages/fighter/fighter?registerType=guanshi';
if (inviteCode) {
url += `&inviteCode=${encodeURIComponent(inviteCode)}`;
}
wx.reLaunch({ url });
},
});

View File

@@ -0,0 +1 @@
{ "navigationBarTitleText": "加载中", "usingComponents": {} }

View File

@@ -0,0 +1 @@
<view class="tip">正在进入管事注册…</view>

View File

@@ -0,0 +1 @@
.tip { display:flex; align-items:center; justify-content:center; min-height:100vh; color:#999; font-size:28rpx; }

View File

@@ -5,6 +5,7 @@ import { fetchShopGoods, bindShop } from '../../utils/shop';
import { backfillUserProfileCache, getSessionToken } from '../../utils/role-tab-bar';
import PopupService from '../../services/popupService.js';
import { createPage } from '../../utils/base-page.js';
import { fetchGonggaoLunbo, isGonggaoCacheValid } from '../../utils/display-config';
// 扫码场景参数名
const SCENE_PARAM = 'scene';
@@ -182,8 +183,7 @@ Page(createPage({
*/
loadGonggaoAndLunbo() {
return new Promise((resolve, reject) => {
// 如果全局已有数据,直接使用
if (app.globalData.shangpingonggao && app.globalData.shangpinlunbo.length > 0) {
if (isGonggaoCacheValid(app)) {
this.setData({
shangpingonggao: app.globalData.shangpingonggao,
lunboList: this.processImageUrls(app.globalData.shangpinlunbo)
@@ -192,26 +192,15 @@ Page(createPage({
return;
}
wx.request({
url: app.globalData.apiBaseUrl + '/peizhi/shangpingonggao/',
method: 'POST',
header: { 'content-type': 'application/json' },
success: (res) => {
if (res.statusCode === 200 && res.data) {
const data = res.data;
app.globalData.shangpingonggao = data.shangpingonggao || '';
app.globalData.shangpinlunbo = data.shangpinlunbo || [];
fetchGonggaoLunbo(app, 'order_pool')
.then((data) => {
this.setData({
shangpingonggao: app.globalData.shangpingonggao,
lunboList: this.processImageUrls(app.globalData.shangpinlunbo)
shangpingonggao: data.shangpingonggao || '',
lunboList: this.processImageUrls(data.shangpinlunbo || [])
});
resolve();
} else {
reject(new Error('请求失败'));
}
},
fail: reject
});
})
.catch(reject);
});
},

View File

@@ -18,7 +18,7 @@
class="lunbo-swiper"
indicator-dots="{{lunboList.length > 1}}"
indicator-color="rgba(255,255,255,0.6)"
indicator-active-color="#a855f7"
indicator-active-color="#ffcc00"
autoplay="{{lunboList.length > 1}}"
interval="3000"
circular

View File

@@ -12,7 +12,7 @@
border-radius: 40rpx;
background: linear-gradient(135deg,
rgba(220, 255, 220, 0.85) 0%,
rgba(237, 233, 254, 0.85) 100%);
rgba(255, 255, 200, 0.85) 100%);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
@@ -120,7 +120,7 @@
position: absolute;
width: 12rpx;
height: 12rpx;
background: #a855f7;
background: #ffcc00;
border-radius: 50%;
top: 15rpx;
right: 15rpx;
@@ -195,8 +195,8 @@
}
.leixing-active .leixing-glow {
border-color: #a855f7;
box-shadow: 0 0 20rpx rgba(168, 85, 247, 0.5);
border-color: #ffcc00;
box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.5);
}
.leixing-active .leixing-image-box {
@@ -239,7 +239,7 @@
.zhuanqu-icon-image {
width: 100%;
height: 100%;
filter: drop-shadow(0 2rpx 4rpx rgba(168, 85, 247, 0.3));
filter: drop-shadow(0 2rpx 4rpx rgba(255, 204, 0, 0.3));
}
.zhuanqu-sparkle {
@@ -249,11 +249,11 @@
@keyframes sparkle {
0%, 100% {
transform: scale(1) rotate(0deg);
filter: drop-shadow(0 2rpx 4rpx rgba(168, 85, 247, 0.3));
filter: drop-shadow(0 2rpx 4rpx rgba(255, 204, 0, 0.3));
}
50% {
transform: scale(1.1) rotate(10deg);
filter: drop-shadow(0 4rpx 8rpx rgba(168, 85, 247, 0.6));
filter: drop-shadow(0 4rpx 8rpx rgba(255, 204, 0, 0.6));
}
}
@@ -271,7 +271,7 @@
bottom: -8rpx;
width: 60rpx;
height: 4rpx;
background: linear-gradient(90deg, #a855f7, transparent);
background: linear-gradient(90deg, #ffcc00, transparent);
border-radius: 2rpx;
}

View File

@@ -1,21 +1,32 @@
// pages/zuzhanghaibao/zuzhanghaibao.js
// pages/invite-manager/invite-manager.js — 组长推广海报
import request from '../../utils/request.js';
import {
getFullImageUrl,
loadPosterPageConfig,
posterInviteCacheKey,
posterQrCacheKey,
} from '../../utils/poster-page.js';
const app = getApp();
Page({
data: {
qrcodeUrl: '', // 二维码完整URL
yaoqingma: '', // 组长邀请码字符串
qrcodeUrl: '',
yaoqingma: '',
isLoading: false,
showPosterCanvas: false,
posterImageTemp: '', // 生成的海报临时路径
avatarUrl: '', // 用户头像完整URL
posterImageTemp: '',
avatarUrl: '',
uid: '',
nickName: '',
posterBgUrl: '',
},
onLoad() {
async onLoad() {
const { bgUrl, qrCacheKey } = await loadPosterPageConfig('zuzhang', app);
this._qrCacheKey = qrCacheKey;
this._inviteCacheKey = posterInviteCacheKey();
this.setData({ posterBgUrl: bgUrl });
this.loadFromCache();
this.loadUserInfo();
},
@@ -29,126 +40,88 @@ Page({
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
hide: () => notificationComp.hideNotification(),
};
}
},
/**
* 从缓存加载二维码URL和邀请码
* 缓存键名使用独特拼音,避免与管事页面冲突
*/
loadFromCache() {
try {
const relativeUrl = wx.getStorageSync('zuzhang_haibao_url');
const key = this._qrCacheKey || posterQrCacheKey('zuzhang');
const relativeUrl = wx.getStorageSync(key);
if (relativeUrl) {
const fullUrl = this.getFullImageUrl(relativeUrl);
this.setData({ qrcodeUrl: fullUrl });
}
const yaoqingma = wx.getStorageSync('zuzhang_yaoqingma');
if (yaoqingma) {
this.setData({ yaoqingma });
this.setData({ qrcodeUrl: getFullImageUrl(relativeUrl, app) });
}
const inviteKey = this._inviteCacheKey || posterInviteCacheKey();
const yaoqingma = wx.getStorageSync(inviteKey);
if (yaoqingma) this.setData({ yaoqingma });
} catch (error) {
console.error('读取缓存失败', error);
}
},
/**
* 加载用户头像、昵称、UID
*/
loadUserInfo() {
const touxiang = wx.getStorageSync('touxiang');
const ossUrl = app.globalData.ossImageUrl || '';
const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
let avatarUrl = defaultAvatar;
if (touxiang && typeof touxiang === 'string' && touxiang.trim() !== '') {
avatarUrl = this.getFullImageUrl(touxiang);
avatarUrl = getFullImageUrl(touxiang, app);
}
const uid = wx.getStorageSync('uid') || '';
// 组长昵称可能从全局变量或缓存获取
let nickName = app.globalData.nicheng || wx.getStorageSync('nicheng') || '组长';
const nickName = app.globalData.nicheng || wx.getStorageSync('nicheng') || '组长';
this.setData({ avatarUrl, uid, nickName });
},
/**
* 拼接完整图片URL
*/
getFullImageUrl(url) {
if (!url) return '';
const ossUrl = app.globalData.ossImageUrl || '';
return url.startsWith('http') ? url : ossUrl + url;
},
/**
* 刷新/获取二维码和邀请码
* 调用 /peizhi/zuzhanghb 接口
*/
refreshQRCode() {
if (this.data.isLoading) return;
this.setData({ isLoading: true });
request({
url: '/peizhi/zuzhanghb',
method: 'POST',
})
.then(res => {
request({ url: '/peizhi/zuzhanghb', method: 'POST' })
.then((res) => {
if (res.data && res.data.code === 0) {
const { url: relativeUrl, yaoqingma } = res.data.data; // 假设返回字段为 url 和 yaoqingma
const { url: relativeUrl, yaoqingma } = res.data.data || {};
if (relativeUrl) {
wx.setStorageSync('zuzhang_haibao_url', relativeUrl);
const fullUrl = this.getFullImageUrl(relativeUrl);
this.setData({ qrcodeUrl: fullUrl });
const key = this._qrCacheKey || posterQrCacheKey('zuzhang');
wx.setStorageSync(key, relativeUrl);
this.setData({ qrcodeUrl: getFullImageUrl(relativeUrl, app) });
}
if (yaoqingma) {
wx.setStorageSync('zuzhang_yaoqingma', yaoqingma);
const inviteKey = this._inviteCacheKey || posterInviteCacheKey();
wx.setStorageSync(inviteKey, yaoqingma);
this.setData({ yaoqingma });
}
wx.showToast({ title: '获取成功', icon: 'success' });
} else {
wx.showToast({ title: res.data.msg || '获取失败', icon: 'none' });
wx.showToast({ title: res.data.msg || res.data.message || '获取失败', icon: 'none' });
}
})
.catch(err => {
.catch((err) => {
console.error('获取组长海报失败', err);
wx.showToast({ title: '网络错误', icon: 'none' });
})
.finally(() => {
this.setData({ isLoading: false });
});
.finally(() => this.setData({ isLoading: false }));
},
/**
* 保存二维码到相册(先下载网络图片)
*/
saveToAlbum() {
if (!this.data.qrcodeUrl) {
wx.showToast({ title: '暂无二维码', icon: 'none' });
return;
}
wx.showLoading({ title: '保存中...', mask: true });
wx.downloadFile({
url: this.data.qrcodeUrl,
success: (res) => {
wx.hideLoading();
if (res.statusCode === 200) {
this.downloadAndSave(res.tempFilePath);
} else {
wx.showToast({ title: '下载失败', icon: 'none' });
}
if (res.statusCode === 200) this.downloadAndSave(res.tempFilePath);
else wx.showToast({ title: '下载失败', icon: 'none' });
},
fail: (err) => {
fail: () => {
wx.hideLoading();
console.error('下载二维码失败', err);
wx.showToast({ title: '下载失败', icon: 'none' });
}
},
});
},
/**
* 复制邀请码到剪贴板
*/
copyInviteCode() {
if (!this.data.yaoqingma) {
wx.showToast({ title: '暂无邀请码', icon: 'none' });
@@ -156,145 +129,78 @@ Page({
}
wx.setClipboardData({
data: this.data.yaoqingma,
success: () => {
wx.showToast({ title: '复制成功', icon: 'success' });
},
fail: () => {
wx.showToast({ title: '复制失败', icon: 'none' });
}
success: () => wx.showToast({ title: '复制成功', icon: 'success' }),
});
},
/**
* 生成专属海报(包含头像、二维码、昵称、标语)
*/
generateMyPoster() {
if (!this.data.qrcodeUrl) {
wx.showToast({ title: '请先生成二维码', icon: 'none' });
return;
}
wx.showLoading({ title: '生成海报中...', mask: true });
const ossUrl = app.globalData.ossImageUrl || '';
// 🔥 组长海报背景图路径beijing/zuzhangbeijing.jpg
const bgUrl = ossUrl + 'beijing/zuzhangbeijing.jpg';
const urls = [bgUrl, this.data.qrcodeUrl, this.data.avatarUrl];
// 并行加载所有图片
const promises = urls.map(url => this.loadImage(url));
Promise.all(promises)
const bgUrl = this.data.posterBgUrl;
Promise.all([bgUrl, this.data.qrcodeUrl, this.data.avatarUrl].map((u) => this.loadImage(u)))
.then((images) => {
wx.hideLoading();
this.setData({ showPosterCanvas: true }, () => {
// 确保 canvas 已渲染
setTimeout(() => {
this.drawPoster(images[0], images[1], images[2]);
}, 200);
setTimeout(() => this.drawPoster(images[0], images[1], images[2]), 200);
});
})
.catch((err) => {
.catch(() => {
wx.hideLoading();
wx.showToast({ title: '图片加载失败', icon: 'none' });
console.error('图片加载失败', err);
});
},
/**
* 加载图片为本地路径(用于 canvas 绘制)
*/
loadImage(url) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: url,
success: (res) => resolve(res.path),
fail: reject
});
wx.getImageInfo({ src: url, success: (res) => resolve(res.path), fail: reject });
});
},
/**
* 绘制海报
* @param {string} bgPath 背景图本地路径
* @param {string} qrPath 二维码本地路径
* @param {string} avatarPath 头像本地路径
*/
drawPoster(bgPath, qrPath, avatarPath) {
const ctx = wx.createCanvasContext('posterCanvas', this);
const query = wx.createSelectorQuery().in(this);
query.select('.poster-canvas').boundingClientRect(rect => {
wx.createSelectorQuery().in(this).select('.poster-canvas').boundingClientRect((rect) => {
if (!rect) {
wx.showToast({ title: '画布获取失败', icon: 'none' });
return;
}
const canvasWidth = rect.width;
const canvasHeight = rect.height;
// 1. 绘制背景
ctx.drawImage(bgPath, 0, 0, canvasWidth, canvasHeight);
// 🔥【位置调整】二维码向右上角移动一丢丢 (增加 margin 从 5 到 15)
const qrSize = 100;
const margin = 15; // 原来 5现在 15向右下各移一点但看起来更协调
const margin = 15;
const qrX = canvasWidth - qrSize - margin;
const qrY = canvasHeight - qrSize - margin;
ctx.drawImage(qrPath, qrX, qrY, qrSize, qrSize);
// 🔥【位置调整】头像也向右上移动一丢丢 (原来 margin 5现在 margin 15)
const avatarSize = 60;
const avatarMargin = 15;
const avatarX = avatarMargin + 10; // 原来 margin 5+20现在 15+20相当于右移10
const avatarY = canvasHeight - avatarSize - avatarMargin;
// 圆形头像裁剪
const avatarX = margin + 10;
const avatarY = canvasHeight - avatarSize - margin;
ctx.save();
ctx.beginPath();
ctx.arc(avatarX + avatarSize/2, avatarY + avatarSize/2, avatarSize/2, 0, 2 * Math.PI);
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI);
ctx.clip();
ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize);
ctx.restore();
// 绘制昵称(右侧)
ctx.setFontSize(16);
ctx.setFillStyle('#333333'); // 根据背景图颜色可调整
ctx.setFillStyle('#333333');
ctx.setTextAlign('left');
let nick = this.data.nickName || '组长';
if (nick.length > 3) {
nick = nick.substring(0, 3) + '...';
}
const nameText = nick;
const textX = avatarX + avatarSize + 8;
const textY = avatarY + avatarSize/2 + 6;
ctx.fillText(nameText, textX, textY);
// 绘制邀请语
if (nick.length > 3) nick = nick.substring(0, 3) + '...';
ctx.fillText(nick, avatarX + avatarSize + 8, avatarY + avatarSize / 2 + 6);
ctx.setFontSize(18);
ctx.setFillStyle('#9333ea'); // 金黄色,呼应组长主题
ctx.setTextAlign('left');
const slogan = '加入我的团队 ✦';
const sloganX = avatarX;
const sloganY = avatarY - 12;
ctx.fillText(slogan, sloganX, sloganY);
// 异步绘制
ctx.setFillStyle('#ffaa00');
ctx.fillText('加入我的团队 ✦', avatarX, avatarY - 12);
ctx.draw(false, () => {
wx.canvasToTempFilePath({
canvasId: 'posterCanvas',
success: (res) => {
this.setData({ posterImageTemp: res.tempFilePath });
},
fail: (err) => {
console.error('生成海报临时文件失败', err);
}
success: (res) => this.setData({ posterImageTemp: res.tempFilePath }),
}, this);
});
}).exec();
},
/**
* 保存生成的海报
*/
savePoster() {
if (!this.data.posterImageTemp) {
wx.showToast({ title: '海报尚未生成', icon: 'none' });
@@ -303,54 +209,37 @@ Page({
this.downloadAndSave(this.data.posterImageTemp);
},
/**
* 隐藏海报画布,返回主界面
*/
hidePoster() {
this.setData({ showPosterCanvas: false, posterImageTemp: '' });
},
/**
* 通用保存图片到相册(带权限检查)
*/
downloadAndSave(filePath) {
wx.getSetting({
success: (res) => {
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
this._saveImage(filePath);
},
success: () => this._saveImage(filePath),
fail: () => {
wx.showModal({
title: '提示',
content: '需要您授权保存到相册',
success: (modalRes) => {
if (modalRes.confirm) {
wx.openSetting();
}
}
success: (m) => { if (m.confirm) wx.openSetting(); },
});
}
},
});
} else {
this._saveImage(filePath);
}
}
},
});
},
_saveImage(filePath) {
wx.saveImageToPhotosAlbum({
filePath: filePath,
success: () => {
wx.showToast({ title: '保存成功', icon: 'success' });
},
fail: (err) => {
console.error('保存失败', err);
wx.showToast({ title: '保存失败', icon: 'none' });
}
filePath,
success: () => wx.showToast({ title: '保存成功', icon: 'success' }),
fail: () => wx.showToast({ title: '保存失败', icon: 'none' }),
});
},
});

View File

@@ -15,8 +15,8 @@ page {
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(168, 85, 247, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(168, 85, 247, 0.05) 1px, transparent 1px);
linear-gradient(rgba(255, 200, 0, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 200, 0, 0.05) 1px, transparent 1px);
background-size: 50rpx 50rpx;
pointer-events: none;
z-index: 0;
@@ -41,14 +41,14 @@ page {
left: 0;
width: 100%;
height: 2rpx;
background: linear-gradient(90deg, transparent, #9333ea, transparent);
background: linear-gradient(90deg, transparent, #ffaa00, transparent);
}
.line-v {
right: 50rpx;
top: 100rpx;
width: 2rpx;
height: 300rpx;
background: linear-gradient(180deg, transparent, #9333ea, transparent);
background: linear-gradient(180deg, transparent, #ffaa00, transparent);
}
.content {
@@ -71,14 +71,14 @@ page {
font-size: 44rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 0 30rpx #9333ea;
text-shadow: 0 0 30rpx #ffaa00;
letter-spacing: 2rpx;
display: block;
margin-bottom: 20rpx;
}
.subtitle {
font-size: 28rpx;
color: #c4b5fd;
color: #ffd966;
opacity: 0.9;
letter-spacing: 1rpx;
}
@@ -96,11 +96,11 @@ page {
width: 400rpx;
height: 400rpx;
background: rgba(20, 20, 20, 0.8);
border: 4rpx solid #9333ea;
border: 4rpx solid #ffaa00;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 80rpx rgba(147, 51, 234, 0.4);
box-shadow: 0 0 80rpx rgba(255, 170, 0, 0.4);
animation: qrcodePulse 2s infinite alternate;
}
.qrcode-img {
@@ -114,14 +114,14 @@ page {
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 30% 30%, rgba(147, 51, 234, 0.2), transparent 70%);
background: radial-gradient(circle at 30% 30%, rgba(255, 170, 0, 0.2), transparent 70%);
pointer-events: none;
}
.empty-qrcode {
width: 400rpx;
height: 400rpx;
background: rgba(20, 20, 20, 0.8);
border: 4rpx dashed #9333ea;
border: 4rpx dashed #ffaa00;
display: flex;
flex-direction: column;
align-items: center;
@@ -129,13 +129,13 @@ page {
}
.empty-icon {
font-size: 80rpx;
color: #9333ea;
color: #ffaa00;
margin-bottom: 20rpx;
opacity: 0.5;
}
.empty-text {
font-size: 26rpx;
color: #c4b5fd;
color: #ffd966;
text-align: center;
padding: 0 40rpx;
}
@@ -148,14 +148,14 @@ page {
background: rgba(0, 0, 0, 0.5);
padding: 20rpx 30rpx;
border-radius: 60rpx;
border: 2rpx solid #9333ea;
border: 2rpx solid #ffaa00;
margin-bottom: 30rpx;
width: 90%;
box-sizing: border-box;
}
.code-label {
font-size: 28rpx;
color: #c4b5fd;
color: #ffd966;
margin-right: 10rpx;
}
.code-value {
@@ -169,23 +169,23 @@ page {
.copy-btn {
display: flex;
align-items: center;
background: rgba(147, 51, 234, 0.2);
background: rgba(255, 170, 0, 0.2);
padding: 10rpx 20rpx;
border-radius: 40rpx;
margin-left: 20rpx;
border: 2rpx solid #9333ea;
border: 2rpx solid #ffaa00;
}
.copy-icon {
font-size: 28rpx;
color: #9333ea;
color: #ffaa00;
margin-right: 6rpx;
}
.copy-text {
font-size: 24rpx;
color: #9333ea;
color: #ffaa00;
}
.copy-btn:active {
background: rgba(147, 51, 234, 0.4);
background: rgba(255, 170, 0, 0.4);
transform: scale(0.96);
}
@@ -206,7 +206,7 @@ page {
width: 220rpx;
height: 80rpx;
background: rgba(20, 20, 20, 0.8);
border: 2rpx solid #9333ea;
border: 2rpx solid #ffaa00;
border-right: none;
border-bottom: none;
clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
@@ -219,22 +219,22 @@ page {
}
.btn:active {
transform: scale(0.96);
box-shadow: 0 0 40rpx #9333ea;
box-shadow: 0 0 40rpx #ffaa00;
}
.save-btn {
border-color: #9333ea;
border-color: #ffaa00;
}
.refresh-btn {
border-color: #9333ea;
border-color: #ffaa00;
}
.refresh-btn .btn-icon {
color: #9333ea;
color: #ffaa00;
}
.poster-btn {
border-color: #9333ea;
border-color: #ffaa00;
}
.poster-btn .btn-icon {
color: #9333ea;
color: #ffaa00;
}
.loading-btn {
opacity: 0.7;
@@ -254,7 +254,7 @@ page {
width: 30rpx;
height: 30rpx;
border: 4rpx solid rgba(255,255,255,0.2);
border-top: 4rpx solid #9333ea;
border-top: 4rpx solid #ffaa00;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10rpx;
@@ -272,8 +272,8 @@ page {
width: 600rpx;
height: 900rpx;
background: #1a1f2e;
border: 4rpx solid #9333ea;
box-shadow: 0 0 60rpx #9333ea;
border: 4rpx solid #ffaa00;
box-shadow: 0 0 60rpx #ffaa00;
}
.poster-actions {
display: flex;
@@ -282,12 +282,12 @@ page {
justify-content: center;
}
.save-poster-btn {
background: linear-gradient(45deg, #6d28d9, #9333ea);
border-color: #9333ea;
background: linear-gradient(45deg, #cc8400, #ffaa00);
border-color: #ffaa00;
}
.back-btn {
background: rgba(20, 20, 20, 0.8);
border-color: #9333ea;
border-color: #ffaa00;
}
/* 使用说明 */
@@ -295,19 +295,19 @@ page {
margin-top: 30rpx;
padding: 20rpx 40rpx;
background: rgba(20, 20, 20, 0.5);
border-left: 4rpx solid #9333ea;
border-left: 4rpx solid #ffaa00;
clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%);
}
.instruction-text {
font-size: 24rpx;
color: #c4b5fd;
color: #ffd966;
line-height: 1.6;
}
/* 动画 */
@keyframes qrcodePulse {
0% { box-shadow: 0 0 40rpx rgba(147, 51, 234, 0.3); }
100% { box-shadow: 0 0 100rpx rgba(147, 51, 234, 0.6); }
0% { box-shadow: 0 0 40rpx rgba(255, 170, 0, 0.3); }
100% { box-shadow: 0 0 100rpx rgba(255, 170, 0, 0.6); }
}
@keyframes spin {
0% { transform: rotate(0deg); }

View File

@@ -1,5 +1,5 @@
{
"navigationBarTitleText": "关注星阙",
"navigationBarTitleText": "星之界",
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
},

View File

@@ -0,0 +1,67 @@
import { createPage, request } from '../../utils/base-page.js';
import { fetchMerchantOrderStats, applyOrderStatsToPage } from '../../utils/merchant-order-stats.js';
Page(createPage({
data: {
statusBar: 20,
loading: true,
canViewFinance: true,
orderStats: {
pending_count: 0,
pending_amount: '0.00',
completed_count: 0,
completed_amount: '0.00',
refund_count: 0,
refund_amount: '0.00',
dispatch_count: 0,
dispatch_amount: '0.00',
},
penaltyStats: {
fakuan_pending: 0,
fakuan_pending_amount: '0.00',
jifen_pending: 0,
},
jinriliushui: '0.00',
jinyueliushui: '0.00',
jinridingdan: 0,
jinrituikuan: 0,
},
onLoad() {
const sys = wx.getSystemInfoSync();
this.setData({ statusBar: sys.statusBarHeight || 20 });
this.loadData();
},
onPullDownRefresh() {
this.loadData().finally(() => wx.stopPullDownRefresh());
},
async loadData() {
this.setData({ loading: true });
try {
const statsData = await fetchMerchantOrderStats(request, {});
applyOrderStatsToPage(this, statsData);
try {
const res = await request({ url: '/yonghu/shangjiaxinxi', method: 'POST' });
if (res.data && res.data.code === 200) {
const d = res.data.data || {};
this.setData({
jinriliushui: d.jinriliushui || '0.00',
jinyueliushui: d.jinyueliushui || '0.00',
jinridingdan: d.jinripaidan || 0,
jinrituikuan: d.jinrituikuan || 0,
});
}
} catch (e) {
console.warn('商家流水加载失败', e);
}
} finally {
this.setData({ loading: false });
}
},
goBack() {
wx.navigateBack();
},
}));

View File

@@ -0,0 +1,5 @@
{
"navigationStyle": "custom",
"enablePullDownRefresh": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,56 @@
<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}}" refresher-enabled refresher-triggered="{{loading}}" bindrefresherrefresh="onPullDownRefresh">
<view class="sj-panel sj-mx20 data-panel">
<view class="data-highlight flexb">
<view class="data-hl-item">
<text class="data-hl-num accent">{{orderStats.pending_count}}</text>
<text class="data-hl-lbl">待结算(单)</text>
<text class="data-hl-sub" wx:if="{{canViewFinance}}">¥{{orderStats.pending_amount}}</text>
</view>
<view class="data-hl-item">
<text class="data-hl-num">{{orderStats.completed_count}}</text>
<text class="data-hl-lbl">成交总量</text>
<text class="data-hl-sub" wx:if="{{canViewFinance}}">¥{{orderStats.completed_amount}}</text>
</view>
</view>
<view class="data-grid">
<view class="data-cell">
<text class="data-cell-num">{{orderStats.refund_count}}</text>
<text class="data-cell-lbl">退款总量</text>
<text class="data-cell-sub" wx:if="{{canViewFinance}}">¥{{orderStats.refund_amount}}</text>
</view>
<view class="data-cell">
<text class="data-cell-num">{{orderStats.dispatch_count}}</text>
<text class="data-cell-lbl">发单总量</text>
<text class="data-cell-sub" wx:if="{{canViewFinance}}">¥{{orderStats.dispatch_amount}}</text>
</view>
<view class="data-cell">
<text class="data-cell-num">{{jinriliushui}}</text>
<text class="data-cell-lbl">今日流水</text>
</view>
<view class="data-cell">
<text class="data-cell-num">{{jinyueliushui}}</text>
<text class="data-cell-lbl">今月流水</text>
</view>
<view class="data-cell">
<text class="data-cell-num">{{jinridingdan}}</text>
<text class="data-cell-lbl">今日派单</text>
</view>
<view class="data-cell">
<text class="data-cell-num">{{jinrituikuan}}</text>
<text class="data-cell-lbl">今日退款</text>
</view>
</view>
<view class="penalty-mini" wx:if="{{penaltyStats.fakuan_pending || penaltyStats.jifen_pending}}">
<text>处罚待处理:罚单 {{penaltyStats.fakuan_pending}}(¥{{penaltyStats.fakuan_pending_amount}})· 积分 {{penaltyStats.jifen_pending}}</text>
</view>
</view>
</scroll-view>
</view>

View File

@@ -0,0 +1,84 @@
@import '../../styles/shangjia-xym-common.wxss';
page { background: #fff8e1; }
.sub-scroll { height: calc(100vh - 120rpx); }
.data-panel { padding: 24rpx; margin-top: 16rpx; }
.data-highlight {
margin-bottom: 24rpx;
gap: 16rpx;
}
.data-hl-item {
flex: 1;
text-align: center;
background: #fff;
border-radius: 16rpx;
padding: 20rpx 12rpx;
}
.data-hl-num {
display: block;
font-size: 40rpx;
font-weight: 700;
color: #333;
}
.data-hl-num.accent { color: #e65100; }
.data-hl-lbl {
display: block;
font-size: 24rpx;
color: #888;
margin-top: 8rpx;
}
.data-hl-sub {
display: block;
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
.data-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
}
.data-cell {
background: #fff;
border-radius: 16rpx;
padding: 20rpx 16rpx;
text-align: center;
}
.data-cell-num {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.data-cell-lbl {
display: block;
font-size: 22rpx;
color: #888;
margin-top: 6rpx;
}
.data-cell-sub {
display: block;
font-size: 20rpx;
color: #aaa;
margin-top: 4rpx;
}
.penalty-mini {
margin-top: 20rpx;
font-size: 24rpx;
color: #666;
line-height: 1.5;
}

View File

@@ -24,6 +24,7 @@ Page(createPage({
dingdanJieshao: '', // 订单介绍
dingdanBeizhu: '', // 订单备注
laobanName: '', // 老板游戏昵称/UID
waibuDingdanId: '', // 外部平台订单号
zhidingUid: '', // 指定打手UID
jiage: '', // 订单价格
@@ -214,6 +215,9 @@ Page(createPage({
onLaobanNameInput(e) {
this.setData({ laobanName: e.detail.value })
},
onWaibuDingdanIdInput(e) {
this.setData({ waibuDingdanId: e.detail.value })
},
onZhidingUidInput(e) {
this.setData({ zhidingUid: e.detail.value })
},
@@ -342,6 +346,7 @@ Page(createPage({
dingdanJieshao: '',
dingdanBeizhu: '',
laobanName: '',
waibuDingdanId: '',
zhidingUid: '',
jiage: '',
selectedLabelId: '',
@@ -371,6 +376,7 @@ Page(createPage({
dingdanJieshao: this.data.dingdanJieshao.trim(),
dingdanBeizhu: this.data.dingdanBeizhu.trim() || '',
laobanName: this.data.laobanName.trim(),
waibuDingdanId: this.data.waibuDingdanId.trim() || '',
zhidingUid: this.data.zhidingUid.trim() || '',
jiage: this.data.jiage,
// 新增字段

View File

@@ -4,9 +4,9 @@
"popup-notice": "/components/popup-notice/popup-notice"
},
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#fff8e1",
"navigationBarBackgroundColor": "#f7dc51",
"navigationBarTitleText": "自定义发单",
"navigationBarTextStyle": "black",
"backgroundColor": "#f5f5f5",
"backgroundColor": "#fff8e1",
"enablePullDownRefresh": false
}

View File

@@ -84,6 +84,21 @@
/>
</view>
<!-- 拼多多订单号 -->
<view class="field">
<view class="field-head">
<text class="field-label">拼多多订单号</text>
<text class="field-optional">选填</text>
</view>
<input
class="field-input"
placeholder="外部平台订单号"
maxlength="64"
value="{{waibuDingdanId}}"
bindinput="onWaibuDingdanIdInput"
/>
</view>
<!-- 指定接单员UID -->
<view class="field">
<view class="field-head">
@@ -144,7 +159,7 @@
<text class="field-optional">选填</text>
</view>
<view class="switch-row">
<switch checked="{{commissionEnabled}}" bindchange="onCommissionToggle" color="#9333ea"/>
<switch checked="{{commissionEnabled}}" bindchange="onCommissionToggle" color="#ffd061"/>
<text class="switch-label">{{commissionEnabled ? '已开启' : '未开启'}}</text>
</view>
<input

View File

@@ -5,9 +5,21 @@ import {
restoreStaffContextAfterAuth, applyStaffStatsToPage, getStaffContext,
} from '../../utils/staff-api.js';
import { ensurePhoneAuth } from '../../utils/phone-auth.js';
import { fetchGonggaoLunbo, isGonggaoCacheValid } from '../../utils/display-config';
import { ICON_KEYS, resolveMiniappIcon } from '../../utils/miniapp-icons.js';
import { fetchMerchantOrderStats, applyOrderStatsToHome } from '../../utils/merchant-order-stats.js';
const app = getApp();
const XYM_HOME_FALLBACK = {
noticeIco: 'https://bintao.xmxym88.com/xcx/bintao/38.png',
statCardBg: 'https://bintao.xmxym88.com/xcx/bintao/3.png',
kefuKeyBtn: 'https://bintao.xmxym88.com/xcx/bintao/13.png',
kefuListBtn: 'https://bintao.xmxym88.com/xcx/bintao/21.png',
iconRegular: 'https://bintao.xmxym88.com/xcx/bintao/74.png',
iconCustom: 'https://bintao.xmxym88.com/xcx/bintao/76.png',
};
/** 与订单页「全部」Tab 一致的状态筛选 */
const ALL_ORDER_ZHUANGTAI = [1, 2, 3, 4, 5, 6, 7, 8];
const RECENT_ORDER_SIZE = 5;
@@ -27,16 +39,24 @@ Page(createPage({
gonggao: '',
swiperCurrent: 0,
imgUrls: {
noticeIco: 'https://bintao.xmxym88.com/xcx/bintao/38.png',
statCardBg: 'https://bintao.xmxym88.com/xcx/bintao/3.png',
kefuKeyBtn: 'https://bintao.xmxym88.com/xcx/bintao/13.png',
kefuListBtn: 'https://bintao.xmxym88.com/xcx/bintao/21.png',
noticeIco: XYM_HOME_FALLBACK.noticeIco,
statCardBg: XYM_HOME_FALLBACK.statCardBg,
kefuKeyBtn: XYM_HOME_FALLBACK.kefuKeyBtn,
kefuListBtn: XYM_HOME_FALLBACK.kefuListBtn,
iconRegular: XYM_HOME_FALLBACK.iconRegular,
iconCustom: XYM_HOME_FALLBACK.iconCustom,
},
stats: {
pendingCount: 0,
pendingAmount: '0.00',
todayDispatch: 0,
todayRefund: 0,
completedCount: 0,
completedAmount: '0.00',
refundCount: 0,
refundAmount: '0.00',
dispatchCount: 0,
dispatchAmount: '0.00',
},
recentOrders: [],
recentLoading: false,
@@ -54,18 +74,30 @@ Page(createPage({
}
this.checkRole();
syncStaffUi(this);
this.waitForConfigAndLoadBanner();
this.setupImageUrls();
await this.waitForConfigAndLoadBanner();
if (this.data.isShangjia) {
await this.loadDashboard(false);
}
this._merchantHomeSessionReady = true;
this._skipShowRefresh = true;
this.checkColdStartPopup('shangjiaduan');
},
async onShow() {
if (!app.globalData._shangjiaPhoneChecked) {
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'shangjia' });
if (!phoneOk) return;
app.globalData._shangjiaPhoneChecked = true;
}
this.registerNotificationComponent();
// 先用本地缓存渲染,再向服务端同步子客服身份
this.checkRole();
syncStaffUi(this);
if (app.globalData.ossImageUrl || app.globalData.miniappIcons) {
this.setupImageUrls();
}
const token = wx.getStorageSync('token');
const isOwner = Number(wx.getStorageSync('shangjiastatus')) === 1;
if (token && !isOwner) {
@@ -78,11 +110,26 @@ Page(createPage({
syncStaffUi(this);
applyStaffStatsToPage(this, getStaffContext());
}
if (this.data.isShangjia) {
ensureRoleOnCenterPage(this, 'shangjia');
this.loadDashboard();
}
this.loadBannerData();
if (app.startImWhenReady) app.startImWhenReady();
if (this._skipShowRefresh) {
this._skipShowRefresh = false;
return;
}
const silent = this._merchantHomeSessionReady;
if (this.data.isShangjia) {
this.loadDashboard(silent);
}
if (silent) {
this.loadBannerData(true, true);
} else {
this.loadBannerData(false);
}
},
onPullDownRefresh() {
@@ -96,10 +143,36 @@ Page(createPage({
waitForConfigAndLoadBanner() {
if (app.globalData.ossImageUrl || app.globalData.apiBaseUrl) {
this.loadBannerData(true);
return;
this.setupImageUrls();
return this.loadBannerData(true);
}
setTimeout(() => this.waitForConfigAndLoadBanner(), 100);
return new Promise((resolve) => {
const tick = () => {
if (app.globalData.ossImageUrl || app.globalData.apiBaseUrl) {
this.setupImageUrls();
this.loadBannerData(true).finally(resolve);
} else {
setTimeout(tick, 100);
}
};
tick();
});
},
setupImageUrls() {
const ossBase = app.globalData.ossImageUrl || '';
const homeDir = `${ossBase}beijing/shangjiaduan/home/`;
const icon = (key, fallback) => resolveMiniappIcon(app, key, fallback);
this.setData({
imgUrls: {
noticeIco: icon(ICON_KEYS.MERCHANT_HOME_NOTICE, XYM_HOME_FALLBACK.noticeIco),
statCardBg: icon(ICON_KEYS.MERCHANT_HOME_STAT_BG, `${ossBase}beijing/shangjiaduan/stat_card_bg.png`) || XYM_HOME_FALLBACK.statCardBg,
kefuKeyBtn: icon(ICON_KEYS.MERCHANT_HOME_KEFU_KEY, XYM_HOME_FALLBACK.kefuKeyBtn),
kefuListBtn: icon(ICON_KEYS.MERCHANT_HOME_KEFU_LIST, XYM_HOME_FALLBACK.kefuListBtn),
iconRegular: icon(ICON_KEYS.MERCHANT_HOME_REGULAR, XYM_HOME_FALLBACK.iconRegular),
iconCustom: icon(ICON_KEYS.MERCHANT_HOME_CUSTOM, XYM_HOME_FALLBACK.iconCustom),
},
});
},
processImageUrls(urlList) {
@@ -109,13 +182,13 @@ Page(createPage({
},
checkRole() {
this.setData({ isShangjia: isMerchantPortalUser() });
syncStaffUi(this);
},
loadBannerData(forceFetch) {
loadBannerData(forceFetch, silent) {
const g = app.globalData || {};
const cachedList = g.shangpinlunbo || [];
if (!forceFetch && cachedList.length) {
if (!forceFetch && isGonggaoCacheValid(app) && cachedList.length) {
this.setData({
lunboList: this.processImageUrls(cachedList),
gonggao: g.shangpingonggao || '',
@@ -123,41 +196,20 @@ Page(createPage({
});
return Promise.resolve();
}
if (!forceFetch && g.shangpingonggao) {
this.setData({
lunboList: this.processImageUrls(cachedList),
gonggao: g.shangpingonggao,
});
return Promise.resolve();
}
const apiBase = g.apiBaseUrl || '';
if (!apiBase) return Promise.resolve();
return new Promise((resolve) => {
wx.request({
url: `${apiBase}/peizhi/shangpingonggao/`,
method: 'POST',
header: { 'content-type': 'application/json' },
success: (res) => {
if (res.statusCode === 200 && res.data) {
const data = res.data;
app.globalData.shangpingonggao = data.shangpingonggao || '';
app.globalData.shangpinlunbo = data.shangpinlunbo || [];
return fetchGonggaoLunbo(app, 'merchant_home').then((data) => {
if (!data) return;
this.setData({
lunboList: this.processImageUrls(app.globalData.shangpinlunbo),
gonggao: app.globalData.shangpingonggao,
});
}
},
complete: resolve,
});
gonggao: app.globalData.shangpingonggao || '',
});
}).catch(() => {});
},
async loadDashboard() {
async loadDashboard(silent = false) {
await Promise.all([
this.loadShangjiaBrief(),
this.loadPendingStats(),
this.loadRecentOrders(),
this.loadRecentOrders(silent),
]);
},
@@ -191,36 +243,21 @@ Page(createPage({
async loadPendingStats() {
try {
const res = await request({
url: isStaffMode() ? STAFF_API.orderList : '/dingdan/sjdingdanhq',
method: 'POST',
data: { zhuangtai_list: [8], page: 1, page_size: 20 },
});
if (res.data && (res.data.code === 0 || res.data.code === 200)) {
const data = res.data.data || {};
const list = data.list || [];
let amount = 0;
list.forEach((item) => {
const v = parseFloat(item.jine || item.dingdan_jine || 0);
if (!Number.isNaN(v)) amount += v;
});
this.setData({
stats: {
...this.data.stats,
pendingCount: data.pending_count != null ? data.pending_count : list.length,
pendingAmount: amount.toFixed(2),
},
});
}
const data = await fetchMerchantOrderStats(request, {});
applyOrderStatsToHome(this, data);
} catch (e) {
console.error('待结算统计失败', e);
console.warn('待结算统计失败', e);
}
},
/** 最近订单:同订单页「全部」筛选,取最新若干条 */
async loadRecentOrders() {
if (this.data.recentLoading) return;
async loadRecentOrders(silent = false) {
if (this.data.recentLoading && !silent) return;
if (silent && this._recentOrdersRequesting) return;
if (!silent) {
this.setData({ recentLoading: true });
}
this._recentOrdersRequesting = true;
try {
const res = await request({
url: isStaffMode() ? STAFF_API.orderList : '/dingdan/sjdingdanhq',
@@ -253,20 +290,35 @@ Page(createPage({
} catch (e) {
console.error('最近订单加载失败', e);
} finally {
this._recentOrdersRequesting = false;
if (!silent) {
this.setData({ recentLoading: false });
}
}
},
onSwiperChange(e) {
this.setData({ swiperCurrent: e.detail.current });
},
goDispatch() {
goRegularDispatch() {
wx.navigateTo({ url: '/pages/merchant-regular-dispatch/merchant-regular-dispatch' });
},
goLinkDispatch() {
wx.navigateTo({ url: '/pages/express-order/express-order' });
},
goCustomDispatch() {
wx.navigateTo({ url: '/pages/merchant-dispatch/merchant-dispatch' });
},
goDispatch() {
this.goCustomDispatch();
},
goExpress() {
wx.navigateTo({ url: '/pages/express-order/express-order' });
this.goLinkDispatch();
},
goOrdersTab() {

View File

@@ -18,6 +18,11 @@
<block wx:else>
<view class="sj-main-body">
<view class="sj-notice-bar" wx:if="{{gonggao}}">
<image class="notice-ico" src="{{imgUrls.noticeIco}}" mode="aspectFit"/>
<text class="sj-notice-txt">{{gonggao}}</text>
</view>
<view class="swiper-wrap" wx:if="{{lunboList.length}}">
<view class="swiper-con">
<swiper class="banner-swiper" autoplay circular interval="4000" duration="500" bindchange="onSwiperChange">
@@ -34,18 +39,21 @@
<image class="slide-image banner-placeholder" src="{{lunbozhanwei}}" mode="aspectFill"/>
</view>
<view class="sj-notice-bar" wx:if="{{gonggao}}">
<image class="notice-ico" src="{{imgUrls.noticeIco}}" mode="aspectFit"/>
<text class="sj-notice-txt">{{gonggao}}</text>
</view>
<view class="staff-banner sj-mx20" wx:if="{{isStaffMode}}">
<text class="staff-banner-txt">商家客服 · {{staffRoleName}} · 可用额度 ¥{{quotaAvailable}}</text>
</view>
<view class="sj-fadan-row" wx:if="{{canDispatch}}">
<view class="sj-fadan-img-btn sj-fadan-img-btn--left" bindtap="goExpress"/>
<view class="sj-fadan-img-btn sj-fadan-img-btn--right" bindtap="goDispatch"/>
<view class="sj-fadan-block" wx:if="{{canDispatch}}">
<view class="sj-fadan-row">
<view class="sj-fadan-img-btn sj-fadan-img-btn--left" bindtap="goRegularDispatch"/>
<view class="sj-fadan-img-btn sj-fadan-img-btn--right" bindtap="goCustomDispatch"/>
</view>
<view class="sj-fadan-row sj-fadan-row--link">
<view class="sj-fadan-big-btn sj-fadan-big-btn--link" bindtap="goLinkDispatch">
<text class="sj-fadan-big-title">链接派单</text>
<text class="sj-fadan-big-sub">模板管理 · 生成链接</text>
</view>
</view>
</view>
<view class="merchant-stats shadow">

View File

@@ -1,7 +1,7 @@
@import '../../styles/shangjia-xym-common.wxss';
page {
background: #f5f3ff;
background: #fff8e1;
}
.sj-page--home {
@@ -85,14 +85,14 @@ page {
.dot.active {
width: 16rpx;
border-radius: 8rpx;
background: #9333ea;
background: #ffd061;
}
.sj-notice-bar {
display: flex;
align-items: center;
background: #fdf9db;
margin: 0 24rpx;
margin: 12rpx 24rpx 0;
border-radius: 10rpx;
padding: 10rpx 16rpx;
}
@@ -102,6 +102,10 @@ page {
height: 56rpx;
flex-shrink: 0;
margin-right: 12rpx;
background-image: url('https://bintao.xmxym88.com/xcx/bintao/38.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.sj-notice-txt {
@@ -111,11 +115,22 @@ page {
flex: 1;
}
.sj-fadan-block {
margin: 16rpx 24rpx 0;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.sj-fadan-row {
display: flex;
margin: 24rpx 20rpx 0;
padding: 10rpx;
gap: 8rpx;
margin: 0;
padding: 0;
gap: 16rpx;
}
.sj-fadan-row--link {
margin-top: 0;
}
.sj-fadan-img-btn {
@@ -127,16 +142,41 @@ page {
.sj-fadan-img-btn--left {
background-image: url('https://bintao.xmxym88.com/xcx/bintao/74.png');
margin-right: 8rpx;
}
.sj-fadan-img-btn--right {
background-image: url('https://bintao.xmxym88.com/xcx/bintao/76.png');
margin-left: 8rpx;
}
.sj-fadan-big-btn {
flex: 1;
height: 128rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
}
.sj-fadan-big-btn--link {
background: linear-gradient(180deg, #fae04d, #ffc0a3);
}
.sj-fadan-big-title {
font-size: 30rpx;
font-weight: 700;
color: #492f00;
}
.sj-fadan-big-sub {
font-size: 22rpx;
color: #886633;
margin-top: 8rpx;
}
.merchant-stats {
margin: 32rpx 25rpx 0;
margin: 12rpx 25rpx 0;
border-radius: 26rpx;
padding-bottom: 24rpx;
position: relative;
@@ -151,8 +191,10 @@ page {
left: 0;
right: 0;
bottom: 0;
background-image: url('https://bintao.xmxym88.com/xcx/bintao/3.png');
background-size: 100% auto;
background-repeat: no-repeat;
background-position: center top;
pointer-events: none;
z-index: 0;
}
@@ -311,7 +353,7 @@ page {
.staff-banner {
margin-top: 16rpx;
padding: 16rpx 24rpx;
background: #f5f3ff;
background: #fff8e8;
border-radius: 12rpx;
border: 1rpx solid #f0d89a;
}

View File

@@ -95,7 +95,7 @@
padding: 16rpx 32rpx;
min-height: 64rpx;
border-radius: 10rpx;
background: linear-gradient(180deg, #c4b5fd, #a78bfa);
background: linear-gradient(180deg, #fae04d, #ffc0a3);
color: #492f00;
font-size: 28rpx;
font-weight: 700;
@@ -108,8 +108,8 @@
}
.tips-bar {
background: #f5f3ff;
border: 1rpx solid #c4b5fd;
background: #fff7e6;
border: 1rpx solid #ffd591;
border-radius: 12rpx;
padding: 18rpx 20rpx;
font-size: 26rpx;

Some files were not shown because too many files have changed in this diff Show More