diff --git a/miniprogram/app.js b/miniprogram/app.js
new file mode 100644
index 0000000..fb438a9
--- /dev/null
+++ b/miniprogram/app.js
@@ -0,0 +1,345 @@
+// app.js - 优化生产版本(修复重连机制和用户ID问题)
+import GoEasy from './static/lib/goeasy-2.13.24.esm.min';
+const ChatCore = require('./utils/chat-core');
+import { check } from './utils/phone-auth';
+
+
+// ===== 角色 → 默认首页(与 custom-tab-bar 完全一致) =====
+const roleDefaultPage = {
+ normal: '/pages/index/index',
+ dashou: '/pages/dashouduan/dashouduan',
+ shangjia: '/pages/shangjiaduan/shangjiaduan', // 商家首页也是商城
+ guanshi: '/pages/guanshiduan/guanshiduan',
+ zuzhang: '/pages/zuzhangduan/zuzhangduan',
+ kaoheguan: '/pages/kaoheguan/kaoheguan'
+};
+
+App({
+ globalData: {
+
+ hasShownPopupOnColdStart: false,
+
+ apiBaseUrl: 'https://6ccy.top/hqhd',
+ ossImageUrl: '',
+ morentouxiang: '',
+ dashouguize: '',
+ paihangAssets: null,
+
+ shangpinliebiao: [],
+ shangpinzhuanqu: [],
+ shangpinleixing: [],
+ shangpinlunbo: [],
+ shangpingonggao: '',
+ lunbozhanwei: '/images/lunbozhanwei.jpg',
+
+ dingdanTiaoshu: {
+ daifuwu: 0,
+ fuwuzhong: 0,
+ yiwancheng: 0,
+ yituikuan: 0
+ },
+
+ dashouqun: '',
+ dashouqunid: '',
+ guanshiqun: '',
+ guanshiqunid: '',
+
+ appId:'wx2517043ca3f39677',
+
+ shangjiastatus: 0,
+ dashoustatus: 0,
+ guanshistatus: 0,
+ dashouNicheng: '',
+ zhanghaoStatus: null,
+ dashouzhuangtai: null,
+ yongjin: null,
+ zonge: null,
+ yajin: null,
+ chenghao: '',
+ jinfen: null,
+ clumber: [{
+ huiyuanid: '',
+ huiyuanming: '',
+ daoqi: ''
+ }],
+ chengjiaoliang: null,
+ zaixianZhuangtai: null,
+
+ guanshi: {
+ nicheng: '',
+ uid: '',
+ touxiang: '',
+ gszhstatus: '',
+ yaoqingzongshu: 0,
+ fenyongzonge: '0.00',
+ fenyongtixian: '0.00'
+ },
+
+ shangjia: {
+ sjzhzhuangtai: '',
+ sjyue: '',
+ fadanzong: null,
+ tuikuanzong: null,
+ riliushui: '',
+ yueliushui: ''
+ },
+
+ liaotian_liebiao: [],
+ xshenfen: 1,
+
+ goEasyConfig: null,
+ chatEnabled: false,
+ kefuConfig: {
+ link: '',
+ enterpriseId: ''
+ },
+
+ cosConfig: {
+ bucket: '',
+ region: 'ap-shanghai',
+ uploadPathPrefix: 'order/'
+ },
+
+ goEasyConnection: {
+ status: 'disconnected',
+ userId: '',
+ identityType: '',
+ lastConnectTime: 0,
+ autoReconnect: true,
+ reconnectAttempts: 0,
+ maxReconnectAttempts: 5,
+ heartbeatInterval: null,
+ cacheKeys: {
+ savedConnection: 'savedGoEasyConnection',
+ userId: 'goEasyUserId',
+ identityType: 'currentGoEasyIdentity',
+ connectTime: 'goEasyConnectTime'
+ },
+ config: {
+ heartbeatInterval: 20000,
+ reconnectDelay: 3000,
+ offlineTimeout: 30000,
+ cacheValidityHours: 12
+ }
+ },
+
+ messageManager: {
+ unreadTotal: 0,
+ unreadMap: {},
+ latestMessages: [],
+ notificationQueue: [],
+ notificationVisible: false,
+ lastNotificationTime: 0,
+ notificationCooldown: 3000,
+ tabBarIndex: 2,
+ showTabBarBadge: true,
+ customTabBar: true,
+ tabBarBadgeText: 0,
+ soundEnabled: true,
+ vibrationEnabled: true,
+ doNotDisturb: false,
+ doNotDisturbStart: '22:00',
+ doNotDisturbEnd: '08:00',
+ notificationStyle: {
+ position: 'top',
+ duration: 3000,
+ backgroundColor: '#00f7ff',
+ textColor: '#ffffff',
+ borderRadius: '16rpx',
+ boxShadow: '0 10rpx 30rpx rgba(0, 247, 255, 0.3)'
+ },
+ currentNotification: null,
+ cacheKeys: {
+ messageSettings: 'messageSettings',
+ unreadTotal: 'messageUnreadTotal',
+ latestMessages: 'latestMessages',
+ notificationMuted: 'notificationMuted'
+ }
+ },
+
+ pageState: {
+ currentPage: '',
+ isInChatPage: false,
+ currentChatId: '',
+ lastPageUpdate: 0
+ },
+
+ eventListeners: {},
+ debugMode: false,
+ currentUser: null,
+ currentRole: 'normal' // 当前角色,默认老板
+ },
+
+ // ========== 仅修改 onLaunch,其余未动 ==========
+ async onLaunch() {
+ // ---------- ① 立即隐藏“返回首页”按钮 ----------
+ wx.hideHomeButton();
+ wx.onAppRoute(() => {
+ wx.hideHomeButton();
+ });
+ // 🆕 手机号认证检查
+
+
+ // ---------- ② 恢复角色并跳转(不依赖网络) ----------
+ const savedRole = wx.getStorageSync('currentRole') || 'normal';
+ this.globalData.currentRole = savedRole;
+
+ const targetPage = roleDefaultPage[savedRole] || '/pages/index/index';
+ if (savedRole !== 'normal' || targetPage !== '/pages/index/index') {
+ setTimeout(() => {
+ wx.reLaunch({ url: targetPage });
+ }, 0);
+ }
+
+ // ---------- ③ 后台静默获取配置,成功后再初始化 GoEasy/IM ----------
+ this.fetchDynamicConfig()
+ .then(config => {
+ this.applyDynamicConfig(config);
+ this.initGoEasyWithConfig();
+ ChatCore.initGlobalMessageSystem(this);
+ this.initCurrentUser();
+ this.bootstrapImListening();
+ })
+ .catch(error => {
+ console.error('获取动态配置失败:', error);
+ this.initGoEasyWithConfig();
+ ChatCore.initGlobalMessageSystem(this);
+ this.initCurrentUser();
+ });
+
+ try {
+ const needAuth = await check();
+ if (needAuth) {
+ wx.reLaunch({ url: '/pages/phone-auth/phone-auth' });
+ return;
+ }
+ } catch (e) {}
+ },
+
+ onShow() {
+ if (typeof this.ensureConnection === 'function') {
+ this.ensureConnection();
+ }
+ },
+
+ bootstrapImListening() {
+ const uid = wx.getStorageSync('uid');
+ if (!uid) return;
+ try {
+ this.ensureConnection();
+ } catch (e) {
+ console.warn('IM 启动失败:', e);
+ }
+ },
+
+
+
+
+ // 以下三个方法完全保持原样
+ fetchDynamicConfig() {
+ return new Promise((resolve, reject) => {
+ wx.request({
+ url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq',
+ method: 'POST',
+ header: { 'content-type': 'application/json' },
+ success: (res) => {
+ if (res.statusCode === 200 && res.data && res.data.code === 0) {
+ resolve(res.data.data);
+ } else {
+ reject(new Error(res.data?.msg || '接口返回错误'));
+ }
+ },
+ fail: reject
+ });
+ });
+ },
+
+ applyDynamicConfig(config) {
+ if (config.cos) {
+ this.globalData.ossImageUrl = config.cos.ossImageUrl || '';
+ this.globalData.cosConfig = {
+ bucket: config.cos.bucket || '',
+ region: config.cos.region || 'ap-shanghai',
+ uploadPathPrefix: config.cos.uploadPathPrefix || 'order/'
+ };
+ }
+ if (config.goEasy) {
+ this.globalData.goEasyConfig = {
+ host: config.goEasy.host || 'hangzhou.goeasy.io',
+ appkey: config.goEasy.appkey || ''
+ };
+ }
+ if (config.otherConfig) {
+ this.globalData.morentouxiang = config.otherConfig.morentouxiang || '';
+ this.globalData.dashouguize = config.otherConfig.dashouguize || '';
+ this.globalData.paihangAssets = config.otherConfig.paihangAssets || null;
+ }
+ if (config.kefu) {
+ this.globalData.kefuConfig = {
+ link: config.kefu.link || '',
+ enterpriseId: config.kefu.enterpriseId || ''
+ };
+ }
+ },
+
+ initGoEasyWithConfig() {
+ const cfg = this.globalData.goEasyConfig;
+ if (!cfg || !cfg.appkey) {
+ console.error('GoEasy appkey 未配置,无法初始化');
+ this.globalData.chatEnabled = false;
+ return;
+ }
+ try {
+ wx.goEasy = GoEasy.getInstance({
+ host: cfg.host || 'hangzhou.goeasy.io',
+ appkey: cfg.appkey,
+ modules: ['im', 'pubsub']
+ });
+ wx.GoEasy = GoEasy;
+ const imReady = !!(wx.goEasy?.im && typeof wx.goEasy.im.on === 'function');
+ this.globalData.chatEnabled = imReady;
+ if (!imReady) {
+ wx.goEasy = null;
+ }
+ } catch (e) {
+ console.warn('GoEasy 初始化失败:', e);
+ this.globalData.chatEnabled = false;
+ wx.goEasy = null;
+ }
+ },
+
+ initCurrentUser() {
+ const uid = wx.getStorageSync('uid');
+ if (uid) {
+ this.globalData.currentUser = {
+ id: uid,
+ name: '用户' + uid.substring(0, 6),
+ avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang
+ };
+ }
+ },
+
+ emitEvent(eventName, data) {
+ if (this.globalData.eventListeners[eventName]) {
+ this.globalData.eventListeners[eventName].forEach(callback => {
+ try { callback(data); } catch (error) { console.error(error); }
+ });
+ }
+ },
+
+ on(eventName, callback) {
+ if (!this.globalData.eventListeners[eventName]) {
+ this.globalData.eventListeners[eventName] = [];
+ }
+ this.globalData.eventListeners[eventName].push(callback);
+ },
+
+ off(eventName, callback) {
+ if (this.globalData.eventListeners[eventName]) {
+ const index = this.globalData.eventListeners[eventName].indexOf(callback);
+ if (index > -1) {
+ this.globalData.eventListeners[eventName].splice(index, 1);
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/dashoupaihang/dashoupaihang.js b/miniprogram/pages/dashoupaihang/dashoupaihang.js
new file mode 100644
index 0000000..1bf4b2e
--- /dev/null
+++ b/miniprogram/pages/dashoupaihang/dashoupaihang.js
@@ -0,0 +1,262 @@
+/**
+ * 统一排行榜(打手/管事/组长/商家)
+ * POST /yonghu/phbhqsj
+ */
+import request from '../../utils/request.js'
+import { resolveAvatarUrl, getDefaultAvatarUrl } from '../../utils/avatar.js'
+
+const RANK_API = '/yonghu/phbhqsj'
+
+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' },
+ ],
+ },
+}
+
+function buildAvatar(path, app) {
+ const def = getDefaultAvatarUrl(app)
+ if (!path) return def
+ return resolveAvatarUrl(path, app) || def
+}
+
+Page({
+ data: {
+ imgUrls: {},
+ roleList: ROLE_LIST,
+ dateList: DATE_LIST,
+ currentRole: 'dashou',
+ currentDate: '今日',
+ roleMeta: ROLE_META.dashou,
+ sortLabel: '成交总额',
+ isLoading: true,
+ rankList: [],
+ topThree: [],
+ restList: [],
+ isEmpty: false,
+ defaultAvatar: '',
+ },
+
+ onLoad(options) {
+ const type = options.type || options.rankType || 'dashou'
+ const validRole = ROLE_META[type] ? type : 'dashou'
+ this.initPageAssets()
+ this.applyRole(validRole, false)
+ this.fetchRankList()
+ },
+
+ onPullDownRefresh() {
+ this.fetchRankList().finally(() => wx.stopPullDownRefresh())
+ },
+
+ initPageAssets() {
+ this.syncDefaultAvatar()
+ const g = getApp().globalData
+ const assets = g.paihangAssets || {}
+ const base = (g.ossImageUrl || '') + (assets.basePath || 'beijing/paihangbang/')
+ this.setData({
+ imgUrls: {
+ pageBg: base + (assets.pageBg || 'page-bg.jpg'),
+ cardBg: base + (assets.cardBg || 'list-card-bg.png'),
+ emptyIcon: base + (assets.emptyIcon || 'empty.png'),
+ refreshIcon: base + (assets.refreshIcon || 'icon-refresh.png'),
+ },
+ })
+ },
+
+ syncDefaultAvatar() {
+ const def = getDefaultAvatarUrl()
+ if (def) this.setData({ defaultAvatar: def })
+ },
+
+ ensureAvatars() {
+ const def = getDefaultAvatarUrl()
+ if (!def) return
+ const oss = getApp().globalData.ossImageUrl || ''
+ const { rankList, topThree, restList } = this.data
+ if (!rankList.length) {
+ this.setData({ defaultAvatar: def })
+ return
+ }
+ const isValid = (url) => url && typeof url === 'string' && (url.startsWith('http') || (oss && url.indexOf(oss) === 0))
+ const fix = (item) => ({ ...item, avatar: isValid(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 = 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()
+ },
+
+ 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 = ROLE_META[role]
+ const app = getApp()
+ const uid = raw.yonghuid || raw.uid || ''
+ const nicheng = raw.nicheng || raw.nick || '用户'
+ const avatar = buildAvatar(raw.touxiang || raw.avatar, app)
+ 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]
+ const mainType = meta.mainType || 'money'
+ const mainValue = mainType === 'money' ? this.formatMoney(sortRaw) : this.formatInt(sortRaw)
+ return {
+ mingci: raw.mingci || index + 1,
+ uid,
+ nicheng,
+ avatar,
+ mainType,
+ mainPrefix: mainType === 'money' ? '¥' : '',
+ mainValue,
+ mainLabel: meta.sortLabel,
+ metrics,
+ }
+ },
+
+ async fetchRankList() {
+ this.setData({ isLoading: true, isEmpty: false })
+ try {
+ const res = await request({
+ url: RANK_API,
+ method: 'POST',
+ data: { shenfen: this.data.currentRole, riqi: this.data.currentDate },
+ })
+ const body = res?.data || {}
+ const ok = body.code === 0 || body.code === 200
+ let list = ok && body.data ? (body.data.list || body.data.paihang_list || []) : []
+ if (!Array.isArray(list)) list = []
+ const role = this.data.currentRole
+ const normalized = list.slice(0, 50).map((item, i) => this.normalizeItem(item, i, role))
+ this.setData({
+ rankList: normalized,
+ topThree: normalized.slice(0, 3),
+ restList: normalized.slice(3),
+ isEmpty: normalized.length === 0,
+ isLoading: false,
+ })
+ this.ensureAvatars()
+ } 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 = getDefaultAvatarUrl()
+ 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()
+ },
+})
diff --git a/miniprogram/pages/dashoupaihang/dashoupaihang.json b/miniprogram/pages/dashoupaihang/dashoupaihang.json
new file mode 100644
index 0000000..2d19320
--- /dev/null
+++ b/miniprogram/pages/dashoupaihang/dashoupaihang.json
@@ -0,0 +1,11 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "排行榜",
+ "navigationBarBackgroundColor": "#0d1117",
+ "navigationBarTextStyle": "white",
+ "enablePullDownRefresh": true,
+ "backgroundColor": "#0d1117",
+ "backgroundTextStyle": "light"
+}
diff --git a/miniprogram/pages/dashoupaihang/dashoupaihang.wxml b/miniprogram/pages/dashoupaihang/dashoupaihang.wxml
new file mode 100644
index 0000000..60b533c
--- /dev/null
+++ b/miniprogram/pages/dashoupaihang/dashoupaihang.wxml
@@ -0,0 +1,127 @@
+
+
+
+
+
+ {{roleMeta.title}}
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+ {{item.label}}
+
+ {{currentDate}} · 按{{sortLabel}} · TOP50
+
+
+
+
+ 加载中
+
+
+
+
+ 暂无排行数据
+
+
+
+
+
+
+
+ 2
+
+
+
+ {{topThree[1].nicheng}}
+ ID {{topThree[1].uid}}
+
+ {{topThree[1].mainPrefix}}{{topThree[1].mainValue}}
+ {{topThree[1].mainLabel}}
+
+
+ {{m.label}} {{m.value}}
+
+
+
+
+
+ 1
+
+
+
+ {{topThree[0].nicheng}}
+ ID {{topThree[0].uid}}
+
+ {{topThree[0].mainPrefix}}{{topThree[0].mainValue}}
+ {{topThree[0].mainLabel}}
+
+
+ {{m.label}} {{m.value}}
+
+
+
+
+
+ 3
+
+
+
+ {{topThree[2].nicheng}}
+ ID {{topThree[2].uid}}
+
+ {{topThree[2].mainPrefix}}{{topThree[2].mainValue}}
+ {{topThree[2].mainLabel}}
+
+
+ {{m.label}} {{m.value}}
+
+
+
+
+
+
+
+
+ 完整榜单
+ 第 4 - {{rankList.length}} 名
+
+
+
+
+
+ {{item.mingci}}
+
+
+ {{item.nicheng}}
+ ID {{item.uid}}
+
+ {{m.label}} {{m.value}}
+
+
+
+ {{item.mainPrefix}}{{item.mainValue}}
+ {{item.mainLabel}}
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/pages/dashoupaihang/dashoupaihang.wxss b/miniprogram/pages/dashoupaihang/dashoupaihang.wxss
new file mode 100644
index 0000000..748f24e
--- /dev/null
+++ b/miniprogram/pages/dashoupaihang/dashoupaihang.wxss
@@ -0,0 +1,415 @@
+page {
+ background: #0d1117;
+ color: #fff;
+ font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif;
+}
+
+.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);
+}
+
+.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);
+}
+
+.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;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+.state-img {
+ width: 160rpx; height: 160rpx;
+ margin-bottom: 20rpx;
+ opacity: 0.65;
+}
+
+.body-scroll {
+ position: relative;
+ z-index: 5;
+ height: calc(100vh - 300rpx);
+}
+
+.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;
+}
+
+.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);
+}
+
+.badge-silver {
+ color: #fff;
+ background: linear-gradient(145deg, #A8B4C4, #7A8899);
+}
+
+.badge-bronze {
+ color: #fff;
+ background: linear-gradient(145deg, #C4A882, #9A7858);
+}
+
+.p-avatar-wrap {
+ border-radius: 50%;
+ padding: 4rpx;
+ margin-bottom: 12rpx;
+}
+
+.wrap-first { padding: 5rpx; background: linear-gradient(145deg, #8EB8FF, #5B8FD8); }
+.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;
+}
+
+.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;
+}
+
+.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;
+}
+
+.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);
+}
+
+.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);
+}
+
+.bottom-gap { height: 48rpx; }
diff --git a/miniprogram/pages/guanshipaihang/guanshipaihang.js b/miniprogram/pages/guanshipaihang/guanshipaihang.js
new file mode 100644
index 0000000..eb7b020
--- /dev/null
+++ b/miniprogram/pages/guanshipaihang/guanshipaihang.js
@@ -0,0 +1,6 @@
+// 已合并到统一排行榜页,保留路由兼容
+Page({
+ onLoad() {
+ wx.redirectTo({ url: '/pages/dashoupaihang/dashoupaihang?type=guanshi' })
+ },
+})
diff --git a/miniprogram/pages/sjpaihang/sjpaihang.js b/miniprogram/pages/sjpaihang/sjpaihang.js
new file mode 100644
index 0000000..e6b9440
--- /dev/null
+++ b/miniprogram/pages/sjpaihang/sjpaihang.js
@@ -0,0 +1,6 @@
+// 已合并到统一排行榜页,保留路由兼容
+Page({
+ onLoad() {
+ wx.redirectTo({ url: '/pages/dashoupaihang/dashoupaihang?type=shangjia' })
+ },
+})
diff --git a/miniprogram/utils/avatar.js b/miniprogram/utils/avatar.js
new file mode 100644
index 0000000..a53a361
--- /dev/null
+++ b/miniprogram/utils/avatar.js
@@ -0,0 +1,19 @@
+/** 头像 URL 解析(排行榜等页面共用) */
+export function getDefaultAvatarUrl(app) {
+ const g = app || getApp();
+ const oss = g.globalData.ossImageUrl || '';
+ const def = g.globalData.morentouxiang || '';
+ if (!def) return '';
+ if (def.startsWith('http')) return def;
+ return oss + def.replace(/^\//, '');
+}
+
+export function resolveAvatarUrl(path, app) {
+ if (!path || path === 'null' || path === 'undefined') return '';
+ const s = String(path).trim();
+ if (!s) return '';
+ if (s.startsWith('http')) return s;
+ const g = app || getApp();
+ const oss = g.globalData.ossImageUrl || '';
+ return oss + s.replace(/^\//, '');
+}