Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc650762f8 | ||
|
|
684f392cb6 | ||
|
|
77fefbe8aa | ||
|
|
b9de9ea34e | ||
|
|
1c6c9a7ac0 | ||
|
|
eef2e95bd3 | ||
|
|
0067a63093 | ||
|
|
7eca128bb5 | ||
|
|
83a5a0aaf0 | ||
|
|
42b585274c | ||
|
|
cec1b477b4 | ||
|
|
e203223922 | ||
|
|
fdfd3a9524 | ||
|
|
ada903de07 | ||
|
|
a44be49ac6 | ||
|
|
283133473a | ||
|
|
4b064c6303 | ||
|
|
2a8f7c427c | ||
|
|
16eecd1278 | ||
|
|
09df206fd8 | ||
|
|
6f6dac422f | ||
|
|
782a18006e | ||
|
|
4b60ebbc10 | ||
|
|
566aeaa3b7 |
No files matched your search
@@ -3,6 +3,12 @@ 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 { isFighterOnly, resolveFighterOnlyLaunchUrl } from './utils/fighter-only';
|
||||
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 +42,8 @@ App({
|
||||
guanshiqun: '',
|
||||
guanshiqunid: '',
|
||||
|
||||
appId:'wx0e4be86faac4a8d1',
|
||||
appId: WX_APP_ID,
|
||||
clubId: CLUB_ID,
|
||||
|
||||
shangjiastatus: 0,
|
||||
dashoustatus: 0,
|
||||
@@ -49,11 +56,7 @@ App({
|
||||
yajin: null,
|
||||
chenghao: '',
|
||||
jinfen: null,
|
||||
clumber: [{
|
||||
huiyuanid: '',
|
||||
huiyuanming: '',
|
||||
daoqi: ''
|
||||
}],
|
||||
clumber: [],
|
||||
chengjiaoliang: null,
|
||||
zaixianZhuangtai: null,
|
||||
|
||||
@@ -80,6 +83,8 @@ App({
|
||||
xshenfen: 1,
|
||||
|
||||
goEasyConfig: null,
|
||||
chatEnabled: false,
|
||||
groupInfoMap: {},
|
||||
kefuConfig: {
|
||||
link: '',
|
||||
enterpriseId: ''
|
||||
@@ -151,6 +156,7 @@ App({
|
||||
pageState: {
|
||||
currentPage: '',
|
||||
isInChatPage: false,
|
||||
isInGroupChat: false,
|
||||
currentChatId: '',
|
||||
lastPageUpdate: 0
|
||||
},
|
||||
@@ -159,11 +165,23 @@ App({
|
||||
debugMode: false,
|
||||
currentUser: null,
|
||||
currentRole: 'normal',
|
||||
primaryRole: 'normal'
|
||||
},
|
||||
primaryRole: 'normal',
|
||||
/** 客服浮钮:full | mini,onLaunch 重置为 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) => {
|
||||
@@ -184,7 +202,7 @@ App({
|
||||
}
|
||||
|
||||
migrateLegacyCenterRole(this);
|
||||
const savedRole = getPrimaryRole(this);
|
||||
const savedRole = isFighterOnly() ? 'dashou' : getPrimaryRole(this);
|
||||
lockPrimaryRole(savedRole, this);
|
||||
|
||||
// ②b 冷启动:有 token 时先问后端是否需要手机号认证
|
||||
@@ -193,19 +211,27 @@ App({
|
||||
if (!phoneOk) return;
|
||||
}
|
||||
|
||||
const targetPage = roleDefaultPage[savedRole] || '/pages/index/index';
|
||||
if (savedRole !== 'normal') {
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({ url: targetPage });
|
||||
}, 0);
|
||||
// ②c 打手端冷启动:同步会员/押金等到 globalData(抢单页依赖 clumber)
|
||||
if (wx.getStorageSync('token') && savedRole === 'dashou') {
|
||||
refreshDashouMembership(this).catch(() => {});
|
||||
}
|
||||
|
||||
// ③ 应用本地缓存配置
|
||||
// ③ 先应用本地缓存配置(必须在 reLaunch 抢单池之前,否则弹窗图片拼不出 OSS)
|
||||
const cachedConfig = this.readConfigFromStorage();
|
||||
if (cachedConfig) {
|
||||
this.applyDynamicConfig(cachedConfig);
|
||||
}
|
||||
|
||||
// 纯打手端:未登录必进登录页;已认证进抢单池
|
||||
const targetPage = isFighterOnly()
|
||||
? resolveFighterOnlyLaunchUrl()
|
||||
: (roleDefaultPage[savedRole] || '/pages/index/index');
|
||||
if (isFighterOnly() || savedRole !== 'normal') {
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({ url: targetPage });
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// ④ 初始化
|
||||
this.initGoEasyWithConfig(); // 只有appkey有效时才真正初始化
|
||||
ChatCore.initGlobalMessageSystem(this);
|
||||
@@ -214,13 +240,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 +252,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 +269,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 +303,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 +323,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 +334,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 +357,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 +400,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 +436,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 +449,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,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,113 +1,318 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/fighter/fighter",
|
||||
"pages/accept-order/accept-order",
|
||||
"pages/team-hall/team-hall",
|
||||
"pages/fighter-orders/fighter-orders",
|
||||
"pages/messages/messages",
|
||||
"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/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/alipay-webview",
|
||||
"pages": ["alipay-webview"]
|
||||
},
|
||||
{
|
||||
"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/fund-freeze",
|
||||
"pages": ["fund-freeze"]
|
||||
},
|
||||
{
|
||||
"root": "pages/role-agreement",
|
||||
"pages": ["role-agreement"]
|
||||
},
|
||||
{
|
||||
"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/gongdan",
|
||||
"pages": [
|
||||
"create/create",
|
||||
"list/list",
|
||||
"detail/detail",
|
||||
"chongzhi-list/chongzhi-list"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"navigationBarTitleText": "星阙网络"
|
||||
"navigationBarTitleText": "龙先生电竞"
|
||||
},
|
||||
"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/team-hall/team-hall",
|
||||
"text": "组队",
|
||||
"iconPath": "/images/team-hall.png",
|
||||
"selectedIconPath": "/images/team-hall.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"
|
||||
}
|
||||
}
|
||||
@@ -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); }
|
||||
|
||||
|
||||
@@ -1,73 +1,119 @@
|
||||
// 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 || {};
|
||||
|
||||
// 1. 尺寸(稍大一些,一行能放3~4个)
|
||||
const width = cfg.width || 152;
|
||||
const height = cfg.height || 52;
|
||||
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 || '';
|
||||
|
||||
// 2. 背景处理:优先背景图,否则用渐变/纯色
|
||||
// 无 shape:身份装饰标签 / 自定义色圆角标;有 shape:考核称号等特殊形状
|
||||
const isPill = !cfg.shape || PILL_SHAPES.includes(cfg.shape);
|
||||
|
||||
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
|
||||
imageUrl = cfg.image_url.startsWith('http')
|
||||
? cfg.image_url
|
||||
: ossImageUrl + cfg.image_url;
|
||||
} else if (cfg.bg_gradient) {
|
||||
bgStyle = `background: ${cfg.bg_gradient};`;
|
||||
} 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,
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 背景图加载失败时回退为纯色背景
|
||||
},
|
||||
|
||||
onImageError() {
|
||||
this.setData({ imageUrl: '' });
|
||||
// 如果 bgStyle 为空,给个默认背景
|
||||
if (!this.data.bgStyle) {
|
||||
this.setData({ bgStyle: 'background: linear-gradient(135deg, #c4b5fd, #9333ea);' });
|
||||
if (!this.data.inlineStyle.includes('background')) {
|
||||
this.setData({
|
||||
inlineStyle: this.data.inlineStyle + ' background: linear-gradient(135deg, #FFD700, #FF8C00);',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,20 +1,17 @@
|
||||
<!-- components/chenghao-tag/chenghao-tag.wxml -->
|
||||
<view
|
||||
class="tag-root {{shapeClass}} {{animationClass}}"
|
||||
style="width: {{width}}rpx; height: {{height}}rpx; {{bgStyle}}"
|
||||
<view
|
||||
class="tag-root {{shapeClass}} {{animationClass}}"
|
||||
style="{{inlineStyle}}"
|
||||
>
|
||||
<!-- 背景图(如果有) -->
|
||||
<image
|
||||
wx:if="{{imageUrl}}"
|
||||
class="tag-bg-image"
|
||||
src="{{imageUrl}}"
|
||||
<image
|
||||
wx:if="{{imageUrl && !isPill}}"
|
||||
class="tag-bg-image"
|
||||
src="{{imageUrl}}"
|
||||
mode="aspectFill"
|
||||
binderror="onImageError"
|
||||
></image>
|
||||
|
||||
<!-- 文字层 -->
|
||||
<text
|
||||
class="tag-text"
|
||||
/>
|
||||
<text
|
||||
class="tag-text"
|
||||
style="color: {{textColor}}; font-size: {{textSize}}rpx;"
|
||||
>{{mingcheng}}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -12,6 +12,18 @@
|
||||
margin: 4rpx;
|
||||
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 {
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const { resolveAvatarUrl } = require('../../utils/avatar.js');
|
||||
const { resolveAvatarUrl, getDefaultAvatarUrl } = require('../../utils/avatar.js');
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
@@ -53,7 +53,8 @@ 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: ''
|
||||
@@ -265,6 +268,13 @@ Component({
|
||||
this.triggerEvent('close');
|
||||
this.hideNotification();
|
||||
},
|
||||
|
||||
onAvatarError() {
|
||||
const def = getDefaultAvatarUrl(getApp());
|
||||
if (def && this.data.avatar !== def) {
|
||||
this.setData({ avatar: def });
|
||||
}
|
||||
},
|
||||
|
||||
onMute(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
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;
|
||||
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 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;
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
</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>
|
||||
@@ -1,45 +1,140 @@
|
||||
.order-sender-mask {
|
||||
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;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
z-index: 2001;
|
||||
transform: translateY(100%);
|
||||
transition: 0.25s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.order-sender.show { transform: translateY(0); }
|
||||
|
||||
.header {
|
||||
display: flex; justify-content: space-between;
|
||||
padding: 24rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.title { font-size: 32rpx; font-weight: 600; }
|
||||
.close { font-size: 44rpx; color: #999; padding: 0 12rpx; }
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
.icon { font-size: 80rpx; margin-bottom: 20rpx; }
|
||||
.notice {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.sub {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
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: 72vh;
|
||||
max-height: 900rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
z-index: 2001;
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.25s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.order-sender.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.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;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.order-id {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -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');
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -41,15 +41,24 @@ Component({
|
||||
async show(config, onCloseCallback) {
|
||||
const { title, images, duration, showMuteCheckbox, serverTime } = config;
|
||||
this.onCloseCallback = onCloseCallback;
|
||||
|
||||
this._showToken = (this._showToken || 0) + 1;
|
||||
const token = this._showToken;
|
||||
|
||||
let safeImages = JSON.parse(JSON.stringify(images || []));
|
||||
// 兼容 url / image_url
|
||||
safeImages = safeImages.map((img) => ({
|
||||
...img,
|
||||
url: img.url || img.image_url || '',
|
||||
text: img.text || '',
|
||||
})).filter((img) => !!img.url);
|
||||
|
||||
const hasImages = safeImages.length > 0;
|
||||
let hasTextOnly = false, textContent = '';
|
||||
if (!hasImages) {
|
||||
hasTextOnly = true;
|
||||
textContent = config.text || '暂无内容';
|
||||
}
|
||||
|
||||
|
||||
this.setData({
|
||||
visible: true,
|
||||
isMinimized: false,
|
||||
@@ -65,36 +74,91 @@ Component({
|
||||
isFullscreen: false,
|
||||
processedImages: [],
|
||||
});
|
||||
|
||||
|
||||
if (duration > 0) {
|
||||
this.startCountdown();
|
||||
}
|
||||
|
||||
if (hasImages) {
|
||||
const processed = [];
|
||||
const screenWidth = wx.getSystemInfoSync().windowWidth;
|
||||
const containerWidth = screenWidth * 0.9;
|
||||
const imageWidth = containerWidth - 60;
|
||||
for (let i = 0; i < safeImages.length; i++) {
|
||||
const img = safeImages[i];
|
||||
const fullUrl = this.getFullImageUrl(img.url);
|
||||
let imageHeight = 200;
|
||||
try {
|
||||
const res = await this.getImageInfoPromise(fullUrl);
|
||||
const realHeight = (imageWidth * res.height) / res.width;
|
||||
imageHeight = realHeight;
|
||||
} catch (e) {
|
||||
console.error('获取图片信息失败', fullUrl, e);
|
||||
}
|
||||
processed.push({
|
||||
url: fullUrl,
|
||||
text: img.text,
|
||||
height: imageHeight,
|
||||
});
|
||||
|
||||
if (!hasImages) return;
|
||||
|
||||
// 等 OSS 域名就绪再拼接,避免相对路径导致整图空白
|
||||
await this.ensureOssBase(4000);
|
||||
if (token !== this._showToken) return;
|
||||
|
||||
const processed = safeImages.map((img) => ({
|
||||
url: this.getFullImageUrl(img.url),
|
||||
text: img.text,
|
||||
height: 0,
|
||||
}));
|
||||
console.log('[popup-notice] 图片URL:', processed.map((p) => p.url));
|
||||
this.setData({ processedImages: processed });
|
||||
|
||||
// 可选:量真实高度(失败也不影响 widthFix 展示)
|
||||
const screenWidth = wx.getSystemInfoSync().windowWidth;
|
||||
const containerWidth = Math.min(screenWidth * 0.86, 310);
|
||||
const imageWidth = Math.max(containerWidth - 30, 200);
|
||||
for (let i = 0; i < processed.length; i++) {
|
||||
if (token !== this._showToken) return;
|
||||
const fullUrl = processed[i].url;
|
||||
if (!fullUrl || !/^https?:\/\//.test(fullUrl)) {
|
||||
console.error('[popup-notice] OSS未就绪或URL非法', fullUrl);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const res = await this.getImageInfoPromise(fullUrl);
|
||||
const realHeight = (imageWidth * res.height) / res.width;
|
||||
this.setData({ [`processedImages[${i}].height`]: realHeight });
|
||||
} catch (e) {
|
||||
console.error('获取图片信息失败', fullUrl, e);
|
||||
}
|
||||
this.setData({ processedImages: processed });
|
||||
}
|
||||
},
|
||||
|
||||
/** 等待 / 回读本地缓存,确保能拼出完整 COS 地址 */
|
||||
ensureOssBase(timeoutMs = 3000) {
|
||||
const app = getApp();
|
||||
const readOss = () => this.resolveOssBase();
|
||||
if (readOss()) return Promise.resolve(readOss());
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let done = false;
|
||||
const finish = () => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
if (app.off) app.off('configApplied', onCfg);
|
||||
clearInterval(timer);
|
||||
clearTimeout(timerOut);
|
||||
resolve(readOss());
|
||||
};
|
||||
const onCfg = () => finish();
|
||||
if (app.on) app.on('configApplied', onCfg);
|
||||
const timer = setInterval(() => {
|
||||
if (readOss()) finish();
|
||||
}, 100);
|
||||
const timerOut = setTimeout(finish, timeoutMs);
|
||||
// 主动再读一次缓存配置
|
||||
try {
|
||||
if (typeof app.readConfigFromStorage === 'function') {
|
||||
const cached = app.readConfigFromStorage();
|
||||
if (cached && app.applyDynamicConfig) app.applyDynamicConfig(cached);
|
||||
}
|
||||
} catch (e) {}
|
||||
if (readOss()) finish();
|
||||
});
|
||||
},
|
||||
|
||||
resolveOssBase() {
|
||||
const app = getApp();
|
||||
let base = (app.globalData && app.globalData.ossImageUrl) || '';
|
||||
if (!base && app && typeof app.readConfigFromStorage === 'function') {
|
||||
try {
|
||||
const cached = app.readConfigFromStorage();
|
||||
base = (cached && cached.cos && cached.cos.ossImageUrl) || '';
|
||||
if (base && app.globalData) app.globalData.ossImageUrl = base;
|
||||
} catch (e) {}
|
||||
}
|
||||
return base || '';
|
||||
},
|
||||
|
||||
getImageInfoPromise(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -204,12 +268,18 @@ Component({
|
||||
|
||||
getFullImageUrl(relativeUrl) {
|
||||
if (!relativeUrl) return '';
|
||||
if (relativeUrl.startsWith('http')) return relativeUrl;
|
||||
const app = getApp();
|
||||
let base = app.globalData.ossImageUrl || '';
|
||||
base = base.replace(/\/+$/, '');
|
||||
const clean = relativeUrl.replace(/^\/+/, '');
|
||||
return base ? base + '/' + clean : clean;
|
||||
if (relativeUrl.startsWith('http://') || relativeUrl.startsWith('https://')) {
|
||||
return relativeUrl;
|
||||
}
|
||||
// 后端 cos.ossImageUrl 带尾斜杠;相对路径不带前导 /
|
||||
let base = this.resolveOssBase();
|
||||
if (!base) {
|
||||
console.warn('[popup-notice] ossImageUrl 为空,无法拼接', relativeUrl);
|
||||
return '';
|
||||
}
|
||||
if (!base.endsWith('/')) base += '/';
|
||||
const clean = String(relativeUrl).replace(/^\/+/, '');
|
||||
return base + clean;
|
||||
},
|
||||
|
||||
preventMove() {},
|
||||
|
||||
@@ -25,15 +25,17 @@
|
||||
<view wx:for="{{processedImages}}" wx:key="index" class="image-block">
|
||||
<view wx:if="{{item.text}}" class="block-text">{{item.text}}</view>
|
||||
<view class="image-padding">
|
||||
<image
|
||||
class="block-image"
|
||||
src="{{item.url}}"
|
||||
style="width:100%; height:{{item.height}}px; display:block;"
|
||||
<image
|
||||
class="block-image"
|
||||
src="{{item.url}}"
|
||||
mode="widthFix"
|
||||
style="width:100%; {{item.height > 0 ? 'height:' + item.height + 'px;' : ''}} display:block;"
|
||||
data-index="{{index}}"
|
||||
data-url="{{item.url}}"
|
||||
bindtap="onCustomPreview"
|
||||
binderror="onImageError"
|
||||
lazy-load="{{false}}"
|
||||
show-menu-by-longpress="{{true}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -15,14 +15,18 @@
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
width: 90%;
|
||||
height: 85vh;
|
||||
width: 86%;
|
||||
max-width: 620rpx;
|
||||
/* 必须给明确高度:scroll-view 依赖父级高度,height:auto 会导致图片区塌成 0 */
|
||||
height: 68vh;
|
||||
max-height: 68vh;
|
||||
background: white;
|
||||
border-radius: 32rpx;
|
||||
border-radius: 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
.popup-container.fullscreen {
|
||||
@@ -78,6 +82,7 @@
|
||||
|
||||
.scroll-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -138,7 +143,7 @@
|
||||
.countdown-tip {
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #9333ea;
|
||||
color: #ff6600;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
@@ -156,10 +161,11 @@
|
||||
|
||||
/* 🔥 关闭按钮(替换原有 button,增加扫光) */
|
||||
.close-btn {
|
||||
width: 600rpx;
|
||||
height: 100rpx;
|
||||
width: 86%;
|
||||
max-width: 480rpx;
|
||||
height: 88rpx;
|
||||
background: #1a1a1a;
|
||||
border-radius: 60rpx;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -223,7 +229,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);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
const STORAGE_KEY = 'tousuFloatHidden';
|
||||
|
||||
Component({
|
||||
data: {
|
||||
hidden: false,
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this._syncHidden();
|
||||
},
|
||||
},
|
||||
|
||||
pageLifetimes: {
|
||||
show() {
|
||||
this._syncHidden();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
_syncHidden() {
|
||||
let hidden = false;
|
||||
try {
|
||||
hidden = wx.getStorageSync(STORAGE_KEY) === 1 || wx.getStorageSync(STORAGE_KEY) === '1';
|
||||
} catch (e) {}
|
||||
if (hidden !== this.data.hidden) {
|
||||
this.setData({ hidden });
|
||||
}
|
||||
},
|
||||
|
||||
go() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/gongdan/create/create',
|
||||
fail: (err) => {
|
||||
console.error('打开投诉页失败', err);
|
||||
wx.showToast({ title: '打开失败,请重试', icon: 'none' });
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
onDismiss(e) {
|
||||
if (e && e.stopPropagation) e.stopPropagation();
|
||||
wx.showModal({
|
||||
title: '隐藏投诉入口',
|
||||
content: '隐藏后可在「我的 / 工单」中继续投诉。需要恢复时可清除小程序缓存。',
|
||||
confirmText: '不再显示',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
try {
|
||||
wx.setStorageSync(STORAGE_KEY, 1);
|
||||
} catch (err) {}
|
||||
this.setData({ hidden: true });
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{"component":true}
|
||||
@@ -0,0 +1,6 @@
|
||||
<view class="tousu-wrap" wx:if="{{!hidden}}">
|
||||
<view class="float" catchtap="go">投诉</view>
|
||||
<view class="dismiss-row" catchtap="onDismiss">
|
||||
<text class="dismiss-label">不再显示</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,37 @@
|
||||
.tousu-wrap {
|
||||
position: fixed;
|
||||
right: 24rpx;
|
||||
bottom: 280rpx;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.float {
|
||||
min-width: 96rpx;
|
||||
height: 72rpx;
|
||||
padding: 0 22rpx;
|
||||
border-radius: 36rpx;
|
||||
background: rgba(55, 55, 55, 0.72);
|
||||
color: #f5f5f5;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.dismiss-row {
|
||||
padding: 4rpx 8rpx;
|
||||
}
|
||||
|
||||
.dismiss-label {
|
||||
font-size: 20rpx;
|
||||
color: rgba(120, 120, 120, 0.9);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4rpx;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 小程序所属俱乐部配置 — 每个小程序工程编译前改这里即可。
|
||||
* 星阙 = xq;星之界 = xzj;龙先生 = lxs(与后台 club 表 club_id 一致,勿写成 lsx)。
|
||||
* 不要依赖后端猜测:前端必须明确所属 club。
|
||||
*/
|
||||
export const CLUB_ID = 'lxs';
|
||||
|
||||
/** 与 club 表 wx_appid 一致,登录时可传给后端反查 club */
|
||||
export const WX_APP_ID = 'wx7ff90e9d024fcdb8';
|
||||
|
||||
export const CLUB_NAME = '龙先生电竞';
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,3 @@
|
||||
# 总会员抢单/展示修复前备份
|
||||
日期: 2026-07-10
|
||||
原文件见同目录。
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 从服务端刷新打手会员/押金/积分等到 globalData(抢单、考试等页共用)
|
||||
*/
|
||||
import request from './request.js';
|
||||
|
||||
export async function refreshDashouMembership(app) {
|
||||
app = app || getApp();
|
||||
try {
|
||||
const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' });
|
||||
if (res && res.data && res.data.code == 200) {
|
||||
const data = res.data.data || {};
|
||||
const clumber = Array.isArray(data.clumber) ? data.clumber : [];
|
||||
app.globalData.clumber = clumber;
|
||||
if (data.yajin !== undefined) app.globalData.yajin = data.yajin;
|
||||
if (data.jifen !== undefined) app.globalData.jinfen = data.jifen;
|
||||
if (data.zhanghaostatus !== undefined) app.globalData.zhanghaoStatus = data.zhanghaostatus;
|
||||
if (data.dashouzhuangtai !== undefined) app.globalData.dashouzhuangtai = data.dashouzhuangtai;
|
||||
return clumber;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('refreshDashouMembership failed', e);
|
||||
}
|
||||
return app.globalData.clumber || [];
|
||||
}
|
||||
|
||||
/** 用户是否持有某会员类型(与抢单卡片判断一致;支持总会员 covers) */
|
||||
export function userHasHuiyuan(huiyuanList, huiyuanId) {
|
||||
if (!huiyuanId) return true;
|
||||
const list = huiyuanList || [];
|
||||
const req = String(huiyuanId);
|
||||
return list.some((h) => {
|
||||
if (String(h.huiyuanid) === req || String(h.huiyuan_id) === req) return true;
|
||||
const covers = h.included_huiyuan_ids || h.covers || [];
|
||||
return Array.isArray(covers) && covers.some((id) => String(id) === req);
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,476 @@
|
||||
// pages/order-pool/order-pool.js
|
||||
const app = getApp();
|
||||
import request from '../../utils/request.js';
|
||||
import PopupService from '../../services/popupService.js';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
// 1. 商品类型相关
|
||||
shangpinleixing: [],
|
||||
xuanzhongLeixingId: null,
|
||||
|
||||
// 2. 订单列表与分页相关
|
||||
dingdanList: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
hasMore: true,
|
||||
isLoading: false,
|
||||
|
||||
// 3. 全局状态字段
|
||||
dashoustatus: null,
|
||||
zhuanghaoStatus: null,
|
||||
dashouzhuangtai: null,
|
||||
uid: null,
|
||||
huiyuanList: [],
|
||||
yajin: 0,
|
||||
jifen: 0,
|
||||
|
||||
// 4. 刷新控制字段
|
||||
scrollViewRefreshing: false,
|
||||
lastRefreshTime: 0,
|
||||
refreshCooldown: 2000,
|
||||
isLoadingMore: false,
|
||||
|
||||
// 5. 切换类型冷却时间控制
|
||||
lastSwitchTime: 0,
|
||||
switchCooldown: 1500,
|
||||
isSwitching: false,
|
||||
|
||||
// 6. 全局OSS地址
|
||||
ossImageUrl: app.globalData.ossImageUrl || '',
|
||||
|
||||
// 7. 授权状态控制
|
||||
showUnauthorized: false,
|
||||
unauthorizedMsg: '您尚未开启此功能',
|
||||
|
||||
// 商品轮播展示(只读 globalData,无额外接口)
|
||||
lunboList: [],
|
||||
},
|
||||
|
||||
async onLoad(options) {
|
||||
this.syncShopBannerFromGlobal();
|
||||
this.loadGlobalStatus();
|
||||
this.checkAuthorization();
|
||||
},
|
||||
// 页面隐藏时清理弹窗视图
|
||||
onHide: function () {
|
||||
const popupComp = this.selectComponent('#popupNotice');
|
||||
if (popupComp && popupComp.cleanup) {
|
||||
popupComp.cleanup();
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.syncShopBannerFromGlobal();
|
||||
this.registerNotificationComponent();
|
||||
this.loadGlobalStatus();
|
||||
this.checkAuthorization();
|
||||
},
|
||||
|
||||
registerNotificationComponent() {
|
||||
const notificationComp = this.selectComponent('#global-notification');
|
||||
if (notificationComp && notificationComp.showNotification) {
|
||||
app.globalData.globalNotification = {
|
||||
show: (data) => notificationComp.showNotification(data),
|
||||
hide: () => notificationComp.hideNotification()
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/** 同步点单端已加载的商品轮播图,仅展示用 */
|
||||
syncShopBannerFromGlobal() {
|
||||
const oss = app.globalData.ossImageUrl || '';
|
||||
const lunboList = (app.globalData.shangpinlunbo || []).map((url) => {
|
||||
if (!url) return '';
|
||||
return url.startsWith('http') ? url : oss + url;
|
||||
}).filter(Boolean);
|
||||
this.setData({ lunboList });
|
||||
},
|
||||
|
||||
loadGlobalStatus() {
|
||||
const globalData = app.globalData;
|
||||
const dashoustatusCache = wx.getStorageSync('dashoustatus');
|
||||
const dashoustatus = dashoustatusCache !== '' ? dashoustatusCache : null;
|
||||
|
||||
let zhuanghaoStatus = globalData.zhanghaoStatus;
|
||||
if (zhuanghaoStatus === undefined || zhuanghaoStatus === null) {
|
||||
zhuanghaoStatus = wx.getStorageSync('zhanghaoStatus');
|
||||
}
|
||||
|
||||
this.setData({
|
||||
dashoustatus: dashoustatus,
|
||||
zhuanghaoStatus: zhuanghaoStatus,
|
||||
dashouzhuangtai: globalData.dashouzhuangtai,
|
||||
uid: wx.getStorageSync('uid'),
|
||||
huiyuanList: globalData.clumber || [],
|
||||
yajin: globalData.yajin || 0,
|
||||
jifen: globalData.jinfen || 0
|
||||
});
|
||||
},
|
||||
|
||||
checkAuthorization() {
|
||||
const { dashoustatus, zhuanghaoStatus } = this.data;
|
||||
|
||||
const isDashouValid = (dashoustatus && dashoustatus == 1) || false;
|
||||
const isZhuanghaoValid = (zhuanghaoStatus == 1);
|
||||
|
||||
const isAuthorized = isDashouValid && isZhuanghaoValid;
|
||||
|
||||
if (!isAuthorized) {
|
||||
this.setData({ showUnauthorized: true });
|
||||
let msg = '';
|
||||
if (!isDashouValid) msg = '您尚未开启身份';
|
||||
this.setData({ unauthorizedMsg: msg || '无法使用抢单功能' });
|
||||
} else {
|
||||
this.setData({ showUnauthorized: false });
|
||||
if (!this.data.shangpinleixing.length) {
|
||||
this.loadShangpinLeixing();
|
||||
} else if (this.data.xuanzhongLeixingId) {
|
||||
this.loadDingdanList(true);
|
||||
}
|
||||
PopupService.checkAndShow(this, 'jiedanchi');
|
||||
}
|
||||
},
|
||||
|
||||
async loadShangpinLeixing() {
|
||||
wx.showLoading({ title: '加载商品类型...' });
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/dingdan/dsqdhqddlx',
|
||||
method: 'POST',
|
||||
header: { 'content-type': 'application/json' }
|
||||
});
|
||||
|
||||
if (res.statusCode === 200 && res.data) {
|
||||
const data = res.data;
|
||||
let list = [];
|
||||
if (data.code === 200 || data.code === 0) {
|
||||
if (data.data && Array.isArray(data.data.list)) {
|
||||
list = data.data.list;
|
||||
} else if (data.data && Array.isArray(data.data)) {
|
||||
list = data.data;
|
||||
} else if (Array.isArray(data.list)) {
|
||||
list = data.list;
|
||||
} else if (Array.isArray(data)) {
|
||||
list = data;
|
||||
}
|
||||
}
|
||||
const processedList = this.processTupianUrl(list);
|
||||
this.setData({
|
||||
shangpinleixing: processedList,
|
||||
xuanzhongLeixingId: processedList[0]?.id || null
|
||||
});
|
||||
if (this.data.xuanzhongLeixingId) {
|
||||
this.loadDingdanList(true);
|
||||
}
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.msg || '加载商品类型失败', icon: 'none', duration: 2000 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载商品类型失败:', error);
|
||||
wx.showToast({ title: '网络错误,加载失败', icon: 'none', duration: 2000 });
|
||||
} finally {
|
||||
wx.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
processTupianUrl(list) {
|
||||
const ossUrl = app.globalData.ossImageUrl || '';
|
||||
return list.map(item => ({
|
||||
...item,
|
||||
full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http') ? ossUrl + item.tupian_url : (item.tupian_url || '/images/default-type.png')
|
||||
}));
|
||||
},
|
||||
|
||||
async selectLeixing(e) {
|
||||
const now = Date.now();
|
||||
if (now - this.data.lastSwitchTime < this.data.switchCooldown) {
|
||||
wx.showToast({
|
||||
title: `操作太快,请${Math.ceil((this.data.switchCooldown - (now - this.data.lastSwitchTime)) / 1000)}秒后再试`,
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.data.isSwitching) return;
|
||||
const leixingId = e.currentTarget.dataset.id;
|
||||
if (leixingId === this.data.xuanzhongLeixingId) return;
|
||||
this.setData({ isSwitching: true, lastSwitchTime: now });
|
||||
wx.showLoading({ title: '切换中...' });
|
||||
try {
|
||||
this.setData({
|
||||
xuanzhongLeixingId: leixingId,
|
||||
dingdanList: [],
|
||||
page: 1,
|
||||
hasMore: true
|
||||
});
|
||||
await this.loadDingdanList(true);
|
||||
} catch (error) {
|
||||
console.error('切换类型失败:', error);
|
||||
} finally {
|
||||
this.setData({ isSwitching: false });
|
||||
wx.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
async loadDingdanList(isRefresh = false) {
|
||||
if (this.data.isLoading || !this.data.xuanzhongLeixingId) return;
|
||||
|
||||
const loadPage = isRefresh ? 1 : this.data.page;
|
||||
if (!isRefresh && !this.data.hasMore) return;
|
||||
|
||||
this.setData({
|
||||
isLoading: true,
|
||||
isLoadingMore: !isRefresh
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/dingdan/ddhq',
|
||||
method: 'POST',
|
||||
data: {
|
||||
leixing_id: this.data.xuanzhongLeixingId,
|
||||
page: loadPage,
|
||||
page_size: this.data.pageSize
|
||||
}
|
||||
});
|
||||
|
||||
this.setData({
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false
|
||||
});
|
||||
|
||||
if (res.data.code === 200 || res.data.code === 0) {
|
||||
const newList = res.data.data.list || [];
|
||||
const hasMore = res.data.data.has_more || false;
|
||||
|
||||
const processedList = newList.map(item => this.processDingdanItem(item));
|
||||
|
||||
const updatedList = isRefresh ? processedList : [...this.data.dingdanList, ...processedList];
|
||||
|
||||
updatedList.sort((a, b) => (b.creat_time || '').localeCompare(a.creat_time || ''));
|
||||
|
||||
this.setData({
|
||||
dingdanList: updatedList,
|
||||
page: loadPage,
|
||||
hasMore: hasMore
|
||||
});
|
||||
|
||||
if (isRefresh) {
|
||||
this.setData({ lastRefreshTime: Date.now() });
|
||||
}
|
||||
} else {
|
||||
wx.showToast({ title: res.data.msg || '加载失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载订单失败:', error);
|
||||
this.setData({
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false
|
||||
});
|
||||
wx.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
processDingdanItem(item) {
|
||||
const ossUrl = app.globalData.ossImageUrl || '';
|
||||
let fullTupianUrl = '';
|
||||
if (item.tupian) {
|
||||
fullTupianUrl = !item.tupian.startsWith('http') ? ossUrl + item.tupian : item.tupian;
|
||||
} else {
|
||||
const leixing = this.data.shangpinleixing.find(l => l.id === item.leixing_id);
|
||||
fullTupianUrl = leixing ? leixing.full_tupian_url : '/images/default-order.png';
|
||||
}
|
||||
|
||||
const isZhiding = item.zhuangtai === 7;
|
||||
|
||||
let zhidingAvatar = '';
|
||||
if (item.zhiding_avatar) {
|
||||
zhidingAvatar = !item.zhiding_avatar.startsWith('http') ? ossUrl + item.zhiding_avatar : item.zhiding_avatar;
|
||||
}
|
||||
const zhidingNicheng = item.zhiding_nicheng || '';
|
||||
|
||||
return {
|
||||
...item,
|
||||
full_tupian_url: fullTupianUrl,
|
||||
isZhiding: isZhiding,
|
||||
isPingtai: item.pingtai == 1,
|
||||
isShangjia: item.pingtai == 2,
|
||||
zhiding_avatar_full: zhidingAvatar,
|
||||
zhiding_nicheng: zhidingNicheng
|
||||
};
|
||||
},
|
||||
|
||||
goToChongzhiPage(failureType, huiyuanId = null, requiredYajin = 0) {
|
||||
let url = '/pages/fighter-recharge/fighter-recharge';
|
||||
let params = {};
|
||||
switch (failureType) {
|
||||
case 'huiyuan':
|
||||
url = '/pages/fighter-recharge/fighter-recharge';
|
||||
params = { scrollTo: 'member' };
|
||||
break;
|
||||
case 'yajin':
|
||||
url = '/pages/fighter-recharge/fighter-deposit';
|
||||
params = { requiredYajin: requiredYajin };
|
||||
break;
|
||||
case 'jifen':
|
||||
url = '/pages/fighter-recharge/fighter-recharge';
|
||||
params = { scrollTo: 'jifen' };
|
||||
break;
|
||||
}
|
||||
if (huiyuanId) params.huiyuanId = huiyuanId;
|
||||
const queryString = Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&');
|
||||
const fullUrl = `${url}?${queryString}`;
|
||||
wx.navigateTo({
|
||||
url: fullUrl,
|
||||
fail: (err) => {
|
||||
console.error('跳转失败:', err);
|
||||
wx.showToast({ title: '跳转失败,请稍后重试', icon: 'none' });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async onQiangdanTap(e) {
|
||||
const dingdanItem = e.currentTarget.dataset.item;
|
||||
if (!dingdanItem) return;
|
||||
|
||||
if (!this.data.dashoustatus || this.data.dashoustatus != 1) {
|
||||
wx.showToast({ title: '请先开启接单员身份', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (this.data.zhuanghaoStatus != 1) {
|
||||
wx.showToast({ title: '账号异常,无法接单', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (this.data.dashouzhuangtai != 1) {
|
||||
wx.showToast({ title: '您有订单正在接待中', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (dingdanItem.isZhiding) {
|
||||
if (!this.data.uid || this.data.uid != dingdanItem.zhiding_uid) {
|
||||
wx.showToast({ title: '此订单为指定单', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (dingdanItem.yaoqiuleixing == 1) {
|
||||
const hasHuiyuan = this.data.huiyuanList.some(h => h.huiyuanid == dingdanItem.huiyuan_id);
|
||||
if (!hasHuiyuan) {
|
||||
wx.showToast({ title: '未开通对应会员,无法抢单', icon: 'none', duration: 2000 });
|
||||
setTimeout(() => this.goToChongzhiPage('huiyuan', dingdanItem.huiyuan_id), 500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (dingdanItem.yaoqiuleixing == 2) {
|
||||
if (this.data.yajin < dingdanItem.yajin) {
|
||||
wx.showToast({ title: `押金不足,需${dingdanItem.yajin}`, icon: 'none', duration: 2000 });
|
||||
setTimeout(() => this.goToChongzhiPage('yajin', null, dingdanItem.yajin), 500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.data.jifen < 5) {
|
||||
wx.showToast({ title: '积分不足,至少需要5积分', icon: 'none', duration: 2000 });
|
||||
setTimeout(() => this.goToChongzhiPage('jifen'), 500);
|
||||
return;
|
||||
}
|
||||
|
||||
const that = this;
|
||||
wx.showModal({
|
||||
title: '确认抢单',
|
||||
content: `确定要抢此订单吗?${dingdanItem.isZhiding ? '(指定订单)' : ''}`,
|
||||
success: async function (res) {
|
||||
if (res.confirm) {
|
||||
wx.showLoading({ title: '抢单中...', mask: true });
|
||||
try {
|
||||
const qiangdanRes = await request({
|
||||
url: '/dingdan/qiangdan',
|
||||
method: 'POST',
|
||||
data: { dingdan_id: dingdanItem.dingdan_id }
|
||||
});
|
||||
wx.hideLoading();
|
||||
if (qiangdanRes.data.code === 200 || qiangdanRes.data.code === 0) {
|
||||
that.showQiangdanSuccessEffect();
|
||||
if (app.globalData) {
|
||||
app.globalData.dashouzhuangtai = 0;
|
||||
that.setData({ dashouzhuangtai: 0 });
|
||||
}
|
||||
const newList = that.data.dingdanList.filter(item => item.dingdan_id !== dingdanItem.dingdan_id);
|
||||
that.setData({ dingdanList: newList });
|
||||
} else {
|
||||
wx.showToast({ title: qiangdanRes.data.msg || '抢单失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
wx.hideLoading();
|
||||
console.error('抢单请求失败:', error);
|
||||
wx.showToast({ title: '网络错误,抢单失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showQiangdanSuccessEffect() {
|
||||
wx.showToast({ title: '抢单成功!', icon: 'success', duration: 2000 });
|
||||
},
|
||||
|
||||
onViewDetail(e) {
|
||||
const { type, content } = e.currentTarget.dataset;
|
||||
wx.showModal({
|
||||
title: type === 'jieshao' ? '订单介绍' : '订单备注',
|
||||
content: content,
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
});
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
const now = Date.now();
|
||||
if (now - this.data.lastRefreshTime < this.data.refreshCooldown) {
|
||||
wx.showToast({
|
||||
title: `操作太快,请${Math.ceil((this.data.refreshCooldown - (now - this.data.lastRefreshTime)) / 1000)}秒后再试`,
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
});
|
||||
return;
|
||||
}
|
||||
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();
|
||||
if (now - this.data.lastRefreshTime < this.data.refreshCooldown) {
|
||||
this.setData({ scrollViewRefreshing: false });
|
||||
wx.showToast({
|
||||
title: `操作太快,请${Math.ceil((this.data.refreshCooldown - (now - this.data.lastRefreshTime)) / 1000)}秒后再试`,
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.data.isLoading) {
|
||||
this.setData({ scrollViewRefreshing: false });
|
||||
return;
|
||||
}
|
||||
this.setData({
|
||||
scrollViewRefreshing: true,
|
||||
lastRefreshTime: now
|
||||
});
|
||||
if (this.data.xuanzhongLeixingId) {
|
||||
this.setData({ page: 1, hasMore: true });
|
||||
this.loadDingdanList(true);
|
||||
} else {
|
||||
this.setData({ scrollViewRefreshing: false });
|
||||
}
|
||||
},
|
||||
|
||||
goToDashouRegister() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/fighter/fighter'
|
||||
});
|
||||
}
|
||||
});
|
||||
+533
-121
@@ -1,9 +1,25 @@
|
||||
// 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, getCachedGonggaoLunbo } from '../../utils/display-config.js';
|
||||
import { checkPenaltyForGrab } from '../../utils/grab-order-gate.js';
|
||||
import { ensureRoleAgreement } from '../../utils/role-agreement-gate.js';
|
||||
import { fetchMyZhidingOrders, submitZhidingResponse, processZhidingList, filterZhidingBannerList, ZHIDING_HF_MAP } from '../../utils/zhiding-order.js';
|
||||
import { isFighterOnly, FIGHTER_MINE, isDashouCertified, canEnterAcceptOrder, kickToLoginPage } from '../../utils/fighter-only.js';
|
||||
|
||||
/** 成交指标芯片:有真实样本才展示,过滤假 0% */
|
||||
import { buildDealStatChips } from '../../utils/deal-stat-display.js';
|
||||
import { consumeBadgeFields, mergeTypePendingCounts } from '../../utils/dashou-badges.js';
|
||||
|
||||
/** 抢单池轮播 page_key(勿与点单页 order_pool 混用) */
|
||||
const ACCEPT_ORDER_PAGE_KEY = 'accept_order';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -30,7 +46,7 @@ Page({
|
||||
// 4. 刷新控制字段
|
||||
scrollViewRefreshing: false,
|
||||
lastRefreshTime: 0,
|
||||
refreshCooldown: 2000,
|
||||
refreshCooldown: 0,
|
||||
isLoadingMore: false,
|
||||
|
||||
// 5. 切换类型冷却
|
||||
@@ -47,16 +63,142 @@ 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();
|
||||
// 未登录 / 未认证:立刻去登录页,禁止在抢单池空转卡死
|
||||
if (isFighterOnly() && !canEnterAcceptOrder()) {
|
||||
wx.reLaunch({ url: FIGHTER_MINE });
|
||||
return;
|
||||
}
|
||||
|
||||
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, ACCEPT_ORDER_PAGE_KEY)) {
|
||||
const gonggaoCached = getCachedGonggaoLunbo(app, ACCEPT_ORDER_PAGE_KEY);
|
||||
const lunboList = this.processLunboUrls((gonggaoCached && gonggaoCached.shangpinlunbo) || []);
|
||||
const gonggao = (gonggaoCached && gonggaoCached.shangpingonggao) || '';
|
||||
this.setData({ lunboList, gonggao });
|
||||
this.persistPageCache({ lunboList, gonggao });
|
||||
}
|
||||
this.loadGlobalStatus();
|
||||
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);
|
||||
this.tryShowAcceptOrderPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadGlobalStatus();
|
||||
await this.loadShangpinLeixing();
|
||||
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;
|
||||
|
||||
this.tryShowAcceptOrderPopup();
|
||||
this._skipShowRefresh = true;
|
||||
},
|
||||
|
||||
async tryShowAcceptOrderPopup() {
|
||||
if (app.globalData._acceptOrderPopupDone) return;
|
||||
// 先确保 OSS 配置已加载,再拉弹窗(否则相对 URL 拼不出图)
|
||||
await this.ensureOssReadyForPopup(4000);
|
||||
if (app.globalData._acceptOrderPopupDone) return;
|
||||
app.globalData._acceptOrderPopupDone = true;
|
||||
PopupService.checkAndShow(this, 'jiedan');
|
||||
},
|
||||
|
||||
ensureOssReadyForPopup(timeoutMs = 3000) {
|
||||
const hasOss = () => !!(app.globalData && app.globalData.ossImageUrl);
|
||||
if (hasOss()) return Promise.resolve(true);
|
||||
try {
|
||||
const cached = app.readConfigFromStorage && app.readConfigFromStorage();
|
||||
if (cached && app.applyDynamicConfig) {
|
||||
app.applyDynamicConfig(cached);
|
||||
}
|
||||
} catch (e) {}
|
||||
if (hasOss()) return Promise.resolve(true);
|
||||
return new Promise((resolve) => {
|
||||
let finished = false;
|
||||
const done = () => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
if (app.off) app.off('configApplied', onCfg);
|
||||
clearInterval(poll);
|
||||
clearTimeout(to);
|
||||
resolve(hasOss());
|
||||
};
|
||||
const onCfg = () => done();
|
||||
if (app.on) app.on('configApplied', onCfg);
|
||||
const poll = setInterval(() => { if (hasOss()) done(); }, 100);
|
||||
const to = setTimeout(done, timeoutMs);
|
||||
});
|
||||
},
|
||||
|
||||
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 () {
|
||||
// 只释放 PopupService 锁,不强制关掉正在展示的弹窗内容(避免刚拼好 URL 就被清掉)
|
||||
PopupService.reset();
|
||||
},
|
||||
|
||||
onUnload: function () {
|
||||
const popupComp = this.selectComponent('#popupNotice');
|
||||
if (popupComp && popupComp.cleanup) {
|
||||
popupComp.cleanup();
|
||||
@@ -64,65 +206,101 @@ Page({
|
||||
},
|
||||
|
||||
async onShow() {
|
||||
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
|
||||
if (!phoneOk) return;
|
||||
if (isFighterOnly() && !canEnterAcceptOrder()) {
|
||||
wx.reLaunch({ url: FIGHTER_MINE });
|
||||
return;
|
||||
}
|
||||
|
||||
// 每次进入抢单池都校验手机号认证
|
||||
{
|
||||
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
|
||||
if (!phoneOk) return;
|
||||
}
|
||||
|
||||
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);
|
||||
reconnectForRole('dashou');
|
||||
if (app.startImWhenReady) app.startImWhenReady();
|
||||
}
|
||||
|
||||
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() {
|
||||
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;
|
||||
async loadGonggaoAndLunbo(forceFetch = false) {
|
||||
const cached = getCachedGonggaoLunbo(app, ACCEPT_ORDER_PAGE_KEY);
|
||||
let lunboRaw = (cached && cached.shangpinlunbo) || [];
|
||||
let gonggaoText = (cached && cached.shangpingonggao) || '';
|
||||
|
||||
if (forceFetch || !isGonggaoCacheValid(app, ACCEPT_ORDER_PAGE_KEY)) {
|
||||
try {
|
||||
const data = await fetchGonggaoLunbo(app, ACCEPT_ORDER_PAGE_KEY);
|
||||
if (data) {
|
||||
lunboRaw = data.shangpinlunbo || [];
|
||||
gonggaoText = data.shangpingonggao || '';
|
||||
}
|
||||
} catch (e) {
|
||||
const fallback = getCachedGonggaoLunbo(app, ACCEPT_ORDER_PAGE_KEY);
|
||||
lunboRaw = (fallback && fallback.shangpinlunbo) || [];
|
||||
gonggaoText = (fallback && fallback.shangpingonggao) || '';
|
||||
}
|
||||
this.setData({
|
||||
zhuanghaoStatus: patch.zhanghaoStatus,
|
||||
dashouzhuangtai: patch.dashouzhuangtai,
|
||||
huiyuanList: patch.clumber,
|
||||
yajin: patch.yajin,
|
||||
jifen: patch.jifen,
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const lunboList = this.processLunboUrls(lunboRaw);
|
||||
const gonggao = gonggaoText;
|
||||
this.setData({ lunboList, gonggao });
|
||||
return { lunboList, gonggao };
|
||||
},
|
||||
|
||||
registerNotificationComponent() {
|
||||
@@ -148,9 +326,36 @@ Page({
|
||||
});
|
||||
},
|
||||
|
||||
/** 拉取最新打手信息(会员/押金/积分),并刷新订单卡片上的会员展示 */
|
||||
async syncDashouProfileFromServer() {
|
||||
if (!wx.getStorageSync('token')) return;
|
||||
try {
|
||||
const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' });
|
||||
if (res && res.data && res.data.code == 200 && res.data.data) {
|
||||
const data = res.data.data;
|
||||
if (Number(data.zhanghaostatus) !== 1) {
|
||||
kickToLoginPage(
|
||||
data.fengjin_yuanyin
|
||||
? `账号状态异常,您的账号已被封禁。原因:${data.fengjin_yuanyin}`
|
||||
: '账号状态异常,您的账号已被封禁,请联系客服'
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
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',
|
||||
@@ -166,15 +371,18 @@ Page({
|
||||
else if (Array.isArray(data.list)) list = data.list;
|
||||
else if (Array.isArray(data)) list = data;
|
||||
}
|
||||
const processedList = this.processTupianUrl(list);
|
||||
const processedList = mergeTypePendingCounts(this.processTupianUrl(list), data.data?.type_pending_counts);
|
||||
consumeBadgeFields(data.data || {});
|
||||
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 +390,7 @@ Page({
|
||||
} catch (error) {
|
||||
wx.showToast({ title: '网络错误,加载失败', icon: 'none' });
|
||||
} finally {
|
||||
wx.hideLoading();
|
||||
if (showLoading) wx.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -249,8 +457,8 @@ Page({
|
||||
xuanzhongBiaoqianId: 0 // 重置标签筛选
|
||||
});
|
||||
await this.loadDingdanList(true);
|
||||
// 🆕 切换类型后加载新标签
|
||||
await this.loadBankuaiBiaoqian();
|
||||
this.persistPageCache();
|
||||
} catch (error) {
|
||||
console.error('切换类型失败:', error);
|
||||
} finally {
|
||||
@@ -270,19 +478,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;
|
||||
|
||||
this.setData({
|
||||
isLoading: true,
|
||||
isLoadingMore: !isRefresh
|
||||
});
|
||||
if (!silent) {
|
||||
this.setData({
|
||||
isLoading: true,
|
||||
isLoadingMore: !isRefresh,
|
||||
});
|
||||
}
|
||||
this._listRequesting = true;
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
@@ -302,11 +515,18 @@ Page({
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false
|
||||
});
|
||||
this._listRequesting = false;
|
||||
|
||||
if (res.data.code === 200 || res.data.code === 0) {
|
||||
const newList = res.data.data.list || [];
|
||||
const hasMore = res.data.data.has_more || false;
|
||||
const serverClumber = res.data.data.clumber;
|
||||
const payload = res.data.data || {};
|
||||
consumeBadgeFields(payload);
|
||||
if (payload.type_pending_counts) {
|
||||
const merged = mergeTypePendingCounts(this.data.shangpinleixing, payload.type_pending_counts);
|
||||
this.setData({ shangpinleixing: merged });
|
||||
}
|
||||
const newList = payload.list || [];
|
||||
const hasMore = payload.has_more || false;
|
||||
const serverClumber = payload.clumber;
|
||||
if (Array.isArray(serverClumber) && serverClumber.length) {
|
||||
app.globalData.clumber = serverClumber;
|
||||
this.setData({ huiyuanList: serverClumber });
|
||||
@@ -322,6 +542,7 @@ Page({
|
||||
page: loadPage,
|
||||
hasMore: hasMore
|
||||
});
|
||||
this.persistPageCache();
|
||||
|
||||
if (isRefresh) {
|
||||
this.setData({ lastRefreshTime: Date.now() });
|
||||
@@ -336,25 +557,36 @@ Page({
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false
|
||||
});
|
||||
wx.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
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,33 +594,49 @@ 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)
|
||||
|
||||
const dealStatChips = buildDealStatChips(item.shangjia_deal_stat, {
|
||||
dealLabel: '成交率',
|
||||
fineLabel: '罚款率',
|
||||
});
|
||||
|
||||
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,
|
||||
dealStatChips,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -399,13 +647,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 +675,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 +756,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: '未开通对应会员,无法抢单',
|
||||
@@ -495,6 +796,9 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
const signed = await ensureRoleAgreement('dashou', 'qiangdan');
|
||||
if (!signed) return;
|
||||
|
||||
// 确认框
|
||||
const that = this;
|
||||
wx.showModal({
|
||||
@@ -521,6 +825,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 +857,138 @@ 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() {},
|
||||
|
||||
goToComplaintCreate() {
|
||||
wx.navigateTo({ url: '/pages/gongdan/create/create' });
|
||||
},
|
||||
|
||||
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 +996,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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"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",
|
||||
"popup-notice": "/components/popup-notice/popup-notice"
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
refresher-default-style="black"
|
||||
|
||||
refresher-background="#f5f3ff"
|
||||
refresher-background="#fff8e1"
|
||||
|
||||
refresher-triggered="{{scrollViewRefreshing}}"
|
||||
|
||||
@@ -38,6 +38,44 @@
|
||||
|
||||
|
||||
|
||||
<!-- 公告置顶 -->
|
||||
<view class="tousu-top-bar" catchtap="goToComplaintCreate">
|
||||
<text class="tousu-top-bar-txt">投诉 / 反馈</text>
|
||||
<text class="tousu-top-bar-sub">点这里提交工单</text>
|
||||
<text class="tousu-top-bar-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<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 +88,7 @@
|
||||
|
||||
indicator-color="rgba(255,255,255,0.6)"
|
||||
|
||||
indicator-active-color="#9333ea"
|
||||
indicator-active-color="#ffd061"
|
||||
|
||||
autoplay="{{lunboList.length > 1}}"
|
||||
|
||||
@@ -74,11 +112,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}}">
|
||||
|
||||
@@ -98,7 +136,10 @@
|
||||
|
||||
<view wx:if="{{xuanzhongLeixingId == item.id}}" class="guangyun-effect"></view>
|
||||
|
||||
<image class="leixing-tupian" src="{{item.full_tupian_url}}" mode="aspectFill"/>
|
||||
<view class="leixing-pic-wrap">
|
||||
<image class="leixing-tupian" src="{{item.full_tupian_url}}" mode="aspectFill"/>
|
||||
<view class="leixing-badge" wx:if="{{item.pending_badge}}">{{item.pending_badge}}</view>
|
||||
</view>
|
||||
|
||||
<text class="leixing-jieshao">{{item.jieshao}}</text>
|
||||
|
||||
@@ -176,7 +217,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 +231,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 +243,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 +271,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 +281,10 @@
|
||||
|
||||
<text class="xuqiu-label">需求标签</text>
|
||||
|
||||
<scroll-view class="biaoqian-inline-scroll" scroll-x>
|
||||
|
||||
<chenghao-tag wx:for="{{item.xuqiu_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
|
||||
|
||||
<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 +293,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 +305,87 @@
|
||||
|
||||
|
||||
|
||||
<!-- 优质商家订单(金牌卡片) -->
|
||||
<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 class="merchant-stat-row" wx:if="{{item.dealStatChips.length}}">
|
||||
<text class="merchant-stat {{c.hl ? 'highlight' : ''}}" wx:for="{{item.dealStatChips}}" wx:for-item="c" wx:key="k">{{c.t}}</text>
|
||||
</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,21 +399,30 @@
|
||||
|
||||
<view class="order-con">
|
||||
|
||||
<view wx:if="{{item.isZhiding}}" class="zhiding-strip myflex">
|
||||
|
||||
<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>
|
||||
<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="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>
|
||||
<view class="m-meta-col">
|
||||
<text class="m-name-sm">{{item.sjnicheng || '未知商家'}}</text>
|
||||
<view class="merchant-stat-row" wx:if="{{item.dealStatChips.length}}">
|
||||
<text class="merchant-stat {{c.hl ? 'highlight' : ''}}" wx:for="{{item.dealStatChips}}" wx:for-item="c" wx:key="k">{{c.t}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
@@ -318,24 +450,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>
|
||||
|
||||
<chenghao-tag wx:for="{{item.shangjia_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
|
||||
<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">
|
||||
|
||||
<chenghao-tag wx:for="{{item.xuqiu_biaoqian}}" wx:for-item="tag" wx:key="id" mingcheng="{{tag.mingcheng}}" texiaoJson="{{tag.texiao_json}}" />
|
||||
<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 +485,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 +525,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>
|
||||
|
||||
@@ -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;
|
||||
@@ -8,6 +14,34 @@
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tousu-top-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin: 16rpx 24rpx 8rpx;
|
||||
padding: 18rpx 24rpx;
|
||||
border-radius: 14rpx;
|
||||
background: linear-gradient(90deg, #e85a3a 0%, #f07858 100%);
|
||||
box-shadow: 0 6rpx 18rpx rgba(232, 90, 58, 0.28);
|
||||
z-index: 5;
|
||||
position: relative;
|
||||
}
|
||||
.tousu-top-bar-txt {
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tousu-top-bar-sub {
|
||||
flex: 1;
|
||||
color: rgba(255,255,255,0.88);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.tousu-top-bar-arrow {
|
||||
color: #fff;
|
||||
font-size: 36rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 背景动态纹理,增加层次感 */
|
||||
.qiangdan-page::before {
|
||||
@@ -88,6 +122,31 @@
|
||||
transition: all 0.3s ease;
|
||||
border: 2rpx solid rgba(0, 200, 255, 0.4);
|
||||
}
|
||||
|
||||
.leixing-pic-wrap {
|
||||
position: relative;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.leixing-badge {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -12rpx;
|
||||
min-width: 32rpx;
|
||||
height: 32rpx;
|
||||
padding: 0 8rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 16rpx;
|
||||
background: #fa3534;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
line-height: 32rpx;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.leixing-active .leixing-tupian {
|
||||
box-shadow: 0 16rpx 40rpx rgba(0, 200, 255, 0.5);
|
||||
@@ -126,6 +185,7 @@
|
||||
flex: 1;
|
||||
height: 0;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.scroll-bottom-spacer {
|
||||
@@ -486,7 +546,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 +564,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 +824,189 @@
|
||||
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: 86%;
|
||||
max-width: 600rpx;
|
||||
max-height: 72vh;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 24rpx 64rpx rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.zhiding-modal--sm { max-width: 540rpx; }
|
||||
.zhiding-modal-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 22rpx 26rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.zhiding-modal-title { font-size: 30rpx; font-weight: 700; color: #222; }
|
||||
.zhiding-modal-close { font-size: 44rpx; color: #666; line-height: 1; padding: 0 8rpx; }
|
||||
.zhiding-modal-body {
|
||||
padding: 22rpx 26rpx;
|
||||
background: #fff;
|
||||
max-height: 52vh;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// pages/alipay-webview/alipay-webview.js
|
||||
// 支付宝当面付二维码展示页(替代原 web-view 方案,避免 alipay:// scheme 限制)
|
||||
// 通用化:支持通过 pollUrl 参数指定轮询接口,兼容 code===0(订单)和 code===200(Czjilu类)双成功码
|
||||
// 支付成功后通过 EventChannel 通知原页面刷新状态
|
||||
const app = getApp()
|
||||
import request from '../../utils/request.js'
|
||||
|
||||
Page({
|
||||
data: {
|
||||
qrImage: '',
|
||||
orderId: '',
|
||||
pollUrl: '/dingdan/fucha', // 默认订单轮询接口
|
||||
statusText: '等待支付...',
|
||||
showBack: false,
|
||||
pollCount: 0,
|
||||
maxPollCount: 60,
|
||||
isPaid: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const orderId = options.orderId || ''
|
||||
// base64 图片较大,通过 globalData 传递避免 URL 长度限制
|
||||
const qrImage = app.globalData.alipayQrImage || ''
|
||||
app.globalData.alipayQrImage = ''
|
||||
// 允许调用方指定轮询接口(需 URL 编码),未指定则用默认订单接口
|
||||
const pollUrl = options.pollUrl ? decodeURIComponent(options.pollUrl) : '/dingdan/fucha'
|
||||
this.setData({ qrImage, orderId, pollUrl })
|
||||
// 获取 opener 事件通道,用于支付成功后通知原页面刷新状态
|
||||
this.openerEventChannel = this.getOpenerEventChannel ? this.getOpenerEventChannel() : null
|
||||
this._pollTimer = null
|
||||
this._hidden = false
|
||||
if (orderId) {
|
||||
this.startPolling()
|
||||
}
|
||||
},
|
||||
|
||||
startPolling() {
|
||||
if (this.data.isPaid) return
|
||||
if (this.data.pollCount >= this.data.maxPollCount) {
|
||||
this.setData({ statusText: '支付确认超时,如已支付请点击返回', showBack: true })
|
||||
return
|
||||
}
|
||||
this.setData({ pollCount: this.data.pollCount + 1 })
|
||||
this.pollOrderStatus()
|
||||
},
|
||||
|
||||
pollOrderStatus() {
|
||||
if (this.data.isPaid) return
|
||||
request({
|
||||
url: this.data.pollUrl,
|
||||
method: 'POST',
|
||||
data: { dingdanid: this.data.orderId }
|
||||
})
|
||||
.then(res => {
|
||||
if (this.data.isPaid) return
|
||||
// 兼容两种成功码:订单接口 code===0,Czjilu类接口 code===200
|
||||
if (res.statusCode === 200 && res.data && (res.data.code === 0 || res.data.code === 200)) {
|
||||
this.setData({ isPaid: true, statusText: '支付成功!', showBack: true })
|
||||
wx.showToast({ title: '支付成功!', icon: 'success', duration: 1500 })
|
||||
// 通知原页面刷新状态(原页面通过 wx.navigateTo events 注册 onAlipaySuccess)
|
||||
if (this.openerEventChannel && this.openerEventChannel.emit) {
|
||||
try {
|
||||
this.openerEventChannel.emit('onAlipaySuccess', { orderId: this.data.orderId, data: res.data })
|
||||
} catch (e) {}
|
||||
}
|
||||
} else {
|
||||
this.setData({ statusText: '等待支付...' })
|
||||
this._pollTimer = setTimeout(() => this.startPolling(), 3000)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!this.data.isPaid) {
|
||||
this._pollTimer = setTimeout(() => this.startPolling(), 3000)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onBack() {
|
||||
wx.navigateBack()
|
||||
},
|
||||
|
||||
onHide() {
|
||||
// 切后台时暂停轮询并清理定时器,但不标记为已支付,以便 onShow 恢复
|
||||
this._hidden = true
|
||||
if (this._pollTimer) {
|
||||
clearTimeout(this._pollTimer)
|
||||
this._pollTimer = null
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 从后台恢复时,如果未支付且未超时,恢复轮询
|
||||
if (this._hidden && !this.data.isPaid && this.data.orderId &&
|
||||
this.data.pollCount < this.data.maxPollCount) {
|
||||
this._hidden = false
|
||||
this.startPolling()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "支付宝支付",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<view class="qr-container">
|
||||
<view class="qr-title">支付宝扫码支付</view>
|
||||
<image wx:if="{{qrImage}}" class="qr-image" src="{{qrImage}}" mode="aspectFit" />
|
||||
<view class="qr-tip">请使用 <text class="qr-emph">支付宝</text> APP 扫描上方二维码完成支付</view>
|
||||
<view class="qr-status">{{statusText}}</view>
|
||||
<view class="qr-actions" wx:if="{{showBack}}">
|
||||
<view class="qr-btn" bindtap="onBack">返回</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,45 @@
|
||||
.qr-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx 40rpx;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.qr-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.qr-image {
|
||||
width: 500rpx;
|
||||
height: 500rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.qr-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-top: 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.qr-emph {
|
||||
color: #1677ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.qr-status {
|
||||
font-size: 28rpx;
|
||||
color: #1677ff;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.qr-actions {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
.qr-btn {
|
||||
background: #1677ff;
|
||||
color: #fff;
|
||||
border-radius: 8rpx;
|
||||
padding: 18rpx 80rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
@@ -303,14 +303,28 @@ Page({
|
||||
return;
|
||||
}
|
||||
if (applyFee > 0) {
|
||||
await this.startPayProcess();
|
||||
this.choosePayMethod();
|
||||
} else {
|
||||
await this.doFreeApply();
|
||||
}
|
||||
},
|
||||
|
||||
// 选择支付方式
|
||||
choosePayMethod() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['微信支付', '支付宝(扫码)'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
this.startPayProcess('wechat');
|
||||
} else if (res.tapIndex === 1) {
|
||||
this.startPayProcess('alipay');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 支付流程
|
||||
async startPayProcess() {
|
||||
async startPayProcess(payMethod = 'wechat') {
|
||||
this.setData({ payLoading: true });
|
||||
try {
|
||||
const res = await request({
|
||||
@@ -323,11 +337,20 @@ Page({
|
||||
reapply: this.data.isReapply,
|
||||
jilu_id: this.data.applyJiluId,
|
||||
shenheguan_id: this.data.selectedKaoheguanId, // 新增
|
||||
pay_method: payMethod === 'alipay' ? 'alipay' : 'wechat'
|
||||
}
|
||||
});
|
||||
if (res.data.code !== 200) throw new Error(res.data.message || '下单失败');
|
||||
const payParams = res.data.payParams;
|
||||
const dingdanid = res.data.dingdanid;
|
||||
|
||||
// 支付宝分支:跳转二维码页
|
||||
if (payMethod === 'alipay' || (payParams && payParams.pay_method === 'alipay')) {
|
||||
this.setData({ payLoading: false });
|
||||
this.triggerAlipayPayment(payParams, dingdanid);
|
||||
return;
|
||||
}
|
||||
|
||||
wx.requestPayment({
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
@@ -349,6 +372,32 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 支付宝扫码支付封装 ==========
|
||||
triggerAlipayPayment(payParams, orderId) {
|
||||
const qrImage = payParams && payParams.qr_image ? payParams.qr_image : '';
|
||||
if (!qrImage) {
|
||||
wx.showToast({ title: '支付宝二维码生成失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const app = getApp();
|
||||
app.globalData.alipayQrImage = qrImage;
|
||||
const pollUrl = encodeURIComponent('/yonghu/khzffc');
|
||||
const self = this;
|
||||
wx.navigateTo({
|
||||
url: `/pages/alipay-webview/alipay-webview?orderId=${orderId}&pollUrl=${pollUrl}`,
|
||||
events: {
|
||||
onAlipaySuccess: () => {
|
||||
// 复用微信流成功逻辑:关闭弹窗并刷新考核记录列表
|
||||
wx.showToast({ title: '申请成功', icon: 'success' });
|
||||
self.closeApplyModal();
|
||||
self.fetchAllTags();
|
||||
self.setData({ recordList: [], recordPage: 1 });
|
||||
self.fetchMyRecords(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 轮询支付结果
|
||||
async pollPayStatus(dingdanid) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// pages/category/category.js
|
||||
const app = getApp()
|
||||
import { createPage } from '../../utils/base-page.js';
|
||||
import { buildClubHeaders } from '../../utils/club-context.js';
|
||||
import { isFighterOnly, resolveFighterOnlyLaunchUrl } from '../../utils/fighter-only.js';
|
||||
|
||||
Page(createPage({
|
||||
data: {
|
||||
@@ -24,6 +26,10 @@ Page(createPage({
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
if (isFighterOnly()) {
|
||||
wx.reLaunch({ url: resolveFighterOnlyLaunchUrl() });
|
||||
return;
|
||||
}
|
||||
this.setData({
|
||||
ossImageUrl: app.globalData.ossImageUrl
|
||||
})
|
||||
@@ -31,6 +37,10 @@ Page(createPage({
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (isFighterOnly()) {
|
||||
wx.reLaunch({ url: resolveFighterOnlyLaunchUrl() });
|
||||
return;
|
||||
}
|
||||
this.registerNotificationComponent();
|
||||
},
|
||||
|
||||
@@ -114,9 +124,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+165
-69
@@ -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;
|
||||
}
|
||||
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,63 +256,91 @@ 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) => {
|
||||
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: resolveAvatarUrl(this.data.currentUser.avatar, app),
|
||||
};
|
||||
} else {
|
||||
m.senderData = {
|
||||
name: this.data.toName,
|
||||
avatar: resolveAvatarUrl(this.data.toAvatar, app),
|
||||
};
|
||||
}
|
||||
});
|
||||
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 update = {
|
||||
messages: unique,
|
||||
hasMore: list.length >= 20,
|
||||
lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp,
|
||||
loadingHistory: false
|
||||
};
|
||||
if (refresh) update.scrollToView = 'msg-bottom';
|
||||
this.setData(update);
|
||||
if (refresh) this.markPrivateMessageAsRead();
|
||||
resolve();
|
||||
},
|
||||
onFailed: () => { this.setData({ loadingHistory: false }); resolve(); }
|
||||
mapHistoryMessage(m, idx, list) {
|
||||
m = this.normalizeMessage(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: resolveAvatarUrl(this.data.currentUser.avatar, app),
|
||||
};
|
||||
} else {
|
||||
m.senderData = {
|
||||
name: this.data.toName,
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
const merged = mergeHistoryMessages({
|
||||
incoming: raw,
|
||||
existing: this.data.messages,
|
||||
refresh,
|
||||
limit: 20,
|
||||
mapMessage: (m, idx, list) => this.mapHistoryMessage(m, idx, list),
|
||||
});
|
||||
|
||||
const update = {
|
||||
messages: merged.messages,
|
||||
hasMore: merged.hasMore,
|
||||
lastTimestamp: merged.lastTimestamp,
|
||||
};
|
||||
if (refresh) update.scrollToView = 'msg-bottom';
|
||||
this.setData(update);
|
||||
if (refresh) this.markPrivateMessageAsRead();
|
||||
} catch (e) {
|
||||
console.warn('[私聊] 历史消息加载失败', e);
|
||||
if (!refresh) {
|
||||
wx.showToast({ title: '加载历史消息失败', icon: 'none' });
|
||||
}
|
||||
} finally {
|
||||
this.setData({ loadingHistory: false });
|
||||
}
|
||||
},
|
||||
|
||||
listenNewMsg() {
|
||||
|
||||
@@ -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));">
|
||||
|
||||
+247
-79
@@ -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) {
|
||||
this.setupAllListeners();
|
||||
this.loadHistory(true);
|
||||
const ready = async () => {
|
||||
this.setupAllListeners();
|
||||
await this.loadHistory(true);
|
||||
};
|
||||
|
||||
try {
|
||||
const ok = await waitChatImReady(app, 12000);
|
||||
if (ok) {
|
||||
await ready();
|
||||
return;
|
||||
} else {
|
||||
wx.goEasy.disconnect();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[客服] IM 连接失败', e);
|
||||
}
|
||||
this.connectGoEasy(targetUserId);
|
||||
|
||||
if (this.isConnected()) {
|
||||
await ready();
|
||||
} else {
|
||||
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 });
|
||||
},
|
||||
|
||||
mapHistoryMessage(m, idx, list) {
|
||||
m = this.normalizeMessage(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) {
|
||||
const av = resolveAvatarUrl(this.data.currentUser.avatar, app) || this.data.defaultAvatarUrl;
|
||||
m.senderData = { name: this.data.currentUser.name, avatar: av };
|
||||
} else {
|
||||
const av = resolveAvatarUrl(this.data.teamAvatar, app) || this.data.defaultAvatarUrl;
|
||||
m.senderData = { name: this.data.teamName, avatar: av };
|
||||
}
|
||||
return m;
|
||||
},
|
||||
|
||||
injectWelcomeMessage(messages) {
|
||||
const list = Array.isArray(messages) ? [...messages] : [];
|
||||
const welcomeId = 'welcome-cs';
|
||||
if (!list.some((m) => m.messageId === welcomeId)) {
|
||||
const welcome = this.createWelcomeMessage();
|
||||
welcome.messageId = welcomeId;
|
||||
welcome.showTime = true;
|
||||
list.unshift(welcome);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
|
||||
// 历史消息
|
||||
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,
|
||||
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,
|
||||
onSuccess: (res) => {
|
||||
let list = res.content || [];
|
||||
list.sort((a, b) => a.timestamp - b.timestamp);
|
||||
list.forEach((m, idx) => {
|
||||
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.senderData = { name: this.data.teamName, avatar: this.data.teamAvatar };
|
||||
}
|
||||
});
|
||||
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 welcomeId = 'welcome-cs';
|
||||
if (!unique.some(m => m.messageId === welcomeId)) {
|
||||
const welcome = this.createWelcomeMessage();
|
||||
welcome.messageId = welcomeId;
|
||||
welcome.showTime = true;
|
||||
unique.unshift(welcome);
|
||||
}
|
||||
|
||||
const update = {
|
||||
messages: unique,
|
||||
hasMore: list.length >= 20,
|
||||
lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp,
|
||||
loadingHistory: false
|
||||
};
|
||||
if (refresh) update.scrollToView = 'msg-bottom';
|
||||
this.setData(update);
|
||||
resolve();
|
||||
},
|
||||
onFailed: () => { this.setData({ loadingHistory: false }); resolve(); }
|
||||
});
|
||||
});
|
||||
|
||||
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: merged.messages,
|
||||
hasMore: merged.hasMore,
|
||||
lastTimestamp: merged.lastTimestamp,
|
||||
};
|
||||
if (refresh) update.scrollToView = 'msg-bottom';
|
||||
this.setData(update);
|
||||
} 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();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
<!-- 底部输入区域(保持不变) -->
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"navigationBarTitleText": "接单考试",
|
||||
"navigationBarBackgroundColor": "#c4b5fd",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{ "navigationBarTitleText": "加载中", "usingComponents": {} }
|
||||
@@ -0,0 +1 @@
|
||||
<view class="tip">正在进入打手端…</view>
|
||||
@@ -0,0 +1 @@
|
||||
.tip { display:flex; align-items:center; justify-content:center; min-height:100vh; color:#999; font-size:28rpx; }
|
||||
+278
-129
@@ -1,6 +1,9 @@
|
||||
// pages/edit/edit.js
|
||||
// 点单端设置页;纯打手端时:头像走 /yonghu/xiugai,昵称走 /yonghu/dsgxxx,手机只读(点单端已认证手机号)
|
||||
const app = getApp()
|
||||
import upload from '../../utils/upload.js'
|
||||
import request from '../../utils/request.js'
|
||||
import { isFighterOnly } from '../../utils/fighter-only.js'
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -15,54 +18,55 @@ Page({
|
||||
newAvatarFile: null,
|
||||
isLoading: true,
|
||||
isAvatarActive: false,
|
||||
nicknameError: ''
|
||||
nicknameError: '',
|
||||
// 打手扩展字段缓存,更新昵称时原样带回,避免后端把空串覆盖掉
|
||||
_dsDianhua: '',
|
||||
_dsWeixin: '',
|
||||
_dsJieshao: '',
|
||||
fighterMode: false,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
const fighterMode = isFighterOnly()
|
||||
this.setData({ fighterMode })
|
||||
wx.setNavigationBarTitle({ title: '编辑资料' })
|
||||
this.loadUserData()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.registerNotificationComponent();
|
||||
},
|
||||
|
||||
registerNotificationComponent() {
|
||||
const notificationComp = this.selectComponent('#global-notification');
|
||||
if (notificationComp && notificationComp.showNotification) {
|
||||
app.globalData.globalNotification = {
|
||||
show: (data) => notificationComp.showNotification(data),
|
||||
hide: () => notificationComp.hideNotification()
|
||||
};
|
||||
if (fighterMode) {
|
||||
this.loadFighterModeData()
|
||||
} else {
|
||||
this.loadUserData()
|
||||
}
|
||||
},
|
||||
|
||||
// 加载用户数据
|
||||
onShow() {
|
||||
this.registerNotificationComponent()
|
||||
},
|
||||
|
||||
registerNotificationComponent() {
|
||||
const notificationComp = this.selectComponent('#global-notification')
|
||||
if (notificationComp && notificationComp.showNotification) {
|
||||
app.globalData.globalNotification = {
|
||||
show: (data) => notificationComp.showNotification(data),
|
||||
hide: () => notificationComp.hideNotification(),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ——— 点单端:原逻辑 ———
|
||||
loadUserData() {
|
||||
try {
|
||||
const uid = wx.getStorageSync('uid') || ''
|
||||
const nicheng = wx.getStorageSync('nicheng') || ''
|
||||
const phone = wx.getStorageSync('phone') || ''
|
||||
const touxiang = wx.getStorageSync('touxiang') || ''
|
||||
|
||||
const ossImageUrl = app.globalData.ossImageUrl || ''
|
||||
const defaultAvatar = app.globalData.morentouxiang || ''
|
||||
|
||||
let avatarUrl = ''
|
||||
if (touxiang) {
|
||||
avatarUrl = touxiang.startsWith('http') ? touxiang : `${ossImageUrl}${touxiang}`
|
||||
} else if (defaultAvatar) {
|
||||
avatarUrl = defaultAvatar.startsWith('http') ? defaultAvatar : `${ossImageUrl}${defaultAvatar}`
|
||||
}
|
||||
|
||||
|
||||
this.setData({
|
||||
avatarUrl,
|
||||
avatarUrl: this.buildAvatarUrl(touxiang),
|
||||
uid,
|
||||
nicheng,
|
||||
phone,
|
||||
originalAvatar: touxiang,
|
||||
originalNicheng: nicheng,
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载用户数据失败:', error)
|
||||
@@ -71,157 +75,302 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 微信头像选择
|
||||
// ——— 纯打手端:手机/头像用点单端存储;昵称用打手资料 ———
|
||||
async loadFighterModeData() {
|
||||
this.setData({ isLoading: true })
|
||||
try {
|
||||
const uid = wx.getStorageSync('uid') || ''
|
||||
const phone = wx.getStorageSync('phone') || ''
|
||||
const touxiang = wx.getStorageSync('touxiang') || ''
|
||||
|
||||
let nicheng =
|
||||
wx.getStorageSync('dsnc') ||
|
||||
app.globalData.dashouNicheng ||
|
||||
''
|
||||
let dsDianhua = wx.getStorageSync('dsdianhua') || ''
|
||||
let dsWeixin = wx.getStorageSync('dsweixin') || ''
|
||||
let dsJieshao = wx.getStorageSync('dsjieshao') || ''
|
||||
|
||||
// 昵称缺失时拉打手资料(不碰点单端昵称)
|
||||
if (!nicheng || !String(nicheng).trim()) {
|
||||
try {
|
||||
const res = await request({ url: '/yonghu/dszjxxhq', method: 'POST' })
|
||||
if (res && res.data && res.data.code === 200 && res.data.data) {
|
||||
const d = res.data.data
|
||||
nicheng = d.nicheng || ''
|
||||
dsDianhua = d.dianhua || dsDianhua
|
||||
dsWeixin = d.wechat || dsWeixin
|
||||
dsJieshao = d.jieshao || dsJieshao
|
||||
wx.setStorageSync('dsnc', nicheng)
|
||||
wx.setStorageSync('dsdianhua', dsDianhua)
|
||||
wx.setStorageSync('dsweixin', dsWeixin)
|
||||
wx.setStorageSync('dsjieshao', dsJieshao)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('拉取打手昵称失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
this.setData({
|
||||
avatarUrl: this.buildAvatarUrl(touxiang),
|
||||
uid,
|
||||
phone,
|
||||
nicheng,
|
||||
originalAvatar: touxiang,
|
||||
originalNicheng: nicheng,
|
||||
_dsDianhua: dsDianhua,
|
||||
_dsWeixin: dsWeixin,
|
||||
_dsJieshao: dsJieshao,
|
||||
isLoading: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载打手设置数据失败:', error)
|
||||
this.setData({ isLoading: false })
|
||||
wx.showToast({ title: '加载数据失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
buildAvatarUrl(touxiang) {
|
||||
const ossImageUrl = app.globalData.ossImageUrl || ''
|
||||
const defaultAvatar = app.globalData.morentouxiang || ''
|
||||
if (touxiang) {
|
||||
return touxiang.startsWith('http') ? touxiang : `${ossImageUrl}${touxiang}`
|
||||
}
|
||||
if (defaultAvatar) {
|
||||
return defaultAvatar.startsWith('http') ? defaultAvatar : `${ossImageUrl}${defaultAvatar}`
|
||||
}
|
||||
return ''
|
||||
},
|
||||
|
||||
onChooseAvatar(e) {
|
||||
const avatarTempPath = e.detail.avatarUrl
|
||||
this.setData({
|
||||
avatarUrl: avatarTempPath,
|
||||
newAvatarFile: avatarTempPath
|
||||
}, () => {
|
||||
this.checkChanges()
|
||||
})
|
||||
this.setData(
|
||||
{
|
||||
avatarUrl: avatarTempPath,
|
||||
newAvatarFile: avatarTempPath,
|
||||
},
|
||||
() => this.checkChanges()
|
||||
)
|
||||
},
|
||||
|
||||
chooseAvatar() {
|
||||
this.setData({ isAvatarActive: true })
|
||||
setTimeout(() => {
|
||||
this.setData({ isAvatarActive: false })
|
||||
}, 500)
|
||||
setTimeout(() => this.setData({ isAvatarActive: false }), 500)
|
||||
},
|
||||
|
||||
// 昵称输入
|
||||
onNicknameInput(e) {
|
||||
const value = e.detail.value.trim()
|
||||
this.setData({
|
||||
nicheng: value,
|
||||
nicknameError: ''
|
||||
}, () => {
|
||||
this.checkChanges()
|
||||
})
|
||||
this.setData(
|
||||
{
|
||||
nicheng: value,
|
||||
nicknameError: '',
|
||||
},
|
||||
() => this.checkChanges()
|
||||
)
|
||||
},
|
||||
|
||||
// 检查变化
|
||||
checkChanges() {
|
||||
const { originalNicheng, nicheng, newAvatarFile } = this.data;
|
||||
const hasChanges = (nicheng !== originalNicheng) || (newAvatarFile !== null);
|
||||
this.setData({ hasChanges });
|
||||
const { originalNicheng, nicheng, newAvatarFile } = this.data
|
||||
const hasChanges = nicheng !== originalNicheng || newAvatarFile !== null
|
||||
this.setData({ hasChanges })
|
||||
},
|
||||
|
||||
validateData() {
|
||||
const { nicheng } = this.data
|
||||
let hasError = false
|
||||
this.setData({ nicknameError: '' })
|
||||
|
||||
if (nicheng && nicheng.length > 20) {
|
||||
this.setData({ nicknameError: '昵称不能超过20个字符' })
|
||||
hasError = true
|
||||
}
|
||||
if (nicheng !== this.data.originalNicheng && nicheng.trim() === '') {
|
||||
this.setData({ nicknameError: '昵称不能为空' })
|
||||
hasError = true
|
||||
}
|
||||
if (hasError) {
|
||||
if (this.data.nicknameError) {
|
||||
wx.showToast({ title: this.data.nicknameError, icon: 'none' })
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
// 保存
|
||||
async onSave() {
|
||||
if (!this.data.hasChanges) {
|
||||
wx.showToast({ title: '没有需要保存的更改', icon: 'none' });
|
||||
return;
|
||||
wx.showToast({ title: '没有需要保存的更改', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (this.data.isSaving) return;
|
||||
|
||||
// 验证数据
|
||||
if (!this.validateData()) return;
|
||||
|
||||
this.setData({ isSaving: true });
|
||||
wx.showLoading({ title: '保存中...', mask: true });
|
||||
|
||||
if (this.data.isSaving) return
|
||||
if (!this.validateData()) return
|
||||
|
||||
this.setData({ isSaving: true })
|
||||
wx.showLoading({ title: '保存中...', mask: true })
|
||||
|
||||
try {
|
||||
const formData = {};
|
||||
if (this.data.nicheng !== this.data.originalNicheng) {
|
||||
formData.nicheng = this.data.nicheng;
|
||||
}
|
||||
|
||||
const res = await upload({
|
||||
url: '/yonghu/xiugai',
|
||||
formData,
|
||||
filePath: this.data.newAvatarFile,
|
||||
fileName: 'avatar'
|
||||
});
|
||||
|
||||
if (res.statusCode === 200 && res.data) {
|
||||
const response = res.data;
|
||||
if (response.code === 0) {
|
||||
await this.handleUpdateSuccess(response.data);
|
||||
} else {
|
||||
throw new Error(response.msg || '保存失败');
|
||||
}
|
||||
if (this.data.fighterMode) {
|
||||
await this.saveFighterMode()
|
||||
} else {
|
||||
throw new Error('网络错误');
|
||||
await this.saveOrderMode()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
if (error.message.includes('认证失败') || error.message.includes('401')) {
|
||||
console.error('保存失败:', error)
|
||||
if (error.message && (error.message.includes('认证失败') || error.message.includes('401'))) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '登录已过期,请重新登录',
|
||||
complete: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.removeStorageSync('userInfo');
|
||||
wx.switchTab({ url: '/pages/mine/mine' });
|
||||
wx.removeStorageSync('token')
|
||||
wx.removeStorageSync('userInfo')
|
||||
wx.reLaunch({ url: '/pages/fighter/fighter' })
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: error.message || '保存失败', icon: 'none' });
|
||||
wx.showToast({ title: error.message || '保存失败', icon: 'none' })
|
||||
}
|
||||
} finally {
|
||||
wx.hideLoading();
|
||||
this.setData({ isSaving: false });
|
||||
wx.hideLoading()
|
||||
this.setData({ isSaving: false })
|
||||
}
|
||||
},
|
||||
|
||||
// 验证数据
|
||||
validateData() {
|
||||
const { nicheng } = this.data;
|
||||
let hasError = false;
|
||||
this.setData({ nicknameError: '' });
|
||||
|
||||
if (nicheng && nicheng.length > 20) {
|
||||
this.setData({ nicknameError: '昵称不能超过20个字符' });
|
||||
hasError = true;
|
||||
/** 点单端原保存:昵称+头像 → /yonghu/xiugai */
|
||||
async saveOrderMode() {
|
||||
const formData = {}
|
||||
if (this.data.nicheng !== this.data.originalNicheng) {
|
||||
formData.nicheng = this.data.nicheng
|
||||
}
|
||||
if (nicheng !== this.data.originalNicheng && nicheng.trim() === '') {
|
||||
this.setData({ nicknameError: '昵称不能为空' });
|
||||
hasError = true;
|
||||
const res = await upload({
|
||||
url: '/yonghu/xiugai',
|
||||
formData,
|
||||
filePath: this.data.newAvatarFile,
|
||||
fileName: 'avatar',
|
||||
})
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||
await this.handleUpdateSuccess(res.data.data || {})
|
||||
} else {
|
||||
throw new Error((res.data && (res.data.msg || res.data.message)) || '保存失败')
|
||||
}
|
||||
if (hasError) {
|
||||
if (this.data.nicknameError) {
|
||||
wx.showToast({ title: this.data.nicknameError, icon: 'none' });
|
||||
},
|
||||
|
||||
/**
|
||||
* 纯打手端混调:
|
||||
* - 头像变了 → /yonghu/xiugai(点单端,只传头像,不改点单昵称)
|
||||
* - 昵称变了 → /yonghu/dsgxxx(打手端,带回其它打手字段防覆盖)
|
||||
* 手机号只读,不提交
|
||||
*/
|
||||
async saveFighterMode() {
|
||||
const nickChanged = this.data.nicheng !== this.data.originalNicheng
|
||||
const avatarChanged = !!this.data.newAvatarFile
|
||||
let avatarData = null
|
||||
let nickData = null
|
||||
|
||||
if (avatarChanged) {
|
||||
const res = await upload({
|
||||
url: '/yonghu/xiugai',
|
||||
formData: {},
|
||||
filePath: this.data.newAvatarFile,
|
||||
fileName: 'avatar',
|
||||
})
|
||||
if (!(res.statusCode === 200 && res.data && res.data.code === 0)) {
|
||||
throw new Error((res.data && (res.data.msg || res.data.message)) || '头像保存失败')
|
||||
}
|
||||
return false;
|
||||
avatarData = res.data.data || {}
|
||||
}
|
||||
return true;
|
||||
|
||||
if (nickChanged) {
|
||||
const postData = {
|
||||
nicheng: this.data.nicheng,
|
||||
dianhua: this.data._dsDianhua || wx.getStorageSync('dsdianhua') || '',
|
||||
wechat: this.data._dsWeixin || wx.getStorageSync('dsweixin') || '',
|
||||
jieshao: this.data._dsJieshao || wx.getStorageSync('dsjieshao') || '',
|
||||
}
|
||||
const res = await request({
|
||||
url: '/yonghu/dsgxxx',
|
||||
method: 'POST',
|
||||
data: postData,
|
||||
})
|
||||
if (!(res && res.data && res.data.code === 200)) {
|
||||
throw new Error((res && res.data && res.data.msg) || '昵称保存失败')
|
||||
}
|
||||
nickData = res.data.data || postData
|
||||
}
|
||||
|
||||
this.handleFighterUpdateSuccess(avatarData, nickData)
|
||||
},
|
||||
|
||||
handleFighterUpdateSuccess(avatarData, nickData) {
|
||||
const ossImageUrl = app.globalData.ossImageUrl || ''
|
||||
let avatarUrl = this.data.avatarUrl
|
||||
let originalAvatar = this.data.originalAvatar
|
||||
let originalNicheng = this.data.originalNicheng
|
||||
let nicheng = this.data.nicheng
|
||||
|
||||
if (avatarData && avatarData.touxiang != null) {
|
||||
wx.setStorageSync('touxiang', avatarData.touxiang)
|
||||
originalAvatar = avatarData.touxiang
|
||||
avatarUrl = avatarData.touxiang.startsWith('http')
|
||||
? avatarData.touxiang
|
||||
: `${ossImageUrl}${avatarData.touxiang}`
|
||||
}
|
||||
|
||||
if (nickData) {
|
||||
const newNick = nickData.nicheng != null ? nickData.nicheng : nicheng
|
||||
nicheng = newNick
|
||||
originalNicheng = newNick
|
||||
wx.setStorageSync('dsnc', newNick)
|
||||
app.globalData.dashouNicheng = newNick
|
||||
if (nickData.dianhua != null) wx.setStorageSync('dsdianhua', nickData.dianhua || '')
|
||||
if (nickData.wechat != null) wx.setStorageSync('dsweixin', nickData.wechat || '')
|
||||
if (nickData.jieshao != null) wx.setStorageSync('dsjieshao', nickData.jieshao || '')
|
||||
}
|
||||
|
||||
this.setData({
|
||||
avatarUrl,
|
||||
nicheng,
|
||||
originalAvatar,
|
||||
originalNicheng,
|
||||
newAvatarFile: null,
|
||||
hasChanges: false,
|
||||
_dsDianhua: wx.getStorageSync('dsdianhua') || this.data._dsDianhua,
|
||||
_dsWeixin: wx.getStorageSync('dsweixin') || this.data._dsWeixin,
|
||||
_dsJieshao: wx.getStorageSync('dsjieshao') || this.data._dsJieshao,
|
||||
})
|
||||
|
||||
wx.showToast({ title: '保存成功', icon: 'success', duration: 2000 })
|
||||
},
|
||||
|
||||
// 更新成功处理
|
||||
async handleUpdateSuccess(updateData) {
|
||||
if (updateData.touxiang !== undefined && updateData.touxiang !== null) {
|
||||
wx.setStorageSync('touxiang', updateData.touxiang);
|
||||
wx.setStorageSync('touxiang', updateData.touxiang)
|
||||
}
|
||||
if (updateData.nicheng !== undefined && updateData.nicheng !== null) {
|
||||
wx.setStorageSync('nicheng', updateData.nicheng);
|
||||
wx.setStorageSync('nicheng', updateData.nicheng)
|
||||
}
|
||||
|
||||
|
||||
if (updateData.nicheng) {
|
||||
app.globalData.userNicheng = updateData.nicheng;
|
||||
app.globalData.userNicheng = updateData.nicheng
|
||||
}
|
||||
|
||||
const ossImageUrl = app.globalData.ossImageUrl || '';
|
||||
let avatarUrl = this.data.avatarUrl;
|
||||
|
||||
const ossImageUrl = app.globalData.ossImageUrl || ''
|
||||
let avatarUrl = this.data.avatarUrl
|
||||
if (updateData.touxiang) {
|
||||
avatarUrl = updateData.touxiang.startsWith('http') ?
|
||||
updateData.touxiang : `${ossImageUrl}${updateData.touxiang}`;
|
||||
avatarUrl = updateData.touxiang.startsWith('http')
|
||||
? updateData.touxiang
|
||||
: `${ossImageUrl}${updateData.touxiang}`
|
||||
}
|
||||
|
||||
|
||||
this.setData({
|
||||
avatarUrl,
|
||||
originalAvatar: updateData.touxiang || this.data.originalAvatar,
|
||||
originalNicheng: updateData.nicheng || this.data.originalNicheng,
|
||||
newAvatarFile: null,
|
||||
hasChanges: false
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
hasChanges: false,
|
||||
})
|
||||
|
||||
wx.showToast({ title: '保存成功', icon: 'success', duration: 2000 })
|
||||
},
|
||||
})
|
||||
@@ -27,14 +27,14 @@
|
||||
<view class="item-value readonly">{{uid || '••••••'}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 昵称(可编辑) -->
|
||||
<!-- 昵称(可编辑;纯打手端展示/保存的是打手昵称) -->
|
||||
<view class="form-item">
|
||||
<view class="item-label">昵称</view>
|
||||
<view class="item-label">{{fighterMode ? '服务昵称' : '昵称'}}</view>
|
||||
<view class="input-container">
|
||||
<input
|
||||
class="item-input {{nicknameError ? 'error' : ''}}"
|
||||
type="text"
|
||||
placeholder="请输入昵称"
|
||||
placeholder="{{fighterMode ? '请输入服务昵称' : '请输入昵称'}}"
|
||||
placeholder-class="placeholder"
|
||||
maxlength="20"
|
||||
value="{{nicheng}}"
|
||||
@@ -44,7 +44,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手机号(只读展示,不可修改) -->
|
||||
<!-- 手机号(只读:点单端已认证号码,不可手填) -->
|
||||
<view class="form-item">
|
||||
<view class="item-label">手机号</view>
|
||||
<view class="item-value readonly">{{phone || '未绑定'}}</view>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"chenghao-tag": "/components/chenghao-tag/chenghao-tag"
|
||||
},
|
||||
"backgroundTextStyle": "dark",
|
||||
"navigationBarBackgroundColor": "#fff8e1",
|
||||
"navigationBarBackgroundColor": "#f7dc51",
|
||||
"navigationBarTitleText": "常规发单",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f5f5f5",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
+683
-683
File diff suppressed because it is too large.
Load diff
@@ -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 {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -99,6 +99,15 @@
|
||||
<text class="item-label">订单状态:</text>
|
||||
<text class="item-value zhuangtai-text">{{zhuangtaiWenziMap[jibenShuju.zhuangtai] || '未知状态'}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item-row auto-settle-row" wx:if="{{autoSettleTip || autoSettleClock}}">
|
||||
<text class="item-label">自动结算:</text>
|
||||
<view class="auto-settle-value">
|
||||
<text class="auto-settle-clock" wx:if="{{autoSettleClock}}">{{autoSettleClock}}</text>
|
||||
<text class="item-value auto-settle-text">{{autoSettleTip}}</text>
|
||||
<text class="auto-settle-expire" wx:if="{{autoSettleExpireAt}}">到期 {{autoSettleExpireAt}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 发单平台信息 -->
|
||||
<view class="info-item-row" wx:if="{{jibenShuju.fadanpingtai !== undefined && jibenShuju.fadanpingtai !== null}}">
|
||||
@@ -108,6 +117,55 @@
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 组队面板 -->
|
||||
<view class="team-area" wx:if="{{jibenShuju.zhuangtai == 2 || team}}">
|
||||
<view class="info-title">组队</view>
|
||||
|
||||
<block wx:if="{{!team && jibenShuju.zhuangtai == 2 && isLeader}}">
|
||||
<text class="team-hint">本单进行中,可开启组队招募(最多再招 3 人)</text>
|
||||
<view class="team-open-btn" bindtap="openTeamForm">开启组队</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{team}}">
|
||||
<view class="info-item-row">
|
||||
<text class="item-label">状态:</text>
|
||||
<text class="item-value">{{teamStatusText}}</text>
|
||||
</view>
|
||||
<view class="info-item-row">
|
||||
<text class="item-label">人数:</text>
|
||||
<text class="item-value">已入队 {{team.joined_count}} / 目标 {{team.need_count}}</text>
|
||||
</view>
|
||||
<view class="info-item-row">
|
||||
<text class="item-label">分红:</text>
|
||||
<text class="item-value jine-green">¥{{teamShareText}}({{team.share_mode == 'fixed' ? '固定' : '平均'}})</text>
|
||||
</view>
|
||||
<view class="info-item-row" wx:if="{{team.remark}}">
|
||||
<text class="item-label">备注:</text>
|
||||
<text class="item-value">{{team.remark}}</text>
|
||||
</view>
|
||||
|
||||
<view class="team-members" wx:if="{{team.members && team.members.length}}">
|
||||
<text class="section-subtitle">组队接待者</text>
|
||||
<block wx:for="{{team.members}}" wx:key="user_uid">
|
||||
<view class="team-member-row">
|
||||
<text class="tm-role">{{item.role_text || (item.role == 'leader' ? '队长' : '队友')}}</text>
|
||||
<text class="tm-name">{{item.nicheng || '打手'}}</text>
|
||||
<text class="tm-uid">{{item.user_uid}}</text>
|
||||
<text class="tm-game" wx:if="{{item.game_id}}">游戏ID {{item.game_id}}</text>
|
||||
<view class="copy-btn" wx:if="{{item.game_id}}" bindtap="fuzhiWenben" data-text="{{item.game_id}}" catchtap="fuzhiWenben">
|
||||
<text class="copy-text">复制</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<view class="team-actions">
|
||||
<view class="team-chat-btn" bindtap="goToTeamGroupChat">进入订单群聊</view>
|
||||
<view class="team-action-btn ghost" wx:if="{{isLeader && team.status == 1 && jibenShuju.zhuangtai == 2}}" bindtap="lockTeam">关停招募</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 商家信息区域 -->
|
||||
<view
|
||||
@@ -151,14 +209,28 @@
|
||||
<!-- 退款信息区域 -->
|
||||
<view
|
||||
class="tuikuan-area"
|
||||
wx:if="{{showRefundInfo && xiangxiShuju.tuikuan_liyou}}"
|
||||
wx:if="{{showRefundInfo && (xiangxiShuju.tuikuan_liyou || xiangxiShuju.tuikuan_tupian.length)}}"
|
||||
>
|
||||
<view class="info-title">退款信息</view>
|
||||
|
||||
<view class="info-item-row">
|
||||
<view class="info-item-row" wx:if="{{xiangxiShuju.tuikuan_liyou}}">
|
||||
<text class="item-label">退款理由:</text>
|
||||
<text class="item-value">{{xiangxiShuju.tuikuan_liyou}}</text>
|
||||
</view>
|
||||
<view class="info-item-row" wx:if="{{xiangxiShuju.tuikuan_tupian.length}}" style="flex-direction:column;align-items:flex-start;">
|
||||
<text class="item-label" style="margin-bottom:12rpx;">相关图片:</text>
|
||||
<view style="display:flex;flex-wrap:wrap;">
|
||||
<block wx:for="{{xiangxiShuju.tuikuan_tupian}}" wx:key="*this">
|
||||
<image
|
||||
src="{{item}}"
|
||||
mode="aspectFill"
|
||||
style="width:140rpx;height:140rpx;margin:0 12rpx 12rpx 0;border-radius:8rpx;"
|
||||
bindtap="previewTuikuanTupian"
|
||||
data-url="{{item}}"
|
||||
/>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 处罚申请信息区域 -->
|
||||
@@ -179,7 +251,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>
|
||||
@@ -200,7 +272,7 @@
|
||||
|
||||
<!-- 接单员交付历史(只读模式) -->
|
||||
<view class="dashou-jiaofu-area" wx:if="{{!isModifying && xiangxiShuju.dashou_jiaofu.length > 0}}">
|
||||
<view class="info-title">我的交付</view>
|
||||
<view class="info-title">{{isLeader ? '订单交付' : '订单交付(只读)'}}</view>
|
||||
|
||||
<!-- 接单员交付图片 -->
|
||||
<view class="dashou-jiaofu-container">
|
||||
@@ -232,8 +304,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 🆕 修改模式下的可编辑区域(当 isModifying 为 true 时显示) -->
|
||||
<view class="dashou-jiaofu-area" wx:if="{{isModifying}}">
|
||||
<!-- 修改模式(仅队长 canModify) -->
|
||||
<view class="dashou-jiaofu-area" wx:if="{{isModifying && canModify}}">
|
||||
<view class="info-title">修改交付</view>
|
||||
|
||||
<!-- 图片编辑区域 -->
|
||||
@@ -299,8 +371,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交订单区域(仅状态=2时显示,保持不变) -->
|
||||
<view class="tijiao-area" wx:if="{{jibenShuju.zhuangtai == 2}}">
|
||||
<!-- 提交订单:仅队长(或非组队接单人) -->
|
||||
<view class="tijiao-area" wx:if="{{jibenShuju.zhuangtai == 2 && canSubmit}}">
|
||||
<view class="info-title">提交订单</view>
|
||||
|
||||
<!-- 选择图片 -->
|
||||
@@ -367,8 +439,8 @@
|
||||
<!-- ========== 底部按钮区域 ========== -->
|
||||
<view class="bottom-btns-container" wx:if="{{!isLoading}}">
|
||||
|
||||
<!-- 🆕 修改模式下的按钮(取消/确认) -->
|
||||
<block wx:if="{{isModifying}}">
|
||||
<!-- 修改模式按钮(仅队长 canModify) -->
|
||||
<block wx:if="{{isModifying && canModify}}">
|
||||
<view class="btn-row">
|
||||
<view
|
||||
class="bottom-btn cancel-modify-btn"
|
||||
@@ -417,8 +489,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 🆕 新增:修改按钮(仅当状态为4或8时显示) -->
|
||||
<view class="btn-row" wx:if="{{jibenShuju.zhuangtai == 4 || jibenShuju.zhuangtai == 8}}">
|
||||
<!-- 修改交付:仅队长 -->
|
||||
<view class="btn-row" wx:if="{{canModify && (jibenShuju.zhuangtai == 4 || jibenShuju.zhuangtai == 8)}}">
|
||||
<view
|
||||
class="bottom-btn modify-btn full-width"
|
||||
bindtap="enterModify"
|
||||
@@ -428,15 +500,21 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮(仅状态=2时显示) -->
|
||||
<!-- 提交:仅队长 -->
|
||||
<view
|
||||
class="bottom-btn submit-btn"
|
||||
wx:if="{{jibenShuju.zhuangtai == 2}}"
|
||||
wx:if="{{canSubmit && jibenShuju.zhuangtai == 2}}"
|
||||
bindtap="tijiaoDingdan"
|
||||
>
|
||||
<text class="btn-text">{{isSubmitting ? '提交中...' : '立即提交'}}</text>
|
||||
<view class="btn-shine"></view>
|
||||
</view>
|
||||
<view
|
||||
class="team-member-tip"
|
||||
wx:if="{{team && !isLeader && jibenShuju.zhuangtai == 2}}"
|
||||
>
|
||||
<text>组队中仅队长可提交交付,请等待队长操作</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
</view>
|
||||
@@ -472,6 +550,49 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开启组队弹窗 -->
|
||||
<view class="modal-container" wx:if="{{showTeamForm}}">
|
||||
<view class="modal-mask" bindtap="closeTeamForm"></view>
|
||||
<view class="modal-content team-form-modal">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">开启组队</text>
|
||||
<view class="modal-close" bindtap="closeTeamForm">×</view>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="tf-row">
|
||||
<text class="tf-label">招募人数(1~3)</text>
|
||||
<view class="tf-need">
|
||||
<view class="need-btn {{teamForm.need_count == 1 ? 'on' : ''}}" data-n="1" bindtap="setTeamNeed">1</view>
|
||||
<view class="need-btn {{teamForm.need_count == 2 ? 'on' : ''}}" data-n="2" bindtap="setTeamNeed">2</view>
|
||||
<view class="need-btn {{teamForm.need_count == 3 ? 'on' : ''}}" data-n="3" bindtap="setTeamNeed">3</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tf-row">
|
||||
<text class="tf-label">分钱方式</text>
|
||||
<view class="tf-need">
|
||||
<view class="need-btn {{teamForm.share_mode == 'fixed' ? 'on' : ''}}" data-m="fixed" bindtap="setTeamMode">固定值</view>
|
||||
<view class="need-btn {{teamForm.share_mode == 'average' ? 'on' : ''}}" data-m="average" bindtap="setTeamMode">平均值</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tf-row" wx:if="{{teamForm.share_mode == 'fixed'}}">
|
||||
<text class="tf-label">人均金额(元)</text>
|
||||
<input class="tf-input" type="digit" placeholder="队友每人" value="{{teamForm.per_person_amount}}" bindinput="onTeamPerPerson" />
|
||||
</view>
|
||||
<view class="tf-row">
|
||||
<text class="tf-label">队长游戏ID</text>
|
||||
<input class="tf-input" placeholder="必填,队内可见" value="{{teamForm.leader_game_id}}" bindinput="onTeamGameId" maxlength="64" />
|
||||
</view>
|
||||
<view class="tf-row">
|
||||
<text class="tf-label">组队备注(可选)</text>
|
||||
<input class="tf-input" placeholder="大厅可见" value="{{teamForm.remark}}" bindinput="onTeamRemark" maxlength="200" />
|
||||
</view>
|
||||
<view class="tf-actions">
|
||||
<view class="team-open-btn" bindtap="submitOpenTeam">{{teamSubmitting ? '提交中...' : '确认开启'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<global-notification id="global-notification" />
|
||||
<popup-notice id="popupNotice" />
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,6 +2,16 @@ const app = getApp();
|
||||
import { createPage, request } from '../../utils/base-page.js';
|
||||
import { reconnectForRole } from '../../utils/role-tab-bar.js';
|
||||
import { getOrderStatusText } from '../../utils/api-helper.js';
|
||||
import {
|
||||
attachAutoSettleFields,
|
||||
buildAutoSettleDisplay,
|
||||
startAutoSettleTicker,
|
||||
stopAutoSettleTicker,
|
||||
} from '../../utils/auto-settle-display.js';
|
||||
import { sanitizeMyDealStat, DEAL_STAT_HELP } from '../../utils/deal-stat-display.js';
|
||||
import { consumeBadgeFields } from '../../utils/dashou-badges.js';
|
||||
import { isFighterOnly, canEnterAcceptOrder, getLoginPageUrl } from '../../utils/fighter-only.js';
|
||||
import { ensurePhoneAuth } from '../../utils/phone-auth.js';
|
||||
|
||||
Page(createPage({
|
||||
data: {
|
||||
@@ -9,7 +19,7 @@ Page(createPage({
|
||||
shangpinleixing: [],
|
||||
xuanzhongLeixingId: null,
|
||||
|
||||
// 订单类型: normal 普通, peihu 陪护
|
||||
// 订单类型固定普通;旧陪护入口已下线(与组队无关)
|
||||
orderType: 'normal',
|
||||
|
||||
// 搜索关键字
|
||||
@@ -19,11 +29,12 @@ 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' },
|
||||
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' }
|
||||
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' },
|
||||
{ name: '待自动结算', key: 'dai_zidong_jiesuan', zhuangtaiList: [8], color: '#C62828', autoSettlePending: true },
|
||||
],
|
||||
currentStatusKey: 'all',
|
||||
|
||||
@@ -35,7 +46,8 @@ Page(createPage({
|
||||
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
|
||||
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
dai_zidong_jiesuan: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
},
|
||||
|
||||
currentList: [],
|
||||
@@ -45,22 +57,62 @@ Page(createPage({
|
||||
scrollViewRefreshing: false,
|
||||
defaultImg: '/images/default-order.png',
|
||||
ossImageUrl: app.globalData.ossImageUrl || '',
|
||||
dealStat: null,
|
||||
dealStatChips: [],
|
||||
showDealStatHelp: false,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
if (isFighterOnly() && !canEnterAcceptOrder()) {
|
||||
wx.reLaunch({ url: getLoginPageUrl() });
|
||||
return;
|
||||
}
|
||||
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() {
|
||||
async onShow() {
|
||||
if (isFighterOnly() && !canEnterAcceptOrder()) {
|
||||
wx.reLaunch({ url: getLoginPageUrl() });
|
||||
return;
|
||||
}
|
||||
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
|
||||
if (!phoneOk) return;
|
||||
|
||||
this.registerNotificationComponent();
|
||||
this.loadDealStat();
|
||||
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;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -102,9 +154,11 @@ Page(createPage({
|
||||
},
|
||||
|
||||
switchOrderType(e) {
|
||||
// 陪护入口已隐藏;保留方法避免旧事件残留报错
|
||||
const type = e.currentTarget.dataset.type;
|
||||
if (type !== 'normal') return;
|
||||
if (type === this.data.orderType) return;
|
||||
this.setData({ orderType: type });
|
||||
this.setData({ orderType: 'normal' });
|
||||
this.resetCurrentStatusData();
|
||||
this.loadCurrentStatusOrders(true);
|
||||
},
|
||||
@@ -128,6 +182,35 @@ Page(createPage({
|
||||
this.loadCurrentStatusOrders(true);
|
||||
},
|
||||
|
||||
async loadDealStat() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/dingdan/wo-de-deal-stat',
|
||||
method: 'POST',
|
||||
data: { role: 'dashou' },
|
||||
header: { 'content-type': 'application/json' },
|
||||
});
|
||||
if (res.data && (res.data.code === 0 || res.data.code === 200)) {
|
||||
const cleaned = sanitizeMyDealStat(res.data.data, 'dashou');
|
||||
this.setData({
|
||||
dealStat: cleaned,
|
||||
dealStatChips: (cleaned && cleaned.chips) || [],
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
/* 无样本静默 */
|
||||
}
|
||||
},
|
||||
|
||||
onDealStatHelpTap() {
|
||||
wx.showModal({
|
||||
title: '指标说明',
|
||||
content: DEAL_STAT_HELP.dashou,
|
||||
showCancel: false,
|
||||
confirmText: '知道了',
|
||||
});
|
||||
},
|
||||
|
||||
switchStatus(e) {
|
||||
const key = e.currentTarget.dataset.key;
|
||||
if (key === this.data.currentStatusKey) return;
|
||||
@@ -141,17 +224,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;
|
||||
|
||||
this.setData({
|
||||
[`dsDingdanShuju.${key}.isLoading`]: true,
|
||||
isLoading: true,
|
||||
isLoadingMore: !isRefresh
|
||||
});
|
||||
if (!silent) {
|
||||
this.setData({
|
||||
[`dsDingdanShuju.${key}.isLoading`]: true,
|
||||
isLoading: true,
|
||||
isLoadingMore: !isRefresh,
|
||||
});
|
||||
}
|
||||
this._listRequesting = true;
|
||||
|
||||
try {
|
||||
const apiUrl = this.data.orderType === 'peihu'
|
||||
@@ -167,6 +254,9 @@ Page(createPage({
|
||||
leixing_id: this.data.xuanzhongLeixingId,
|
||||
keyword: this.data.searchKeyword || undefined
|
||||
};
|
||||
if (statusItem && statusItem.autoSettlePending) {
|
||||
params.auto_settle_pending = 1;
|
||||
}
|
||||
|
||||
const res = await request({
|
||||
url: apiUrl,
|
||||
@@ -177,11 +267,13 @@ Page(createPage({
|
||||
|
||||
const code = res.data.code;
|
||||
if (code === 200 || code === 0) {
|
||||
const newList = res.data.data.list || [];
|
||||
const hasMore = res.data.data.has_more || false;
|
||||
const payload = res.data.data || {};
|
||||
consumeBadgeFields(payload);
|
||||
const newList = payload.list || [];
|
||||
const hasMore = payload.has_more || false;
|
||||
|
||||
// ✅ 图片URL拼接(核心)
|
||||
const processed = newList.map(item => ({
|
||||
const processed = newList.map(item => attachAutoSettleFields({
|
||||
...item,
|
||||
zhuangtaiZh: getOrderStatusText(item.zhuangtai),
|
||||
zhuangtaiColor: statusItem.color,
|
||||
@@ -208,14 +300,20 @@ Page(createPage({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载订单失败', err);
|
||||
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
|
||||
this.setData({
|
||||
[`dsDingdanShuju.${key}.isLoading`]: false,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false
|
||||
});
|
||||
this.refreshCurrentListView();
|
||||
if (!silent) {
|
||||
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
|
||||
}
|
||||
if (!silent) {
|
||||
this.setData({
|
||||
[`dsDingdanShuju.${key}.isLoading`]: false,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false
|
||||
});
|
||||
this.refreshCurrentListView();
|
||||
}
|
||||
} finally {
|
||||
this._listRequesting = false;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -227,6 +325,53 @@ Page(createPage({
|
||||
hasMore: tabData.hasMore,
|
||||
isLoading: tabData.isLoading
|
||||
});
|
||||
this.syncAutoSettleListTicker();
|
||||
},
|
||||
|
||||
syncAutoSettleListTicker() {
|
||||
const key = this.data.currentStatusKey;
|
||||
const has = (this.data.currentList || []).some((it) => it && it.auto_settle_eligible);
|
||||
if (!has) {
|
||||
stopAutoSettleTicker(this);
|
||||
return;
|
||||
}
|
||||
startAutoSettleTicker(this, {
|
||||
onTick: () => {
|
||||
const k = this.data.currentStatusKey;
|
||||
const list = this.data.currentList || [];
|
||||
const now = Date.now();
|
||||
let changed = false;
|
||||
const next = list.map((it) => {
|
||||
if (!it || !it.auto_settle_eligible) return it;
|
||||
const disp = buildAutoSettleDisplay(it, now);
|
||||
if (!disp) {
|
||||
if (it.autoSettleClock || it.autoSettleTip) {
|
||||
changed = true;
|
||||
return { ...it, autoSettleTip: '', autoSettleClock: '', autoSettleRemainText: '' };
|
||||
}
|
||||
return it;
|
||||
}
|
||||
if (it.autoSettleClock !== disp.clockText || it.autoSettleTip !== disp.tip) {
|
||||
changed = true;
|
||||
return {
|
||||
...it,
|
||||
autoSettleTip: disp.tip,
|
||||
autoSettleExpireAt: disp.expireAt,
|
||||
autoSettleRemainText: disp.remainText,
|
||||
autoSettleClock: disp.clockText,
|
||||
autoSettleRemainSeconds: disp.remainSeconds,
|
||||
};
|
||||
}
|
||||
return it;
|
||||
});
|
||||
if (changed) {
|
||||
this.setData({
|
||||
currentList: next,
|
||||
[`dsDingdanShuju.${k}.list`]: next,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
resetCurrentStatusData() {
|
||||
@@ -285,5 +430,13 @@ Page(createPage({
|
||||
}
|
||||
},
|
||||
|
||||
onImageError() {}
|
||||
onImageError() {},
|
||||
|
||||
onHide() {
|
||||
stopAutoSettleTicker(this);
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
stopAutoSettleTicker(this);
|
||||
},
|
||||
}))
|
||||
@@ -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,
|
||||
|
||||
@@ -17,19 +17,21 @@
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选行 -->
|
||||
<!-- 筛选行:组队已独立 Tab;旧「陪护订单」半成品入口已隐藏 -->
|
||||
<view class="filter-row">
|
||||
<view class="type-switch">
|
||||
<view class="type-btn {{orderType == 'normal' ? 'type-active' : ''}}" data-type="normal" bindtap="switchOrderType">普通订单</view>
|
||||
<view class="type-btn {{orderType == 'peihu' ? 'type-active' : ''}}" data-type="peihu" bindtap="switchOrderType">陪护订单</view>
|
||||
</view>
|
||||
<view class="search-box">
|
||||
<view class="search-box search-box-full">
|
||||
<image class="search-icon" src="/images/icon_search.svg" mode="aspectFit"/>
|
||||
<input class="search-input" placeholder="搜索订单ID/介绍" value="{{searchKeyword}}" bindinput="onSearchInput" bindconfirm="onSearchConfirm" confirm-type="search"/>
|
||||
<view class="search-clear" wx:if="{{searchKeyword}}" bindtap="clearSearch"><text>✕</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 结算率 / 罚款率(有样本才展示) -->
|
||||
<view class="deal-stat-bar" wx:if="{{dealStat}}">
|
||||
<text class="deal-stat-item" wx:if="{{dealStat.deal_rate_text}}">结算率 {{dealStat.deal_rate_text}}</text>
|
||||
<text class="deal-stat-item" wx:if="{{dealStat.fined_rate_text}}">被罚款率 {{dealStat.fined_rate_text}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 主体:左右布局 -->
|
||||
<view class="main-container">
|
||||
<!-- 左侧状态栏(固定) -->
|
||||
@@ -77,8 +79,15 @@
|
||||
<view class="order-card" data-item="{{item}}" bindtap="goToDsDingdanXiangqing">
|
||||
<image class="card-img" src="{{item.tupian}}" mode="aspectFill" binderror="onImageError"/>
|
||||
<view class="card-info">
|
||||
<view class="card-title">{{item.jieshao || '暂无描述'}}</view>
|
||||
<view class="card-title-row">
|
||||
<text class="card-title">{{item.jieshao || '暂无描述'}}</text>
|
||||
<text class="team-badge" wx:if="{{item.has_team}}">组队</text>
|
||||
</view>
|
||||
<view class="card-id" wx:if="{{item.dingdan_id}}">ID: {{item.dingdan_id}}</view>
|
||||
<view class="card-team-meta" wx:if="{{item.has_team}}">
|
||||
{{item.team_role_text || '组队'}} · {{item.team_status_text}} · {{item.team_joined}}/{{item.team_need}}人
|
||||
</view>
|
||||
<view class="card-auto-settle" wx:if="{{item.autoSettleTip}}">{{item.autoSettleTip}}</view>
|
||||
</view>
|
||||
<view class="card-right">
|
||||
<view class="card-status" style="color: {{item.zhuangtaiColor}};">{{item.zhuangtaiZh}}</view>
|
||||
|
||||
@@ -9,6 +9,30 @@ page {
|
||||
flex-direction: column;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
.deal-stat-bar {
|
||||
margin: 12rpx 24rpx;
|
||||
padding: 14rpx 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 14rpx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.deal-stat-item {
|
||||
font-size: 22rpx;
|
||||
color: #37474f;
|
||||
background: #eceff1;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.card-auto-settle {
|
||||
margin-top: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #c62828;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 商品类型区 */
|
||||
.leixing-area {
|
||||
@@ -92,6 +116,10 @@ page {
|
||||
padding: 0 20rpx;
|
||||
height: 68rpx;
|
||||
}
|
||||
.search-box-full {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
.search-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
@@ -224,6 +252,28 @@ page {
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.card-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10rpx;
|
||||
}
|
||||
.team-badge {
|
||||
flex-shrink: 0;
|
||||
margin-top: 4rpx;
|
||||
font-size: 20rpx;
|
||||
color: #1f8a7c;
|
||||
background: rgba(31, 138, 124, 0.12);
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 6rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.card-team-meta {
|
||||
font-size: 22rpx;
|
||||
color: #1f8a7c;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
.card-id {
|
||||
font-size: 22rpx;
|
||||
@@ -346,4 +396,5 @@ page {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@import '../../styles/dashou-xym-orders-theme.wxss';
|
||||
@import '../../styles/dashou-xym-orders-theme.wxss';
|
||||
@import '../../styles/dashou-xym-orders-page.wxss';
|
||||
@@ -0,0 +1,16 @@
|
||||
# 排行榜页 UI 改造备份
|
||||
|
||||
备份时间:2026-07-06
|
||||
|
||||
## 备份文件
|
||||
|
||||
- `fighter-rank.wxml` / `fighter-rank.wxss` / `fighter-rank.js` / `fighter-rank.json`
|
||||
|
||||
## 改造说明
|
||||
|
||||
- 视觉对齐参考图:黄色顶栏、白底 Tab、领奖台、4 名以后列表
|
||||
- **接口与 JS 业务逻辑不变**(仅 DATE_LIST 展示顺序、Tab 文案微调)
|
||||
|
||||
## 回滚
|
||||
|
||||
将本目录内四个文件覆盖回 `pages/fighter-rank/` 即可。
|
||||
@@ -0,0 +1,477 @@
|
||||
/**
|
||||
* 排行榜
|
||||
* 默认 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 ROLE_LIST = [
|
||||
{ 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: '上月' },
|
||||
]
|
||||
|
||||
const ROLE_META = {
|
||||
dashou: {
|
||||
title: '接单员排行榜',
|
||||
sortField: 'chengjiao_zonge',
|
||||
sortLabel: '分红总额',
|
||||
mainType: 'money',
|
||||
metrics: [
|
||||
{ field: 'jiedan_zongliang', label: '接单量', type: 'int' },
|
||||
{ field: 'jiedan_zonge', label: '接单额', type: 'money' },
|
||||
{ field: 'chengjiao_zongliang', label: '成交量', type: 'int' },
|
||||
],
|
||||
},
|
||||
guanshi: {
|
||||
title: '管事排行榜',
|
||||
sortField: 'shouru_zonge',
|
||||
sortLabel: '收入总额',
|
||||
mainType: 'money',
|
||||
metrics: [
|
||||
{ field: 'yaoqing_dashou_shu', label: '邀请', type: 'int' },
|
||||
{ field: 'chongzhi_dashou_shu', label: '充值', type: 'int' },
|
||||
],
|
||||
},
|
||||
zuzhang: {
|
||||
title: '组长排行榜',
|
||||
sortField: 'shouru_zonge',
|
||||
sortLabel: '收入总额',
|
||||
mainType: 'money',
|
||||
metrics: [
|
||||
{ field: 'yaoqing_guanshi_shu', label: '邀请', type: 'int' },
|
||||
{ field: 'fenyong_jine', label: '分佣', type: 'money' },
|
||||
],
|
||||
},
|
||||
shangjia: {
|
||||
title: '商家排行榜',
|
||||
sortField: 'jiesuan_jine',
|
||||
sortLabel: '成交总额',
|
||||
mainType: 'money',
|
||||
metrics: [
|
||||
{ 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: '接单员排行榜',
|
||||
sortField: 'chengjiao_zonge',
|
||||
sortLabel: '成交金额',
|
||||
mainType: 'money',
|
||||
metrics: [
|
||||
{ field: 'jiedan_zongliang', label: '接单量', type: 'int' },
|
||||
{ field: 'jiedan_zonge', label: '接单额', type: 'money' },
|
||||
{ field: 'chengjiao_zongliang', label: '成交量', type: 'int' },
|
||||
],
|
||||
},
|
||||
guanshi: {
|
||||
title: '管事排行榜',
|
||||
sortField: 'yaoqing_dashou_shu',
|
||||
sortLabel: '邀请人数',
|
||||
mainType: 'int',
|
||||
metrics: [
|
||||
{ field: 'wuxiao_yaoqing_dashou_shu', label: '无效邀请', type: 'int' },
|
||||
{ field: 'chongzhi_dashou_shu', label: '有效人数', type: 'int' },
|
||||
{ field: 'shouru_zonge', label: '收入金额', type: 'money' },
|
||||
],
|
||||
},
|
||||
zuzhang: {
|
||||
title: '组长排行榜',
|
||||
sortField: 'yaoqing_guanshi_shu',
|
||||
sortLabel: '邀请人数',
|
||||
mainType: 'int',
|
||||
metrics: [
|
||||
{ field: 'wuxiao_yaoqing_guanshi_shu', label: '无效邀请', type: 'int' },
|
||||
{ field: 'youxiao_guanshi_shu', label: '有效人数', type: 'int' },
|
||||
{ field: 'shouru_zonge', label: '收入金额', type: 'money' },
|
||||
],
|
||||
},
|
||||
shangjia: {
|
||||
title: '商家排行榜',
|
||||
sortField: 'paifa_jine',
|
||||
sortLabel: '派单流水',
|
||||
mainType: 'money',
|
||||
metrics: [
|
||||
{ field: 'paifa_dingdan_shu', label: '派单量', type: 'int' },
|
||||
{ field: 'paifa_jine', label: '派单流水', type: 'money' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const ACTIVE_ROLE_META = IS_XZJ ? XZJ_ROLE_META : ROLE_META
|
||||
|
||||
function isInvalidAvatarPath(path) {
|
||||
if (path === null || path === undefined) return true
|
||||
if (typeof path !== 'string') return true
|
||||
const s = path.trim()
|
||||
return !s || s === 'null' || s === 'undefined' || s === 'none'
|
||||
}
|
||||
|
||||
function getDefaultAvatar(app) {
|
||||
return getDefaultAvatarUrl(app)
|
||||
}
|
||||
|
||||
function buildAvatar(path, app) {
|
||||
app = app || getApp()
|
||||
if (isInvalidAvatarPath(path)) return getDefaultAvatar(app)
|
||||
return resolveAvatarUrl(path, app) || getDefaultAvatar(app)
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
imgUrls: {},
|
||||
roleList: ROLE_LIST,
|
||||
dateList: DATE_LIST,
|
||||
currentRole: 'dashou',
|
||||
currentDate: '今日',
|
||||
fanwei: 'jituan',
|
||||
myClubId: CLUB_ID || 'xq',
|
||||
roleMeta: ROLE_META.dashou,
|
||||
sortLabel: '分红总额',
|
||||
isLoading: true,
|
||||
rankList: [],
|
||||
topThree: [],
|
||||
restList: [],
|
||||
isEmpty: false,
|
||||
defaultAvatar: '',
|
||||
rewardInfo: null,
|
||||
showClaimBar: false,
|
||||
claimLoading: false,
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const type = options.type || options.rankType || 'dashou'
|
||||
const validRole = ACTIVE_ROLE_META[type] ? type : 'dashou'
|
||||
this.initPage()
|
||||
this.applyRole(validRole, false)
|
||||
this.fetchRankList()
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.fetchRankList().finally(() => wx.stopPullDownRefresh())
|
||||
},
|
||||
|
||||
initPage() {
|
||||
this.syncDefaultAvatar()
|
||||
const g = getApp().globalData
|
||||
const base = (g.ossImageUrl || '') + 'beijing/paihangbang/'
|
||||
this.setData({
|
||||
imgUrls: {
|
||||
pageBg: base + 'page-bg.jpg',
|
||||
cardBg: base + 'list-card-bg.png',
|
||||
emptyIcon: base + 'empty.png',
|
||||
refreshIcon: base + 'icon-refresh.png',
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
/** 与 app.js 一致:ossImageUrl + morentouxiang,配置异步加载后 onShow 再同步 */
|
||||
syncDefaultAvatar() {
|
||||
const def = getDefaultAvatar()
|
||||
if (def && def !== this.data.defaultAvatar) {
|
||||
this.setData({ defaultAvatar: def })
|
||||
} else if (!this.data.defaultAvatar && def) {
|
||||
this.setData({ defaultAvatar: def })
|
||||
}
|
||||
},
|
||||
|
||||
/** 配置晚于页面加载时,补全空头像 */
|
||||
ensureAvatars() {
|
||||
const def = getDefaultAvatar()
|
||||
if (!def) return
|
||||
const g = getApp().globalData
|
||||
const oss = g.ossImageUrl || ''
|
||||
const { rankList, topThree, restList } = this.data
|
||||
if (!rankList.length) {
|
||||
if (def !== this.data.defaultAvatar) this.setData({ defaultAvatar: def })
|
||||
return
|
||||
}
|
||||
const isValidUrl = (url) => {
|
||||
if (!url || typeof url !== 'string') return false
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) return true
|
||||
return !!(oss && url.indexOf(oss) === 0)
|
||||
}
|
||||
const fix = (item) => ({
|
||||
...item,
|
||||
avatar: isValidUrl(item.avatar) ? item.avatar : def,
|
||||
})
|
||||
this.setData({
|
||||
defaultAvatar: def,
|
||||
rankList: rankList.map(fix),
|
||||
topThree: topThree.map(fix),
|
||||
restList: restList.map(fix),
|
||||
})
|
||||
},
|
||||
|
||||
applyRole(role, reload = true) {
|
||||
const meta = ACTIVE_ROLE_META[role]
|
||||
wx.setNavigationBarTitle({ title: meta.title })
|
||||
this.setData({ currentRole: role, roleMeta: meta, sortLabel: meta.sortLabel })
|
||||
if (reload) this.fetchRankList()
|
||||
},
|
||||
|
||||
onRoleTap(e) {
|
||||
const role = e.currentTarget.dataset.role
|
||||
if (!role || role === this.data.currentRole) return
|
||||
this.applyRole(role)
|
||||
},
|
||||
|
||||
onDateTap(e) {
|
||||
const date = e.currentTarget.dataset.date
|
||||
if (!date || date === this.data.currentDate) return
|
||||
this.setData({ currentDate: date })
|
||||
this.fetchRankList()
|
||||
},
|
||||
|
||||
onScopeTap(e) {
|
||||
const fanwei = e.currentTarget.dataset.fanwei
|
||||
if (!fanwei || fanwei === this.data.fanwei) return
|
||||
this.setData({ fanwei })
|
||||
this.fetchRankList()
|
||||
},
|
||||
|
||||
onShowRules() {
|
||||
const r = this.data.rewardInfo
|
||||
if (!r || !r.tiers || !r.tiers.length) {
|
||||
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 tip = r.period_status === 'open' ? '(本期进行中,结束后可领取)' : ''
|
||||
wx.showModal({
|
||||
title: `${r.title || '排行榜奖励'}${tip}`,
|
||||
content: [
|
||||
`俱乐部:${r.club_id || this.data.myClubId}`,
|
||||
`排序:${r.sort_label || ''}`,
|
||||
...lines,
|
||||
r.description || '',
|
||||
].filter(Boolean).join('\n'),
|
||||
showCancel: false,
|
||||
})
|
||||
},
|
||||
|
||||
async onClaimTap() {
|
||||
const claim = this.data.rewardInfo && this.data.rewardInfo.my_claim
|
||||
if (!claim || !claim.claim_id || this.data.claimLoading) return
|
||||
this.setData({ claimLoading: true })
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/yonghu/phbjllq',
|
||||
method: 'POST',
|
||||
data: { claim_id: claim.claim_id },
|
||||
})
|
||||
const body = res?.data || {}
|
||||
if (body.code === 200) {
|
||||
wx.showToast({ title: `已领取 ¥${claim.amount}`, icon: 'success' })
|
||||
await this.fetchRewardInfo()
|
||||
} else {
|
||||
wx.showToast({ title: body.msg || '领取失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.showToast({ title: '领取失败', icon: 'none' })
|
||||
} finally {
|
||||
this.setData({ claimLoading: false })
|
||||
}
|
||||
},
|
||||
|
||||
onRefreshTap() {
|
||||
this.fetchRankList()
|
||||
},
|
||||
|
||||
formatMoney(val) {
|
||||
const n = parseFloat(val)
|
||||
return Number.isNaN(n) ? '0.00' : n.toFixed(2)
|
||||
},
|
||||
|
||||
formatInt(val) {
|
||||
const n = parseInt(val, 10)
|
||||
return Number.isNaN(n) ? '0' : String(n)
|
||||
},
|
||||
|
||||
normalizeItem(raw, index, role) {
|
||||
const meta = ACTIVE_ROLE_META[role]
|
||||
const app = getApp()
|
||||
const uid = raw.yonghuid || raw.uid || ''
|
||||
const nicheng = raw.nicheng || raw.nick || '用户'
|
||||
const def = getDefaultAvatar(app)
|
||||
const avatar = buildAvatar(raw.touxiang || raw.avatar, app) || def
|
||||
const metrics = meta.metrics.map((m) => {
|
||||
const v = raw[m.field]
|
||||
return {
|
||||
label: m.label,
|
||||
value: m.type === 'money' ? this.formatMoney(v) : this.formatInt(v),
|
||||
}
|
||||
})
|
||||
const sortRaw = raw[meta.sortField] ?? raw.metric_value
|
||||
const mainType = raw.metric_label ? (Number.isInteger(Number(sortRaw)) && !String(sortRaw).includes('.') ? 'int' : 'money') : (meta.mainType || 'money')
|
||||
const mainValue = mainType === 'money'
|
||||
? this.formatMoney(sortRaw)
|
||||
: this.formatInt(sortRaw)
|
||||
return {
|
||||
mingci: raw.mingci || index + 1,
|
||||
uid,
|
||||
nicheng,
|
||||
avatar,
|
||||
clubName: raw.club_name || raw.club_id || '',
|
||||
rewardAmount: raw.reward_amount || 0,
|
||||
mainType,
|
||||
mainPrefix: mainType === 'money' ? '¥' : '',
|
||||
mainValue,
|
||||
mainLabel: raw.metric_label || meta.sortLabel,
|
||||
metrics,
|
||||
}
|
||||
},
|
||||
|
||||
applyRewardToList(list, rewardInfo, fanwei) {
|
||||
if (fanwei !== 'club') return list
|
||||
if (!rewardInfo || !rewardInfo.rank_rewards) return list
|
||||
const map = rewardInfo.rank_rewards
|
||||
return list.map((item) => ({
|
||||
...item,
|
||||
rewardAmount: map[item.mingci] || item.rewardAmount || 0,
|
||||
}))
|
||||
},
|
||||
|
||||
async fetchRewardInfo() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/yonghu/phbjlxx',
|
||||
method: 'POST',
|
||||
data: {
|
||||
shenfen: this.data.currentRole,
|
||||
riqi: this.data.currentDate,
|
||||
club_id: this.data.myClubId,
|
||||
},
|
||||
})
|
||||
const body = res?.data || {}
|
||||
if (body.code !== 200 || !body.data) return
|
||||
const rewardInfo = body.data
|
||||
const showClaimBar = !!(rewardInfo.my_claim && rewardInfo.my_claim.status === 'pending')
|
||||
const { rankList, fanwei } = this.data
|
||||
const merged = this.applyRewardToList(rankList, rewardInfo, fanwei)
|
||||
this.setData({
|
||||
rewardInfo,
|
||||
showClaimBar,
|
||||
rankList: merged,
|
||||
topThree: merged.slice(0, 3),
|
||||
restList: merged.slice(3),
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('奖励信息加载失败', e)
|
||||
}
|
||||
},
|
||||
|
||||
async fetchRankList() {
|
||||
this.setData({ isLoading: true, isEmpty: false, showClaimBar: false })
|
||||
try {
|
||||
const { currentRole, currentDate, fanwei, myClubId } = this.data
|
||||
let list = []
|
||||
let rewardFromRank = null
|
||||
|
||||
if (fanwei === 'club') {
|
||||
const res = await request({
|
||||
url: '/yonghu/phbjlphb',
|
||||
method: 'POST',
|
||||
data: {
|
||||
shenfen: currentRole,
|
||||
riqi: currentDate,
|
||||
fanwei: 'club',
|
||||
club_id: myClubId,
|
||||
},
|
||||
})
|
||||
const body = res?.data || {}
|
||||
if (body.code === 200 && body.data) {
|
||||
list = body.data.list || []
|
||||
rewardFromRank = body.data.reward || null
|
||||
if (body.data.sort_label) {
|
||||
this.setData({ sortLabel: body.data.sort_label })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const res = await request({
|
||||
url: RANK_API,
|
||||
method: 'POST',
|
||||
data: { shenfen: currentRole, riqi: currentDate },
|
||||
})
|
||||
const body = res?.data || {}
|
||||
const ok = body.code === 200 || body.code === 0
|
||||
list = ok && body.data
|
||||
? (body.data.list || body.data.paihang_list || body.data.rank_list || [])
|
||||
: []
|
||||
}
|
||||
|
||||
if (!Array.isArray(list)) list = []
|
||||
const role = currentRole
|
||||
let normalized = list.slice(0, 50).map((item, i) => this.normalizeItem(item, i, role))
|
||||
if (rewardFromRank) {
|
||||
normalized = this.applyRewardToList(normalized, rewardFromRank, 'club')
|
||||
this.setData({ rewardInfo: rewardFromRank, showClaimBar: !!(rewardFromRank.my_claim && rewardFromRank.my_claim.status === 'pending') })
|
||||
}
|
||||
this.setData({
|
||||
rankList: normalized,
|
||||
topThree: normalized.slice(0, 3),
|
||||
restList: normalized.slice(3),
|
||||
isEmpty: normalized.length === 0,
|
||||
isLoading: false,
|
||||
})
|
||||
this.ensureAvatars()
|
||||
if (!rewardFromRank) {
|
||||
await this.fetchRewardInfo()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('排行榜加载失败', e)
|
||||
wx.showToast({ title: '加载失败', icon: 'none' })
|
||||
this.setData({ rankList: [], topThree: [], restList: [], isEmpty: true, isLoading: false })
|
||||
}
|
||||
},
|
||||
|
||||
onAvatarError(e) {
|
||||
const { zone, index } = e.currentTarget.dataset
|
||||
const fallback = getDefaultAvatar()
|
||||
if (!fallback) return
|
||||
const key = zone === 'top' ? `topThree[${index}].avatar` : `restList[${index}].avatar`
|
||||
this.setData({ [key]: fallback })
|
||||
},
|
||||
|
||||
registerNotificationComponent() {
|
||||
const app = getApp()
|
||||
const comp = this.selectComponent('#global-notification')
|
||||
if (comp?.showNotification) {
|
||||
app.globalData.globalNotification = {
|
||||
show: (d) => comp.showNotification(d),
|
||||
hide: () => comp.hideNotification(),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.syncDefaultAvatar()
|
||||
this.ensureAvatars()
|
||||
this.registerNotificationComponent()
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification"
|
||||
},
|
||||
"navigationBarTitleText": "排行榜",
|
||||
"navigationBarBackgroundColor": "#0d1117",
|
||||
"navigationBarTextStyle": "white",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#0d1117",
|
||||
"backgroundTextStyle": "light"
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<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">
|
||||
<view
|
||||
wx:for="{{roleList}}" wx:key="key"
|
||||
class="role-chip {{currentRole === item.key ? 'on' : ''}}"
|
||||
data-role="{{item.key}}" bindtap="onRoleTap"
|
||||
>{{item.label}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="date-grid">
|
||||
<view
|
||||
wx:for="{{dateList}}" wx:key="key"
|
||||
class="date-chip {{currentDate === item.key ? 'on' : ''}}"
|
||||
data-date="{{item.key}}" bindtap="onDateTap"
|
||||
>{{item.label}}</view>
|
||||
</view>
|
||||
<text class="sort-tip">{{currentDate}} · 按{{sortLabel}} · TOP50</text>
|
||||
|
||||
<view class="scope-row">
|
||||
<view class="scope-chip {{fanwei === 'jituan' ? 'on' : ''}}" data-fanwei="jituan" bindtap="onScopeTap">集团总榜</view>
|
||||
<view class="scope-chip {{fanwei === 'club' ? 'on' : ''}}" data-fanwei="club" bindtap="onScopeTap">本俱乐部榜</view>
|
||||
</view>
|
||||
|
||||
<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}}">
|
||||
<view class="loader"></view>
|
||||
<text>加载中</text>
|
||||
</view>
|
||||
|
||||
<view class="state-wrap" wx:elif="{{isEmpty}}">
|
||||
<image class="state-img" src="{{imgUrls.emptyIcon}}" mode="aspectFit" />
|
||||
<text>暂无排行数据</text>
|
||||
</view>
|
||||
|
||||
<scroll-view wx:else scroll-y class="body-scroll" enhanced show-scrollbar="{{false}}">
|
||||
|
||||
<!-- 前三名 · 纯 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">
|
||||
<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].mainPrefix}}{{topThree[1].mainValue}}</text>
|
||||
<text class="p-label">{{topThree[1].mainLabel}}</text>
|
||||
<text class="p-reward" wx:if="{{topThree[1].rewardAmount > 0}}">奖 ¥{{topThree[1].rewardAmount}}</text>
|
||||
</view>
|
||||
<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>
|
||||
</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">
|
||||
<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].mainPrefix}}{{topThree[0].mainValue}}</text>
|
||||
<text class="p-label">{{topThree[0].mainLabel}}</text>
|
||||
<text class="p-reward" wx:if="{{topThree[0].rewardAmount > 0}}">奖 ¥{{topThree[0].rewardAmount}}</text>
|
||||
</view>
|
||||
<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>
|
||||
</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">
|
||||
<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].mainPrefix}}{{topThree[2].mainValue}}</text>
|
||||
<text class="p-label">{{topThree[2].mainLabel}}</text>
|
||||
<text class="p-reward" wx:if="{{topThree[2].rewardAmount > 0}}">奖 ¥{{topThree[2].rewardAmount}}</text>
|
||||
</view>
|
||||
<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>
|
||||
</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 wx:if="{{item.clubName && fanwei === 'jituan'}}"> · {{item.clubName}}</text></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.mainPrefix}}{{item.mainValue}}</text>
|
||||
<text class="lc-label">{{item.mainLabel}}</text>
|
||||
<text class="lc-reward" wx:if="{{item.rewardAmount > 0}}">奖¥{{item.rewardAmount}}</text>
|
||||
</view>
|
||||
</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" />
|
||||
@@ -0,0 +1,559 @@
|
||||
page {
|
||||
background: #0d1117;
|
||||
color: #fff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif;
|
||||
}
|
||||
|
||||
/* 主题色 */
|
||||
/* 选中/金额: #6BA3F7 浅蓝: #8EB8FF 银: #A8B4C4 铜: #C4A882 */
|
||||
|
||||
.rank-page {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-bg {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* ========== 顶栏 ========== */
|
||||
.top-bar {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
padding: 16rpx 28rpx 20rpx;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.refresh-wrap {
|
||||
width: 60rpx; height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.14);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.refresh-ico {
|
||||
width: 32rpx; height: 32rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.role-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.role-line {
|
||||
display: inline-flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255,255,255,0.45);
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.scope-row {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.scope-chip {
|
||||
padding: 10rpx 24rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 24rpx;
|
||||
color: rgba(255,255,255,0.65);
|
||||
background: rgba(20, 26, 40, 0.75);
|
||||
border: 1rpx solid rgba(255,255,255,0.12);
|
||||
}
|
||||
|
||||
.scope-chip.on {
|
||||
color: #fff;
|
||||
background: rgba(255, 193, 7, 0.35);
|
||||
border-color: rgba(255, 193, 7, 0.5);
|
||||
}
|
||||
|
||||
.reward-banner {
|
||||
margin-top: 16rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: linear-gradient(135deg, rgba(255,193,7,0.2), rgba(107,163,247,0.15));
|
||||
border: 1rpx solid rgba(255,193,7,0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.reward-banner-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.reward-tag { font-size: 28rpx; font-weight: 600; color: #ffd666; }
|
||||
.reward-club { font-size: 22rpx; color: rgba(255,255,255,0.7); }
|
||||
.reward-status { font-size: 22rpx; color: rgba(255,255,255,0.55); }
|
||||
.reward-status.pending { color: #52c41a; font-weight: 600; }
|
||||
.reward-status.claimed { color: #8c8c8c; }
|
||||
.reward-link { font-size: 24rpx; color: #8EB8FF; }
|
||||
|
||||
.p-reward {
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ffd666;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lc-reward {
|
||||
display: block;
|
||||
margin-top: 4rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ffd666;
|
||||
}
|
||||
|
||||
.claim-bar {
|
||||
position: fixed;
|
||||
left: 0; right: 0; bottom: 0;
|
||||
z-index: 100;
|
||||
padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: linear-gradient(180deg, rgba(13,17,23,0.2), rgba(13,17,23,0.95));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-top: 1rpx solid rgba(255,193,7,0.3);
|
||||
}
|
||||
|
||||
.claim-text { display: flex; flex-direction: column; }
|
||||
.claim-title { font-size: 26rpx; color: #fff; }
|
||||
.claim-amount { font-size: 36rpx; font-weight: 700; color: #ffd666; }
|
||||
|
||||
.claim-btn {
|
||||
margin: 0;
|
||||
padding: 0 40rpx;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
font-size: 28rpx;
|
||||
background: linear-gradient(135deg, #ffc107, #ff9800);
|
||||
color: #1a1a1a;
|
||||
border-radius: 999rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.bottom-gap {
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
/* ========== 状态 ========== */
|
||||
.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);
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 48rpx; height: 48rpx;
|
||||
border: 3rpx solid rgba(255,255,255,0.12);
|
||||
border-top-color: #6BA3F7;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.7s linear infinite;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.state-img {
|
||||
width: 160rpx; height: 160rpx;
|
||||
margin-bottom: 20rpx;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
/* ========== 滚动 ========== */
|
||||
.body-scroll {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
height: calc(100vh - 300rpx);
|
||||
}
|
||||
|
||||
/* ========== 领奖台(纯 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-cols {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.p-col {
|
||||
flex: 1;
|
||||
max-width: 220rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.p-first {
|
||||
max-width: 250rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.badge-first {
|
||||
color: #fff;
|
||||
background: linear-gradient(145deg, #6BA3F7, #4A7FD4);
|
||||
box-shadow: 0 4rpx 16rpx rgba(107, 163, 247, 0.4);
|
||||
}
|
||||
|
||||
.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 {
|
||||
border-radius: 50%;
|
||||
padding: 4rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.wrap-first {
|
||||
padding: 5rpx;
|
||||
background: linear-gradient(145deg, #8EB8FF, #5B8FD8);
|
||||
box-shadow: 0 6rpx 24rpx rgba(107, 163, 247, 0.3);
|
||||
}
|
||||
|
||||
.wrap-silver {
|
||||
background: linear-gradient(145deg, #C8D0DA, #909AA8);
|
||||
}
|
||||
|
||||
.wrap-bronze {
|
||||
background: linear-gradient(145deg, #D4B896, #A88860);
|
||||
}
|
||||
|
||||
.p-avatar {
|
||||
width: 100rpx; height: 100rpx;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
background: #1a2030;
|
||||
}
|
||||
|
||||
.p-avatar-lg {
|
||||
width: 120rpx; height: 120rpx;
|
||||
}
|
||||
|
||||
.p-name {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(255,255,255,0.92);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.p-name-first {
|
||||
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;
|
||||
margin-top: 2rpx;
|
||||
font-size: 20rpx;
|
||||
color: rgba(255,255,255,0.35);
|
||||
}
|
||||
|
||||
.p-metrics {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6rpx;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
min-height: 36rpx;
|
||||
}
|
||||
|
||||
.p-metric {
|
||||
font-size: 18rpx;
|
||||
color: rgba(255,255,255,0.55);
|
||||
background: rgba(255,255,255,0.1);
|
||||
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 {
|
||||
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;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255,255,255,0.32);
|
||||
}
|
||||
|
||||
.lc-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.lc-tag {
|
||||
font-size: 20rpx;
|
||||
color: rgba(255,255,255,0.5);
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.lc-score {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.lc-money {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #8EB8FF;
|
||||
}
|
||||
|
||||
.lc-label {
|
||||
display: block;
|
||||
margin-top: 2rpx;
|
||||
font-size: 20rpx;
|
||||
color: rgba(255,255,255,0.32);
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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" />
|
||||
<!-- 顶区:黄底 + 白 Tab + 时间筛选 -->
|
||||
<view class="header-zone">
|
||||
<view class="role-tabs">
|
||||
<view
|
||||
wx:for="{{roleList}}" wx:key="key"
|
||||
class="role-tab {{currentRole === item.key ? 'active' : ''}}"
|
||||
data-role="{{item.key}}" bindtap="onRoleTap"
|
||||
>
|
||||
<text class="role-tab-text">{{item.label}}</text>
|
||||
<view class="role-tab-line" wx:if="{{currentRole === item.key}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-x class="role-scroll" enhanced show-scrollbar="{{false}}">
|
||||
<view class="role-line">
|
||||
|
||||
<scroll-view scroll-x class="date-scroll" enhanced show-scrollbar="{{false}}">
|
||||
<view class="date-line">
|
||||
<view
|
||||
wx:for="{{roleList}}" wx:key="key"
|
||||
class="role-chip {{currentRole === item.key ? 'on' : ''}}"
|
||||
data-role="{{item.key}}" bindtap="onRoleTap"
|
||||
wx:for="{{dateList}}" wx:key="key"
|
||||
class="date-tab {{currentDate === item.key ? 'active' : ''}}"
|
||||
data-date="{{item.key}}" bindtap="onDateTap"
|
||||
>{{item.label}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="date-grid">
|
||||
<view
|
||||
wx:for="{{dateList}}" wx:key="key"
|
||||
class="date-chip {{currentDate === item.key ? 'on' : ''}}"
|
||||
data-date="{{item.key}}" bindtap="onDateTap"
|
||||
>{{item.label}}</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-label">{{topThree[1].mainLabel}}</text>
|
||||
<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-label">{{topThree[0].mainLabel}}</text>
|
||||
</view>
|
||||
<text class="p-name p-name-lg">{{topThree[0].nicheng}}</text>
|
||||
<text class="p-label">{{topThree[0].mainLabel}}</text>
|
||||
<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-label">{{topThree[2].mainLabel}}</text>
|
||||
<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" />
|
||||
@@ -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; }
|
||||
@@ -0,0 +1,497 @@
|
||||
// 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,
|
||||
rulesTitle: '履约金规则',
|
||||
benefitsTitle: '平台履约金权益',
|
||||
pageCopy: null,
|
||||
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();
|
||||
this.fetchPageCopy();
|
||||
},
|
||||
|
||||
onHide() {
|
||||
this.selectComponent('#popupNotice')?.cleanup?.();
|
||||
},
|
||||
|
||||
async onShow() {
|
||||
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
|
||||
if (!phoneOk) return;
|
||||
this.registerNotification();
|
||||
this.loadYajin();
|
||||
this.refreshConfiguredIcons();
|
||||
if (!this.data.pageCopy) {
|
||||
this.fetchPageCopy();
|
||||
}
|
||||
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(),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
resolveOssUrl(path) {
|
||||
if (!path) return '';
|
||||
if (path.startsWith('http')) return path;
|
||||
return (app.globalData.ossImageUrl || '') + path;
|
||||
},
|
||||
|
||||
async fetchPageCopy() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/jituan/recharge-page-copy',
|
||||
method: 'POST',
|
||||
data: { page_key: 'yajin' },
|
||||
});
|
||||
if (res.data && res.data.code === 0 && res.data.data) {
|
||||
this.setData({ pageCopy: res.data.data.regions || {} });
|
||||
} else {
|
||||
this.setData({ pageCopy: {} });
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('fetchPageCopy failed', e);
|
||||
this.setData({ pageCopy: {} });
|
||||
}
|
||||
this.applyPageCopy();
|
||||
},
|
||||
|
||||
applyPageCopy() {
|
||||
const copy = this.data.pageCopy || {};
|
||||
const patch = {};
|
||||
|
||||
const rulesRegion = copy.rules;
|
||||
if (rulesRegion && Array.isArray(rulesRegion.items) && rulesRegion.items.length > 0) {
|
||||
patch.depositRules = rulesRegion.items
|
||||
.map((it) => (typeof it === 'string' ? it : (it && it.text) || ''))
|
||||
.filter(Boolean);
|
||||
if (rulesRegion.title) patch.rulesTitle = rulesRegion.title;
|
||||
} else {
|
||||
patch.depositRules = DEFAULT_RULES;
|
||||
patch.rulesTitle = '履约金规则';
|
||||
}
|
||||
|
||||
const benefitsRegion = copy.benefits;
|
||||
const fb = (key) => `${ICON_FOLDER}/${key}.png`;
|
||||
if (benefitsRegion && Array.isArray(benefitsRegion.items) && benefitsRegion.items.length > 0) {
|
||||
patch.depositBenefits = benefitsRegion.items.map((item, i) => {
|
||||
const fallback = DEFAULT_BENEFITS[i];
|
||||
const iconUrl = item.icon_url
|
||||
? this.resolveOssUrl(item.icon_url)
|
||||
: (fallback
|
||||
? resolveMiniappIcon(app, fallback.key, fb(fallback.key))
|
||||
: '');
|
||||
return {
|
||||
label: item.label || item.title || '',
|
||||
iconUrl,
|
||||
};
|
||||
});
|
||||
if (benefitsRegion.title) patch.benefitsTitle = benefitsRegion.title;
|
||||
} else {
|
||||
patch.depositBenefits = DEFAULT_BENEFITS.map((item) => ({
|
||||
label: item.label,
|
||||
iconUrl: resolveMiniappIcon(app, item.key, fb(item.key)),
|
||||
}));
|
||||
patch.benefitsTitle = '平台履约金权益';
|
||||
}
|
||||
|
||||
this.setData(patch);
|
||||
},
|
||||
|
||||
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,
|
||||
});
|
||||
if (this.data.pageCopy) {
|
||||
this.applyPageCopy();
|
||||
}
|
||||
},
|
||||
|
||||
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' || method === 'alipay') {
|
||||
const payMethod = method === 'alipay' ? 'alipay' : 'wx';
|
||||
this.setData({ yajinAmount: this.data.currentYajinAmount }, () => this.handleYajinPay(payMethod));
|
||||
} 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' });
|
||||
this.showPaySuccessPopup();
|
||||
} 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(payMethod = 'wx') {
|
||||
const amount = this.data.yajinAmount;
|
||||
if (!amount || amount < 1 || amount > 10000) {
|
||||
wx.showToast({ title: '请输入1-10000元', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
// 支付宝无需微信确认弹窗
|
||||
if (payMethod !== 'alipay' && !(await this.confirmWechatPurchase())) return;
|
||||
this.showLoading('发起支付中...');
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/yajingoumai',
|
||||
method: 'POST',
|
||||
data: {
|
||||
jine: parseInt(amount, 10),
|
||||
pay_method: payMethod === 'alipay' ? 'alipay' : 'wechat',
|
||||
},
|
||||
});
|
||||
if (res.data.code === 200) {
|
||||
const payParams = res.data.payParams;
|
||||
const dingdanid = res.data.dingdanid;
|
||||
this.setData({ currentDingdanid: dingdanid });
|
||||
// 支付宝分支:跳转二维码页
|
||||
if (payMethod === 'alipay' || (payParams && payParams.pay_method === 'alipay')) {
|
||||
this.hideLoading();
|
||||
this.triggerAlipayPayment(payParams, dingdanid);
|
||||
return;
|
||||
}
|
||||
await this.wechatPay(payParams);
|
||||
} else {
|
||||
wx.showToast({ title: res.data.message || '支付发起失败', icon: 'none' });
|
||||
}
|
||||
} catch (err) {
|
||||
wx.showToast({ title: '支付失败', icon: 'none' });
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 支付宝扫码支付封装 ==========
|
||||
triggerAlipayPayment(payParams, orderId) {
|
||||
const qrImage = payParams && payParams.qr_image ? payParams.qr_image : '';
|
||||
if (!qrImage) {
|
||||
wx.showToast({ title: '支付宝二维码生成失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const app = getApp();
|
||||
app.globalData.alipayQrImage = qrImage;
|
||||
const pollUrl = encodeURIComponent('/shangpin/yajinlunxun');
|
||||
const self = this;
|
||||
wx.navigateTo({
|
||||
url: `/pages/alipay-webview/alipay-webview?orderId=${orderId}&pollUrl=${pollUrl}`,
|
||||
events: {
|
||||
// 支付宝支付成功后,更新押金全局变量并刷新页面
|
||||
onAlipaySuccess: (res) => {
|
||||
if (res && res.data && res.data.yajin !== undefined) {
|
||||
app.globalData.yajin = res.data.yajin;
|
||||
}
|
||||
self.loadYajin();
|
||||
wx.showToast({ title: '履约金缴纳成功', icon: 'success', duration: 2000 });
|
||||
self.showPaySuccessPopup();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
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' });
|
||||
this.showPaySuccessPopup();
|
||||
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 });
|
||||
},
|
||||
|
||||
/** 缴纳成功后弹出后台可配置公告(页面标识 yajinchenggong) */
|
||||
showPaySuccessPopup() {
|
||||
try {
|
||||
PopupService.reset();
|
||||
} catch (e) { /* ignore */ }
|
||||
setTimeout(() => {
|
||||
PopupService.checkAndShow(this, 'yajinchenggong');
|
||||
}, 450);
|
||||
},
|
||||
|
||||
stopPropagation() {},
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<!-- 平台履约金 / 保证金充值页 -->
|
||||
<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">{{rulesTitle || '履约金规则'}}</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">{{benefitsTitle || '平台履约金权益'}}</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="alipay">
|
||||
<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" />
|
||||
@@ -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%); }
|
||||
}
|
||||
@@ -1,72 +1,62 @@
|
||||
// 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';
|
||||
import { refreshDashouMembership } from '../../utils/dashou-profile.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: [],
|
||||
|
||||
// ========== 新增:跳转参数处理 ==========
|
||||
// 用于接收从其他页面跳转过来的参数,决定是否自动滚动
|
||||
rulesTitle: '',
|
||||
benefitsTitle: '',
|
||||
advantagesTitle: '平台优势',
|
||||
pageCopy: null,
|
||||
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 +76,7 @@ onHide() {
|
||||
this.registerNotificationComponent();
|
||||
this.loadFromGlobalData();
|
||||
this.checkAndRefreshData();
|
||||
this.refreshConfiguredIcons();
|
||||
|
||||
// 调用统一弹窗组件检查并展示公告
|
||||
PopupService.checkAndShow(this, 'dashouchongzhi');
|
||||
@@ -112,16 +103,210 @@ onHide() {
|
||||
|
||||
// ========== 初始化页面 ==========
|
||||
async initPage() {
|
||||
// 从全局数据加载
|
||||
this.loadFromGlobalData();
|
||||
|
||||
// 获取会员商品列表
|
||||
await this.fetchPageCopy();
|
||||
await this.fetchHuiyuanList();
|
||||
|
||||
// 计算会员列表高度
|
||||
this.calculateHuiyuanHeight();
|
||||
},
|
||||
|
||||
async fetchPageCopy() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/jituan/recharge-page-copy',
|
||||
method: 'POST',
|
||||
data: { page_key: 'huiyuan' },
|
||||
});
|
||||
if (res.data && res.data.code === 0 && res.data.data) {
|
||||
this.setData({ pageCopy: res.data.data.regions || {} });
|
||||
} else {
|
||||
this.setData({ pageCopy: {} });
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('fetchPageCopy failed', e);
|
||||
this.setData({ pageCopy: {} });
|
||||
}
|
||||
},
|
||||
|
||||
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 region = (this.data.pageCopy || {}).advantages;
|
||||
if (region && Array.isArray(region.items) && region.items.length > 0) {
|
||||
return region.items.map((item, i) => {
|
||||
const fallbackKey = [
|
||||
ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_1,
|
||||
ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_2,
|
||||
ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_3,
|
||||
ICON_KEYS.FIGHTER_RECHARGE_ADVANTAGE_4,
|
||||
][i];
|
||||
const iconUrl = item.icon_url
|
||||
? this.resolveOssUrl(item.icon_url)
|
||||
: (fallbackKey ? this._icon(fallbackKey, fb(fallbackKey)) : '');
|
||||
return {
|
||||
title: item.title || '',
|
||||
desc: item.desc || '',
|
||||
iconUrl,
|
||||
};
|
||||
});
|
||||
}
|
||||
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 region = (this.data.pageCopy || {}).benefits;
|
||||
const fb = (name) => `${ICON_FOLDER}/${name}.png`;
|
||||
if (region && Array.isArray(region.items) && region.items.length > 0) {
|
||||
const fallbackKeys = [
|
||||
ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_1,
|
||||
ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_2,
|
||||
ICON_KEYS.FIGHTER_RECHARGE_BENEFIT_3,
|
||||
];
|
||||
return region.items.map((item, i) => {
|
||||
const key = fallbackKeys[i];
|
||||
const iconUrl = item.icon_url
|
||||
? this.resolveOssUrl(item.icon_url)
|
||||
: (key ? this._icon(key, fb(key)) : '');
|
||||
return {
|
||||
title: item.title || '',
|
||||
desc: item.desc || '',
|
||||
iconUrl,
|
||||
accentColor: item.accent_color || '',
|
||||
accentStyle: item.accent_color ? `color:${item.accent_color}` : '',
|
||||
};
|
||||
});
|
||||
}
|
||||
const name = huiyuan.mingzi || '会员';
|
||||
const formalPrice = huiyuan.jiage || '0';
|
||||
const trialPrice = huiyuan.trial_price || '0';
|
||||
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) {
|
||||
const copy = this.data.pageCopy || {};
|
||||
const name = huiyuan.mingzi || '会员';
|
||||
const rulesTitle = (copy.rules && copy.rules.title)
|
||||
|| `${name}规则`;
|
||||
const benefitsTitle = (copy.benefits && copy.benefits.title)
|
||||
|| `${name}专属权益`;
|
||||
const advantagesTitle = (copy.advantages && copy.advantages.title)
|
||||
|| '平台优势';
|
||||
this.setData({
|
||||
currentHuiyuanIndex: index,
|
||||
currentHuiyuan: huiyuan,
|
||||
detailRules: this.buildDetailRules(huiyuan),
|
||||
memberBenefits: this.buildMemberBenefits(huiyuan),
|
||||
platformAdvantages: this.buildPlatformAdvantages(),
|
||||
rulesTitle,
|
||||
benefitsTitle,
|
||||
advantagesTitle,
|
||||
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 +315,12 @@ onHide() {
|
||||
// ✅ 重要:保持字段对应关系
|
||||
// 全局变量中是 clumber,页面中是 clubmber
|
||||
// 全局变量中是 jinfen,页面中是 jifen
|
||||
this.setData({
|
||||
clubmber: globalData.clumber || [],
|
||||
yajin: globalData.yajin || 0,
|
||||
jifen: globalData.jinfen || 0 // ✅ 注意:这里从 jinfen 读取
|
||||
});
|
||||
|
||||
// 计算积分进度条
|
||||
this.calculateJifenProgress();
|
||||
},
|
||||
const rawClubmber = globalData.clumber || [];
|
||||
const clubmber = rawClubmber.filter((c) => c && c.huiyuanid);
|
||||
|
||||
// ========== 计算积分进度条角度 ==========
|
||||
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);
|
||||
|
||||
this.setData({
|
||||
huiyuanListHeight: height
|
||||
clubmber: clubmber,
|
||||
jifen: globalData.jinfen || 0,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -174,17 +334,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 +369,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 +380,34 @@ 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 region = (this.data.pageCopy || {}).rules;
|
||||
if (region && Array.isArray(region.items) && region.items.length > 0) {
|
||||
return region.items
|
||||
.map((it) => (typeof it === 'string' ? it : (it && it.text) || ''))
|
||||
.filter(Boolean)
|
||||
.slice(0, 4);
|
||||
}
|
||||
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 +461,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);
|
||||
},
|
||||
|
||||
// ========== 积分充值相关 ==========
|
||||
@@ -443,7 +495,7 @@ onHide() {
|
||||
},
|
||||
|
||||
// ========== 会员购买相关 ==========
|
||||
async handleHuiyuanBuy(e) {
|
||||
async handleHuiyuanBuy(e, payMethod = 'wx') {
|
||||
const huiyuanId = e.currentTarget.dataset.id;
|
||||
const isTrial = e.currentTarget.dataset.trial === true || e.currentTarget.dataset.trial === 'true';
|
||||
|
||||
@@ -455,18 +507,10 @@ 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;
|
||||
}
|
||||
// 支付宝无需微信确认弹窗
|
||||
if (payMethod !== 'alipay') {
|
||||
const confirmed = await this.confirmWechatPurchase('huiyuan');
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
this.showLoading('发起会员支付中...');
|
||||
@@ -477,18 +521,26 @@ onHide() {
|
||||
method: 'POST',
|
||||
data: {
|
||||
huiyuanid: huiyuanId,
|
||||
is_trial: isTrial
|
||||
is_trial: isTrial,
|
||||
pay_method: payMethod === 'alipay' ? 'alipay' : 'wechat'
|
||||
}
|
||||
});
|
||||
|
||||
if (res.data.code === 200) {
|
||||
const payParams = res.data.payParams;
|
||||
const dingdanid = res.data.dingdanid;
|
||||
|
||||
|
||||
this.setData({
|
||||
currentDingdanid: dingdanid
|
||||
});
|
||||
|
||||
|
||||
// 支付宝分支:跳转二维码页
|
||||
if (payMethod === 'alipay' || (payParams && payParams.pay_method === 'alipay')) {
|
||||
this.hideLoading();
|
||||
this.triggerAlipayPayment(payParams, dingdanid, 'huiyuan');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.wechatPay(payParams, 'huiyuan', huiyuanId);
|
||||
} else {
|
||||
wx.showToast({
|
||||
@@ -508,9 +560,9 @@ onHide() {
|
||||
},
|
||||
|
||||
// ========== 积分购买(微信支付)—— 已替换为正确的后端接口 ==========
|
||||
async handleJifenBuy(e) {
|
||||
async handleJifenBuy(e, payMethod = 'wx') {
|
||||
const disabled = e.currentTarget.dataset.disabled;
|
||||
|
||||
|
||||
// 检查积分是否已满
|
||||
if (disabled === 'true') {
|
||||
wx.showToast({ title: '积分已满,无需补充', icon: 'none' });
|
||||
@@ -524,20 +576,36 @@ onHide() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 支付宝无需微信确认弹窗
|
||||
if (payMethod !== 'alipay') {
|
||||
const confirmed = await this.confirmWechatPurchase('jifen');
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
this.showLoading('发起积分支付中...');
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/jifenbc', // 正确的积分下单接口
|
||||
method: 'POST',
|
||||
data: {}
|
||||
data: {
|
||||
pay_method: payMethod === 'alipay' ? 'alipay' : 'wechat'
|
||||
}
|
||||
});
|
||||
|
||||
if (res.data.code === 200) {
|
||||
const payParams = res.data.payParams;
|
||||
const dingdanid = res.data.dingdanid;
|
||||
|
||||
|
||||
this.setData({ currentDingdanid: dingdanid });
|
||||
|
||||
// 支付宝分支:跳转二维码页
|
||||
if (payMethod === 'alipay' || (payParams && payParams.pay_method === 'alipay')) {
|
||||
this.hideLoading();
|
||||
this.triggerAlipayPayment(payParams, dingdanid, 'jifen');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.wechatPay(payParams, 'jifen');
|
||||
} else {
|
||||
wx.showToast({ title: res.data.message || '积分支付发起失败', icon: 'none' });
|
||||
@@ -549,6 +617,37 @@ onHide() {
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 支付宝扫码支付封装 ==========
|
||||
// 跳转到 alipay-webview 展示二维码,并按 payType 指定轮询接口
|
||||
triggerAlipayPayment(payParams, orderId, payType) {
|
||||
const qrImage = payParams && payParams.qr_image ? payParams.qr_image : '';
|
||||
if (!qrImage) {
|
||||
wx.showToast({ title: '支付宝二维码生成失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const app = getApp();
|
||||
app.globalData.alipayQrImage = qrImage;
|
||||
let pollUrl = '/dingdan/fucha';
|
||||
if (payType === 'huiyuan') pollUrl = '/shangpin/huiyuanlx';
|
||||
else if (payType === 'jifen') pollUrl = '/shangpin/jifenlunxun';
|
||||
const encodedPollUrl = encodeURIComponent(pollUrl);
|
||||
const self = this;
|
||||
wx.navigateTo({
|
||||
url: `/pages/alipay-webview/alipay-webview?orderId=${orderId}&pollUrl=${encodedPollUrl}`,
|
||||
events: {
|
||||
// 支付宝支付成功后,复用微信支付的更新逻辑刷新全局变量与页面
|
||||
onAlipaySuccess: (res) => {
|
||||
if (self.updateAfterPayment) {
|
||||
self.updateAfterPayment(payType, null, res && res.data).then(() => {
|
||||
wx.showToast({ title: self.getSuccessMessage(payType), icon: 'success', duration: 2000 });
|
||||
self.showPaySuccessPopup(payType);
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 微信支付封装 ==========
|
||||
async wechatPay(payParams, payType, extraData = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -641,6 +740,7 @@ onHide() {
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
this.showPaySuccessPopup(payType);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -749,10 +849,11 @@ onHide() {
|
||||
break;
|
||||
|
||||
case 'huiyuan':
|
||||
if (responseData && responseData.huiyuan) {
|
||||
if (responseData && responseData.jifen !== undefined) {
|
||||
app.globalData.jinfen = responseData.jifen;
|
||||
await this.updateClubmberData(responseData.huiyuan);
|
||||
}
|
||||
// 从服务端拉完整 clumber(含总会员子类型展开),避免本地只写总会员 ID
|
||||
await refreshDashouMembership(app);
|
||||
await this.refreshHuiyuanData(extraData);
|
||||
break;
|
||||
}
|
||||
@@ -772,34 +873,29 @@ onHide() {
|
||||
|
||||
// ========== 刷新会员数据 ==========
|
||||
async refreshHuiyuanData(huiyuanId) {
|
||||
await refreshDashouMembership(app);
|
||||
this.loadFromGlobalData();
|
||||
await this.fetchHuiyuanList();
|
||||
},
|
||||
|
||||
// ========== 更新会员数据到全局变量 ==========
|
||||
// ========== 更新会员数据到全局变量(兼容旧回调;优先服务端 clumber) ==========
|
||||
async updateClubmberData(huiyuanData) {
|
||||
const app = getApp();
|
||||
const clumber = await refreshDashouMembership(app);
|
||||
if (clumber && clumber.length) {
|
||||
this.setData({ clubmber: [...clumber] });
|
||||
return;
|
||||
}
|
||||
const globalClubmber = app.globalData.clumber || [];
|
||||
|
||||
const { huiyuanid, huiyuanming, daoqi } = huiyuanData;
|
||||
|
||||
const index = globalClubmber.findIndex(item => item.huiyuanid === huiyuanid);
|
||||
|
||||
const { huiyuanid, huiyuanming, daoqi } = huiyuanData || {};
|
||||
if (!huiyuanid) return;
|
||||
const index = globalClubmber.findIndex((item) => item.huiyuanid === huiyuanid);
|
||||
if (index >= 0) {
|
||||
globalClubmber[index].daoqi = daoqi;
|
||||
} else {
|
||||
globalClubmber.push({
|
||||
huiyuanid: huiyuanid,
|
||||
huiyuanming: huiyuanming,
|
||||
daoqi: daoqi
|
||||
});
|
||||
globalClubmber.push({ huiyuanid, huiyuanming, daoqi });
|
||||
}
|
||||
|
||||
app.globalData.clumber = globalClubmber;
|
||||
|
||||
this.setData({
|
||||
clubmber: [...globalClubmber]
|
||||
});
|
||||
this.setData({ clubmber: [...globalClubmber] });
|
||||
},
|
||||
|
||||
// ========== 检查并刷新数据 ==========
|
||||
@@ -817,6 +913,23 @@ onHide() {
|
||||
}
|
||||
},
|
||||
|
||||
/** 支付成功后弹出后台可配置的图文公告(阅读时长=0 即可直接关闭) */
|
||||
showPaySuccessPopup(payType) {
|
||||
const keyMap = {
|
||||
huiyuan: 'huiyuanchenggong',
|
||||
yajin: 'yajinchenggong',
|
||||
jifen: 'jifenchenggong',
|
||||
};
|
||||
const pageKey = keyMap[payType];
|
||||
if (!pageKey) return;
|
||||
try {
|
||||
PopupService.reset();
|
||||
} catch (e) { /* ignore */ }
|
||||
setTimeout(() => {
|
||||
PopupService.checkAndShow(this, pageKey);
|
||||
}, 450);
|
||||
},
|
||||
|
||||
showLoading(text = '加载中...') {
|
||||
this.setData({
|
||||
payLoading: true,
|
||||
@@ -834,12 +947,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 +982,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,30 +993,22 @@ 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 (method === 'wx' || method === 'alipay') {
|
||||
const payMethod = method === 'alipay' ? 'alipay' : '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' } },
|
||||
}, payMethod);
|
||||
} else if (currentBuyType === 3) {
|
||||
// ✅ 调用已修正的积分支付函数
|
||||
this.handleJifenBuy({ currentTarget: { dataset: { disabled: 'false' } } });
|
||||
this.handleJifenBuy({ currentTarget: { dataset: { disabled: 'false' } } }, payMethod);
|
||||
}
|
||||
} else if (method === 'balance') {
|
||||
this.fetchBalanceOptions();
|
||||
@@ -909,7 +1017,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 +1042,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 +1084,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,18 +1099,19 @@ 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 || {};
|
||||
const payType = currentBuyType === 1 ? 'huiyuan' : 'jifen';
|
||||
await this.updateAfterPayment(
|
||||
currentBuyType === 1 ? 'huiyuan' : (currentBuyType === 2 ? 'yajin' : 'jifen'),
|
||||
payType,
|
||||
currentBuyType === 1 ? currentHuiyuanId : null,
|
||||
responseData
|
||||
);
|
||||
wx.showToast({ title: '购买成功', icon: 'success' });
|
||||
this.showPaySuccessPopup(payType);
|
||||
} else {
|
||||
wx.showToast({ title: res.data.message || '购买失败', icon: 'none' });
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification"
|
||||
},
|
||||
"navigationBarTitleText": "充值中心",
|
||||
"navigationBarBackgroundColor": "#0a0e2a",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#0a0e2a",
|
||||
"backgroundTextStyle": "light",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification",
|
||||
"popup-notice": "/components/popup-notice/popup-notice"
|
||||
},
|
||||
"navigationBarTitleText": "VIP",
|
||||
"navigationBarBackgroundColor": "#FFF9F0",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#FFF9F0"
|
||||
}
|
||||
@@ -1,528 +1,214 @@
|
||||
<!--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"
|
||||
<swiper
|
||||
class="vip-swiper"
|
||||
current="{{currentHuiyuanIndex}}"
|
||||
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>
|
||||
|
||||
<!-- 卡片内容 -->
|
||||
<view class="card-content">
|
||||
<view class="vip-badge" wx:if="{{item.isBought}}">
|
||||
<text class="badge-text">已拥有</text>
|
||||
<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="vip-price-row">
|
||||
<text class="vip-currency">¥</text>
|
||||
<text class="vip-price">{{item.jiage}}</text>
|
||||
</view>
|
||||
<text class="vip-validity">有效期 {{item.formal_days || 30}} 天</text>
|
||||
</view>
|
||||
|
||||
<view class="vip-title">
|
||||
<text class="title-text">{{item.mingzi}}</text>
|
||||
<view class="title-underline"></view>
|
||||
</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>
|
||||
|
||||
<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-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-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">{{rulesTitle || ((currentHuiyuan.mingzi || '会员') + '规则')}}</text>
|
||||
</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 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>
|
||||
<text class="rule-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="section-head">
|
||||
<view class="section-bar"></view>
|
||||
<text class="section-title">{{benefitsTitle || ((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" style="{{item.accentStyle}}">{{item.desc}}</text>
|
||||
</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>
|
||||
<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="section-head">
|
||||
<view class="section-bar"></view>
|
||||
<text class="section-title">{{advantagesTitle || '平台优势'}}</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>
|
||||
|
||||
<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>
|
||||
</view>
|
||||
<view
|
||||
wx:if="{{currentHuiyuan.can_buy_trial}}"
|
||||
class="tech-buy-btn trial-buy-btn"
|
||||
bindtap="onBuyHuiyuanClick"
|
||||
data-id="{{currentHuiyuan.id}}"
|
||||
data-trial="true"
|
||||
>
|
||||
<view class="buy-btn-bg"></view>
|
||||
<text class="buy-btn-text">购买体验版</text>
|
||||
</view>
|
||||
<text class="advantage-title">{{item.title}}</text>
|
||||
<text class="advantage-desc">{{item.desc}}</text>
|
||||
</view>
|
||||
</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="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 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="buy-btn buy-btn--outline"
|
||||
bindtap="onTapTrial"
|
||||
data-id="{{currentHuiyuan.id}}"
|
||||
data-trial="true">
|
||||
体验会员 (¥{{currentHuiyuan.trial_price}})
|
||||
</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
|
||||
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 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>
|
||||
</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>
|
||||
</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="alipay">
|
||||
<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>
|
||||
|
||||
<!-- 🆕 统一弹窗组件 -->
|
||||
<popup-notice id="popupNotice" />
|
||||
<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
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification",
|
||||
"chenghao-tag": "/components/chenghao-tag/chenghao-tag",
|
||||
"tab-bar": "/tab-bar/index",
|
||||
"pindao-modal": "/components/pindao-modal/pindao-modal"
|
||||
},
|
||||
"navigationStyle": "custom",
|
||||
"backgroundColor": "#fff8e1",
|
||||
"backgroundTextStyle": "dark",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification",
|
||||
"chenghao-tag": "/components/chenghao-tag/chenghao-tag",
|
||||
"tab-bar": "/tab-bar/index",
|
||||
"pindao-modal": "/components/pindao-modal/pindao-modal",
|
||||
"kefu-float": "/components/kefu-float/kefu-float",
|
||||
"popup-notice": "/components/popup-notice/popup-notice"
|
||||
},
|
||||
"navigationStyle": "custom",
|
||||
"backgroundColor": "#fff8e1",
|
||||
"backgroundTextStyle": "dark",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<!-- 打手端个人中心 - 逍遥梦布局,逻辑/事件不变 -->
|
||||
|
||||
<!-- 未注册 -->
|
||||
<block wx:if="{{!isDashou && !isGuanshi && !isZuzhang && !isKaoheguan}}">
|
||||
<view class="user-page unreg-page">
|
||||
<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>
|
||||
<view class="unreg-area">
|
||||
<view class="panel shadow unreg-card">
|
||||
<text class="unreg-title">接单员认证</text>
|
||||
<input class="unreg-input" type="text" placeholder="输入邀请码" maxlength="100" value="{{inviteCode}}" bindinput="onInviteCodeInput" placeholder-class="ph" />
|
||||
<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>
|
||||
</view>
|
||||
<tab-bar/>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 已注册 -->
|
||||
<block wx:else>
|
||||
<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>
|
||||
|
||||
<scroll-view
|
||||
class="content"
|
||||
scroll-y
|
||||
enhanced
|
||||
show-scrollbar="{{false}}"
|
||||
refresher-enabled="{{true}}"
|
||||
refresher-threshold="80"
|
||||
refresher-default-style="black"
|
||||
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"/>
|
||||
<view class="user-text-col">
|
||||
<view class="nickname-row">
|
||||
<text class="nickname">{{dashouNicheng || '接单员'}}</text>
|
||||
<view class="deposit-tag" wx:if="{{yajin && yajin != '0' && yajin != '0.00'}}">保证金</view>
|
||||
<view class="role-tags">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
<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) || 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}}"/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view scroll-x class="badge-scroll" wx:if="{{isGuanshi && guanshiChenghaoList.length > 0}}">
|
||||
<view class="badge-tag-wrap" wx:for="{{guanshiChenghaoList}}" wx:key="index">
|
||||
<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>
|
||||
|
||||
<view class="wallet-card flexb">
|
||||
<view bindtap="goToWalletDetail">
|
||||
<view class="w-label">我的佣金(元)</view>
|
||||
<view class="w-num">¥{{totalAssets}}</view>
|
||||
<view class="w-sub">可提现 ¥{{yongjin || '0.00'}} · 查看</view>
|
||||
</view>
|
||||
<view class="wallet-btns flex">
|
||||
<view class="btn-outline" catchtap="goToRecharge">充值</view>
|
||||
<view class="btn-outline" catchtap="goToTotalWithdraw">去提现</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<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>
|
||||
|
||||
<view class="freeze-row flexa">
|
||||
<view class="freeze-col flexmc">
|
||||
<text class="freeze-num">{{jinfen || '0'}}</text>
|
||||
<text class="freeze-lbl">积分</text>
|
||||
</view>
|
||||
<view class="freeze-col flexmc">
|
||||
<text class="freeze-num">{{yajin || '0.00'}}</text>
|
||||
<text class="freeze-lbl">保证金</text>
|
||||
</view>
|
||||
<view class="freeze-col flexmc">
|
||||
<text class="freeze-num">{{yongjin || '0.00'}}</text>
|
||||
<text class="freeze-lbl">佣金</text>
|
||||
</view>
|
||||
</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="my-box shadow order-nav-box">
|
||||
<view class="panel-title flexb" bindtap="goToAllOrders">
|
||||
<text class="lg">我的订单</text>
|
||||
<text class="muted">全部订单 ›</text>
|
||||
</view>
|
||||
<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>
|
||||
<text class="sm">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<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>
|
||||
</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="{{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="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="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>
|
||||
|
||||
<view class="loading-mask" wx:if="{{isLoading}}">
|
||||
<view class="loading-spinner"></view>
|
||||
</view>
|
||||
|
||||
<global-notification id="global-notification"/>
|
||||
<popup-notice id="popupNotice"/>
|
||||
<pindao-modal
|
||||
visible="{{pindaoVisible}}"
|
||||
title="{{pindaoTitle}}"
|
||||
channelNo="{{pindaoChannelNo}}"
|
||||
images="{{pindaoImages}}"
|
||||
bind:close="closePindaoModal"
|
||||
/>
|
||||
@@ -0,0 +1,240 @@
|
||||
<!-- 打手端个人中心 - 逍遥梦布局,逻辑/事件不变 -->
|
||||
|
||||
<!-- 未注册 -->
|
||||
<block wx:if="{{!isDashou && !isGuanshi && !isZuzhang && !isKaoheguan}}">
|
||||
<view class="user-page unreg-page">
|
||||
<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>
|
||||
<view class="unreg-area">
|
||||
<view class="panel shadow unreg-card">
|
||||
<text class="unreg-title">接单员认证</text>
|
||||
<input class="unreg-input" type="text" placeholder="输入邀请码" maxlength="100" value="{{inviteCode}}" bindinput="onInviteCodeInput" placeholder-class="ph" />
|
||||
<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>
|
||||
</view>
|
||||
<tab-bar/>
|
||||
<kefu-float bottom="200rpx" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 已注册 -->
|
||||
<block wx:else>
|
||||
<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>
|
||||
|
||||
<scroll-view
|
||||
class="content"
|
||||
scroll-y
|
||||
enhanced
|
||||
show-scrollbar="{{false}}"
|
||||
refresher-enabled="{{true}}"
|
||||
refresher-threshold="80"
|
||||
refresher-default-style="black"
|
||||
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"/>
|
||||
<view class="user-text-col">
|
||||
<view class="nickname-row">
|
||||
<text class="nickname">{{dashouNicheng || '接单员'}}</text>
|
||||
<view class="deposit-tag" wx:if="{{yajin && yajin != '0' && yajin != '0.00'}}">保证金</view>
|
||||
<view class="role-tags">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
<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="my-zhiding-mine-strip" wx:if="{{myZhidingCount > 0 && isDashou}}" bindtap="goToZhidingFromMine">
|
||||
<view class="my-zhiding-mine-strip-left">
|
||||
<text class="my-zhiding-mine-badge">指定</text>
|
||||
<text class="my-zhiding-mine-count">{{myZhidingCount}}单待接待</text>
|
||||
</view>
|
||||
<text class="my-zhiding-mine-arrow">去处理 ›</text>
|
||||
</view>
|
||||
|
||||
<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}}"/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view scroll-x class="badge-scroll" wx:if="{{isGuanshi && guanshiChenghaoList.length > 0}}">
|
||||
<view class="badge-tag-wrap" wx:for="{{guanshiChenghaoList}}" wx:key="index">
|
||||
<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>
|
||||
|
||||
<view class="wallet-card flexb">
|
||||
<view bindtap="goToWalletDetail">
|
||||
<view class="w-label">我的佣金(元)</view>
|
||||
<view class="w-num">¥{{totalAssets}}</view>
|
||||
<view class="w-sub">可提现 ¥{{yongjin || '0.00'}} · 查看</view>
|
||||
</view>
|
||||
<view class="wallet-btns flex">
|
||||
<view class="btn-outline" catchtap="goToRecharge">充值</view>
|
||||
<view class="btn-outline" catchtap="goToTotalWithdraw">去提现</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="banner-row flex" wx:if="{{showRechargeBanners}}">
|
||||
<image wx:if="{{imgUrls.bannerVip}}" class="banner-img" src="{{imgUrls.bannerVip}}" mode="widthFix" bindtap="goToRecharge"/>
|
||||
<image wx:if="{{imgUrls.bannerDeposit}}" class="banner-img" src="{{imgUrls.bannerDeposit}}" mode="widthFix" bindtap="goToRecharge"/>
|
||||
</view>
|
||||
<view class="recharge-text-row flex" wx:else>
|
||||
<view class="recharge-text-btn" catchtap="goToRecharge">充值会员</view>
|
||||
<view class="recharge-text-btn" catchtap="goToRecharge">充值保证金</view>
|
||||
</view>
|
||||
|
||||
<view class="qiepian-wrap" bindtap="onQiepianTap">
|
||||
<image class="qiepian-banner" src="{{qiepianBanner}}" mode="widthFix"/>
|
||||
</view>
|
||||
|
||||
<view class="freeze-row flexa">
|
||||
<view class="freeze-col flexmc">
|
||||
<text class="freeze-num">{{jinfen || '0'}}</text>
|
||||
<text class="freeze-lbl">积分</text>
|
||||
</view>
|
||||
<view class="freeze-col flexmc">
|
||||
<text class="freeze-num">{{yajin || '0.00'}}</text>
|
||||
<text class="freeze-lbl">保证金</text>
|
||||
</view>
|
||||
<view class="freeze-col flexmc">
|
||||
<text class="freeze-num">{{yongjin || '0.00'}}</text>
|
||||
<text class="freeze-lbl">佣金</text>
|
||||
</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>
|
||||
|
||||
<view wx:if="{{pendingPenaltyCount > 0}}" class="tip-bar flexb" bindtap="goToMyPunishment">
|
||||
<view class="flex">
|
||||
<image class="tip-ico" src="https://bintao.xmxym88.com/xcx/bintao/51.png" mode="aspectFit"/>
|
||||
<text>你有{{pendingPenaltyCount}}条罚单待处理, 前往处理</text>
|
||||
</view>
|
||||
<text class="tip-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<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="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>
|
||||
<text class="sm">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<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>
|
||||
</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="{{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="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="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/>
|
||||
<kefu-float bottom="200rpx" />
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view class="loading-mask" wx:if="{{isLoading}}">
|
||||
<view class="loading-spinner"></view>
|
||||
</view>
|
||||
|
||||
<global-notification id="global-notification"/>
|
||||
<popup-notice id="popupNotice"/>
|
||||
<pindao-modal
|
||||
visible="{{pindaoVisible}}"
|
||||
title="{{pindaoTitle}}"
|
||||
channelNo="{{pindaoChannelNo}}"
|
||||
images="{{pindaoImages}}"
|
||||
bind:close="closePindaoModal"
|
||||
/>
|
||||
@@ -0,0 +1,618 @@
|
||||
/* 打手端个人中心 - 逍遥梦 UI(逻辑不变) */
|
||||
@import '../../styles/dashou-xym-user.wxss';
|
||||
|
||||
page {
|
||||
background: #fff8e1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.flex { display: flex; align-items: center; }
|
||||
.flexb { display: flex; align-items: center; justify-content: space-between; }
|
||||
.flexa { display: flex; align-items: center; justify-content: space-around; }
|
||||
.flexmc { display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
||||
|
||||
.user-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, #f7dc51 0%, #fff 28%, #fff8e1 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-bar,
|
||||
.nav-bar,
|
||||
.content,
|
||||
.unreg-area {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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;
|
||||
color: #343434;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.refresh-float {
|
||||
position: fixed;
|
||||
right: 24rpx;
|
||||
top: calc(env(safe-area-inset-top) + 100rpx);
|
||||
z-index: 10;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.refresh-float-ico {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
padding: 10rpx 30rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 110rpx;
|
||||
height: 110rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid #f1f7ff;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.nickname-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.role-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.role-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
background: linear-gradient(180deg, #fae04d, #ffc0a3);
|
||||
color: #492f00;
|
||||
}
|
||||
|
||||
.sn-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.sn {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.copy-ico {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-left: 8rpx;
|
||||
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;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.badge-zone {
|
||||
padding: 0 24rpx 8rpx;
|
||||
}
|
||||
|
||||
.badge-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.badge-tag-wrap {
|
||||
display: inline-block;
|
||||
transform: scale(0.72);
|
||||
transform-origin: left center;
|
||||
margin-right: -8rpx;
|
||||
}
|
||||
|
||||
.deposit-tag {
|
||||
margin-left: 8rpx;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
background: linear-gradient(90deg, #e8c547, #8b6914);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.wallet-card {
|
||||
margin: 15rpx 30rpx 0;
|
||||
padding: 24rpx 20rpx;
|
||||
border-radius: 26rpx;
|
||||
background: #fef6d4;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wallet-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.w-label {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.w-num {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.w-sub {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.wallet-btns {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wallet-btns .btn-outline {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
padding: 10rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
border: 2rpx solid #fff;
|
||||
border-radius: 60rpx;
|
||||
background: linear-gradient(180deg, #fae04d, #ffc0a3);
|
||||
color: #492f00;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.banner-row {
|
||||
margin: 20rpx 24rpx 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.banner-row .banner-img {
|
||||
width: 48%;
|
||||
height: auto;
|
||||
display: block;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.banner-row .banner-img:first-child {
|
||||
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: #fef6d4;
|
||||
}
|
||||
|
||||
.freeze-col {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.freeze-num {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #7e893c;
|
||||
}
|
||||
|
||||
.freeze-lbl {
|
||||
font-size: 24rpx;
|
||||
color: #7e893c;
|
||||
font-weight: 700;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.btn-bg {
|
||||
background: linear-gradient(180deg, #fae04d, #ffc0a3);
|
||||
border-radius: 30px;
|
||||
color: #492f00;
|
||||
}
|
||||
|
||||
.withdraw {
|
||||
padding: 14rpx 28rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
border: 2rpx solid #fff;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.vip-strip {
|
||||
margin: 16rpx 30rpx 0;
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, #3d3428, #1a1a2e);
|
||||
}
|
||||
|
||||
.vip-strip-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #fcd270;
|
||||
}
|
||||
|
||||
.vip-strip-sub {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: rgba(252, 210, 112, 0.65);
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.vip-strip-btn {
|
||||
padding: 10rpx 24rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.panel {
|
||||
margin: 20rpx 20rpx 12rpx;
|
||||
background: #fdfcfa;
|
||||
border-radius: 20rpx;
|
||||
padding: 20rpx 10rpx 10rpx;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
padding: 8rpx 25rpx 10rpx;
|
||||
}
|
||||
|
||||
.lg {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #343434;
|
||||
}
|
||||
|
||||
.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: 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 {
|
||||
width: 62rpx;
|
||||
height: 62rpx;
|
||||
margin-bottom: 10rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.sm {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.func-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.func-item {
|
||||
width: 25%;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.func-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.func-txt {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.func-tag {
|
||||
font-size: 20rpx;
|
||||
color: #bbb;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.func-tag-done {
|
||||
color: #c9a962;
|
||||
}
|
||||
|
||||
.rank-hub-tip {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rank-hub-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rank-hub-track {
|
||||
display: inline-flex;
|
||||
padding: 8rpx 16rpx 20rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.rank-tile {
|
||||
display: inline-block;
|
||||
width: 280rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.rank-tile-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 20rpx;
|
||||
min-height: 120rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.rank-tile-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 16rpx;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
flex-shrink: 0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.rank-tile-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.rank-tile-name {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.rank-tile-sub {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.rank-tile-go {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
padding: 6rpx 14rpx;
|
||||
border-radius: 20rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.rank-theme-blue {
|
||||
background: linear-gradient(135deg, #3d6eb5 0%, #6ba3f7 100%);
|
||||
}
|
||||
|
||||
.rank-theme-gold {
|
||||
background: linear-gradient(135deg, #a8842a 0%, #f5d563 100%);
|
||||
}
|
||||
|
||||
.rank-theme-gold .rank-tile-name,
|
||||
.rank-theme-gold .rank-tile-sub,
|
||||
.rank-theme-gold .rank-tile-go {
|
||||
color: #3d2a00;
|
||||
}
|
||||
|
||||
.rank-theme-purple {
|
||||
background: linear-gradient(135deg, #6b4fbf 0%, #9b7fe8 100%);
|
||||
}
|
||||
|
||||
.rank-theme-orange {
|
||||
background: linear-gradient(135deg, #d4652a 0%, #f5a962 100%);
|
||||
}
|
||||
|
||||
/* 未注册 */
|
||||
.unreg-page .unreg-area {
|
||||
flex: 1;
|
||||
padding: 40rpx 30rpx 16rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.unreg-card {
|
||||
padding: 40rpx 30rpx !important;
|
||||
}
|
||||
|
||||
.unreg-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.unreg-input {
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: #f7f7f7;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
border: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.ph { color: #bbb; }
|
||||
|
||||
.unreg-btn {
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.unreg-btn-text { color: #492f00; }
|
||||
|
||||
.unreg-btn-secondary {
|
||||
text-align: center;
|
||||
padding: 18rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.unreg-link {
|
||||
text-align: center;
|
||||
padding: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.loading-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid rgba(0, 0, 0, 0.08);
|
||||
border-top-color: #ffd061;
|
||||
border-radius: 50%;
|
||||
animation: fighterSpin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes fighterSpin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
Loaded 100 of 327 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user