优化了样式,抽象出了 WXSS/JS 库

This commit is contained in:
TermiNexus committed 2026-06-13 18:19:46 +08:00
1 parent 6fbae9b32c
commit 3dc03f6fe5
51 files changed
+7154 -9035

No files matched your search

+1179
View File
File diff suppressed because it is too large. Load diff
+163 -218
View File
@@ -1,218 +1,163 @@
// pages/assessor/assessor.js // pages/assessor/assessor.js
// 只新增了两个字段的接收,其余所有代码未变 import { createPage, request, isCenterPageActive, ensureRoleOnCenterPage } from '../../utils/base-page.js';
import request from '../../utils/request.js';
import { Page(createPage({
isRoleStatusActive, data: {
isCenterPageActive, isKaoheguan: false,
syncRoleStatuses, inviteCode: '',
ensureRoleOnCenterPage, statusText: '用户尚未注册或账号已被封禁',
} from '../../utils/role-tab-bar.js'; uid: '',
const app = getApp(); avatarUrl: '',
icons: {
Page({ refresh: '',
data: { tixian: '',
isKaoheguan: false, daofen: '',
isLoading: false, jilu: '',
inviteCode: '', zhongxin: '',
statusText: '用户尚未注册或账号已被封禁', bgImage: ''
uid: '', },
avatarUrl: '', kaoheData: {
icons: { shenheZongshu: 0,
refresh: '', tongguoZongshu: 0,
tixian: '', yue: '0.00',
daofen: '', zonge: '0.00',
jilu: '', shenheZhuangtai: 0,
zhongxin: '', bankuaiList: [],
bgImage: '' isRenzheng: false,
}, pendingShenheCount: 0
kaoheData: { },
shenheZongshu: 0, planetPaused: {
tongguoZongshu: 0, top: false,
yue: '0.00', bottom: false,
zonge: '0.00', left: false,
shenheZhuangtai: 0, right: false
bankuaiList: [], }
isRenzheng: false, // 🆕 是否认证 },
pendingShenheCount: 0 // 🆕 审核中数量
}, _planetTimers: {},
lastRefreshTime: 0,
canRefresh: true, onLoad() {
planetPaused: { wx.redirectTo({ url: '/pages/fighter/fighter' });
top: false, return;
bottom: false, const icons = this.initImageUrls({
left: false, 'icons.refresh': 'beijing/kaohe/shuaxin.png',
right: false 'icons.tixian': 'beijing/kaohe/tixian.png',
} 'icons.daofen': 'beijing/kaohe/daofen.png',
}, 'icons.jilu': 'beijing/kaohe/jilu.png',
'icons.zhongxin': 'beijing/kaohe/zhongxin.png',
_planetTimers: {}, 'icons.bgImage': 'beijing/kaohe/top_bg.jpg'
});
onLoad() { this.setData(icons);
wx.redirectTo({ url: '/pages/fighter/fighter' });
return; this.checkRoleStatus();
const ossUrl = app.globalData.ossImageUrl || ''; if (this.data.isKaoheguan) {
const iconBase = 'beijing/kaohe/'; this.getKaoheguanInfo();
this.setData({ }
'icons.refresh': ossUrl + iconBase + 'shuaxin.png', this.checkColdStartPopup('kaoheguanduan');
'icons.tixian': ossUrl + iconBase + 'tixian.png', },
'icons.daofen': ossUrl + iconBase + 'daofen.png',
'icons.jilu': ossUrl + iconBase + 'jilu.png', onShow() {
'icons.zhongxin': ossUrl + iconBase + 'zhongxin.png', if (isCenterPageActive(this, 'isKaoheguan', 'kaoheguanstatus')) {
'icons.bgImage': ossUrl + iconBase + 'top_bg.jpg' if (!this.data.isKaoheguan) this.setData({ isKaoheguan: true });
}); ensureRoleOnCenterPage(this, 'dashou');
this.refreshKaoheInfo();
const kaoheguanstatus = wx.getStorageSync('kaoheguanstatus'); }
if (isRoleStatusActive(kaoheguanstatus)) { },
this.loadUserInfo();
this.setData({ isKaoheguan: true }); async getKaoheguanInfo() {
this.getKaoheguanInfo(); this.setData({ isLoading: true });
ensureRoleOnCenterPage(this, 'kaoheguan'); try {
} else { const res = await request({ url: '/dengji/khgsx', method: 'POST' });
this.setData({ isKaoheguan: false }); if (res && res.data.code === 200) {
} const data = res.data.data;
}, if (data.zhuangtai === 0 || data.zhuangtai === 2) {
wx.setStorageSync('kaoheguanstatus', 0);
onReady() { this.setData({ isKaoheguan: false, isLoading: false, statusText: '用户尚未注册或账号已被封禁' });
if (isCenterPageActive(this, 'isKaoheguan', 'kaoheguanstatus')) { return;
ensureRoleOnCenterPage(this, 'kaoheguan'); }
} wx.setStorageSync('kaoheguanstatus', 1);
}, this.setData({
isKaoheguan: true,
onShow() { isLoading: false,
if (isCenterPageActive(this, 'isKaoheguan', 'kaoheguanstatus')) { kaoheData: {
if (!this.data.isKaoheguan) this.setData({ isKaoheguan: true }); shenheZongshu: data.shenhe_zongshu || 0,
ensureRoleOnCenterPage(this, 'kaoheguan'); tongguoZongshu: data.tongguo_zongshu || 0,
this.refreshKaoheInfo(); yue: data.yue || '0.00',
} zonge: data.zonge || '0.00',
}, shenheZhuangtai: data.shenhe_zhuangtai || 0,
bankuaiList: data.bankuai_list || [],
loadUserInfo() { isRenzheng: data.is_renzheng || false,
const uid = wx.getStorageSync('uid') || ''; pendingShenheCount: data.pending_shenhe_count || 0
const touxiang = wx.getStorageSync('touxiang') || ''; }
const ossUrl = app.globalData.ossImageUrl || ''; });
const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg'); ensureRoleOnCenterPage(this, 'dashou');
let avatarUrl = defaultAvatar; } else {
if (touxiang) { wx.showToast({ title: res?.data?.msg || '获取信息失败', icon: 'none' });
avatarUrl = ossUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang); this.setData({ isLoading: false });
} }
this.setData({ uid, avatarUrl }); } catch (error) {
}, console.error('获取考核官信息失败:', error);
wx.showToast({ title: '网络错误', icon: 'none' });
async getKaoheguanInfo() { this.setData({ isLoading: false });
this.setData({ isLoading: true }); }
try { },
const res = await request({ url: '/dengji/khgsx', method: 'POST' });
if (res && res.data.code === 200) { onInviteCodeInput(e) {
const data = res.data.data; this.setData({ inviteCode: e.detail.value.trim() });
if (data.zhuangtai === 0 || data.zhuangtai === 2) { },
wx.setStorageSync('kaoheguanstatus', 0);
this.setData({ isKaoheguan: false, isLoading: false, statusText: '用户尚未注册或账号已被封禁' }); async onRegister() {
return; this.registerWithInviteCode({
} inviteCode: this.data.inviteCode,
wx.setStorageSync('kaoheguanstatus', 1); apiUrl: '/dengji/khgzc',
this.setData({ role: 'dashou',
isKaoheguan: true, statusKey: 'kaoheguanstatus',
isLoading: false, successMsg: '注册成功',
kaoheData: { onSuccess: async () => {
shenheZongshu: data.shenhe_zongshu || 0, this.loadUserInfo();
tongguoZongshu: data.tongguo_zongshu || 0, await this.getKaoheguanInfo();
yue: data.yue || '0.00', ensureRoleOnCenterPage(this, 'dashou');
zonge: data.zonge || '0.00', }
shenheZhuangtai: data.shenhe_zhuangtai || 0, });
bankuaiList: data.bankuai_list || [], },
isRenzheng: data.is_renzheng || false, // 🆕
pendingShenheCount: data.pending_shenhe_count || 0 // 🆕 refreshKaoheInfo() {
} this.throttledRefresh(this.getKaoheguanInfo);
}); },
ensureRoleOnCenterPage(this, 'kaoheguan');
} else { goToWithdraw() {
wx.showToast({ title: res?.data?.msg || '获取信息失败', icon: 'none' }); const yue = this.data.kaoheData.yue || 0;
this.setData({ isLoading: false }); wx.navigateTo({
} url: `/pages/withdraw/withdraw?amount=${yue}&from=kaoheguan`
} catch (error) { });
console.error('获取考核官信息失败:', error); },
wx.showToast({ title: '网络错误', icon: 'none' }); goToDafen() { wx.navigateTo({ url: '/pages/assess-score/assess-score' }); },
this.setData({ isLoading: false }); goToJilu() { wx.navigateTo({ url: '/pages/assess-log/assess-log' }); },
} goToZhongxin() { wx.navigateTo({ url: '/pages/assess-center/assess-center' }); },
},
onPlanetTouch(e) {
onInviteCodeInput(e) { const id = e.currentTarget.dataset.id;
this.setData({ inviteCode: e.detail.value.trim() }); if (this._planetTimers[id]) {
}, clearTimeout(this._planetTimers[id]);
this._planetTimers[id] = null;
async onRegister() { }
const { inviteCode } = this.data; const paused = `planetPaused.${id}`;
if (!inviteCode) { this.setData({ [paused]: true });
wx.showToast({ title: '请输入邀请码', icon: 'none', duration: 2000 }); },
return;
} onPlanetLeave(e) {
if (inviteCode.length > 100) { const id = e.currentTarget.dataset.id;
wx.showToast({ title: '邀请码不能超过100位', icon: 'none', duration: 2000 }); this._planetTimers[id] = setTimeout(() => {
return; const paused = `planetPaused.${id}`;
} this.setData({ [paused]: false });
this.setData({ isLoading: true }); }, 5000);
try { }
const res = await request({ }, {
url: '/dengji/khgzc', roleConfig: {
method: 'POST', role: 'dashou',
data: { inviteCode: inviteCode } statusKey: 'kaoheguanstatus',
}); dataFlag: 'isKaoheguan',
if (res && res.data.code === 200) { pageId: 'kaoheguanduan',
const data = res.data.data || {}; apiUrl: '/dengji/khgsx'
syncRoleStatuses(data); }
if (!Object.prototype.hasOwnProperty.call(data, 'kaoheguanstatus')) { }));
wx.setStorageSync('kaoheguanstatus', 1);
}
wx.showToast({ title: '注册成功', icon: 'success', duration: 2000 });
this.loadUserInfo();
await this.getKaoheguanInfo();
ensureRoleOnCenterPage(this, 'kaoheguan');
} else {
wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none', duration: 2000 });
this.setData({ isLoading: false });
}
} catch (error) {
console.error('注册失败:', error);
wx.showToast({ title: '网络错误', icon: 'none' });
this.setData({ isLoading: false });
}
},
refreshKaoheInfo() {
const now = Date.now();
const lastTime = this.data.lastRefreshTime;
const canRefresh = this.data.canRefresh;
if (!canRefresh || (now - lastTime < 3000)) return;
this.setData({ lastRefreshTime: now, canRefresh: false });
this.getKaoheguanInfo();
setTimeout(() => this.setData({ canRefresh: true }), 3000);
},
goToWithdraw() {
const yue = this.data.kaoheData.yue || 0;
wx.navigateTo({
url: `/pages/withdraw/withdraw?amount=${yue}&from=kaoheguan`
});
},
goToDafen() { wx.navigateTo({ url: '/pages/assess-score/assess-score' }); },
goToJilu() { wx.navigateTo({ url: '/pages/assess-log/assess-log' }); },
goToZhongxin() { wx.navigateTo({ url: '/pages/assess-center/assess-center' }); },
onPlanetTouch(e) {
const id = e.currentTarget.dataset.id;
if (this._planetTimers[id]) {
clearTimeout(this._planetTimers[id]);
this._planetTimers[id] = null;
}
const paused = `planetPaused.${id}`;
this.setData({ [paused]: true });
},
onPlanetLeave(e) {
const id = e.currentTarget.dataset.id;
this._planetTimers[id] = setTimeout(() => {
const paused = `planetPaused.${id}`;
this.setData({ [paused]: false });
}, 5000);
}
});
+321 -331
View File
@@ -1,331 +1,321 @@
// pages/category/category.js // pages/category/category.js
const app = getApp() const app = getApp()
import { createPage } from '../../utils/base-page.js';
Page({
data: { Page(createPage({
ossImageUrl: '', data: {
searchText: '', ossImageUrl: '',
showSearchResult: false, searchText: '',
searchResults: [], showSearchResult: false,
isSearching: false, searchResults: [],
shangpinleixing: [], isSearching: false,
shangpinzhuanqu: [], shangpinleixing: [],
shangpinliebiao: [], shangpinzhuanqu: [],
selectedLeixingId: null, shangpinliebiao: [],
selectedZhuanquId: null, selectedLeixingId: null,
filteredZhuanquList: [], selectedZhuanquId: null,
filteredShangpinList: [], filteredZhuanquList: [],
zhuanquByLeixing: {}, filteredShangpinList: [],
shangpinByZhuanqu: {}, zhuanquByLeixing: {},
zhuanquGoodsCount: {}, shangpinByZhuanqu: {},
isLoading: false, zhuanquGoodsCount: {},
hasInitialized: false isLoading: false,
}, hasInitialized: false
},
onLoad(options) {
this.setData({ onLoad(options) {
ossImageUrl: app.globalData.ossImageUrl this.setData({
}) ossImageUrl: app.globalData.ossImageUrl
this.initPageData() })
}, this.initPageData()
},
onShow() {
this.registerNotificationComponent(); onShow() {
}, this.registerNotificationComponent();
},
registerNotificationComponent() {
const app = getApp(); initPageData() {
const notificationComp = this.selectComponent('#global-notification'); const that = this
if (notificationComp && notificationComp.showNotification) { this.setData({ isLoading: true })
app.globalData.globalNotification = { if (app.globalData.shangpinleixing && app.globalData.shangpinleixing.length > 0) {
show: (data) => notificationComp.showNotification(data), this.initFromGlobalData()
hide: () => notificationComp.hideNotification() } else {
}; this.loadAllShangpinData()
} }
}, },
initPageData() { initFromGlobalData() {
const that = this const leixingList = app.globalData.shangpinleixing || []
this.setData({ isLoading: true }) const zhuanquList = app.globalData.shangpinzhuanqu || []
if (app.globalData.shangpinleixing && app.globalData.shangpinleixing.length > 0) { const shangpinList = app.globalData.shangpinliebiao || []
this.initFromGlobalData()
} else { const zhuanquByLeixing = {}
this.loadAllShangpinData() const shangpinByZhuanqu = {}
} const zhuanquGoodsCount = {}
},
zhuanquList.forEach(zhuanqu => {
initFromGlobalData() { const leixingId = zhuanqu.leixing_id
const leixingList = app.globalData.shangpinleixing || [] if (!zhuanquByLeixing[leixingId]) {
const zhuanquList = app.globalData.shangpinzhuanqu || [] zhuanquByLeixing[leixingId] = []
const shangpinList = app.globalData.shangpinliebiao || [] }
zhuanquByLeixing[leixingId].push(zhuanqu)
const zhuanquByLeixing = {} })
const shangpinByZhuanqu = {}
const zhuanquGoodsCount = {} shangpinList.forEach(shangpin => {
const zhuanquId = shangpin.zhuanqu_id
zhuanquList.forEach(zhuanqu => { if (zhuanquId) {
const leixingId = zhuanqu.leixing_id if (!shangpinByZhuanqu[zhuanquId]) {
if (!zhuanquByLeixing[leixingId]) { shangpinByZhuanqu[zhuanquId] = []
zhuanquByLeixing[leixingId] = [] zhuanquGoodsCount[zhuanquId] = 0
} }
zhuanquByLeixing[leixingId].push(zhuanqu) const jiage = parseFloat(shangpin.jiage) || 0
}) const priceInteger = Math.floor(jiage)
let priceDecimal = '00'
shangpinList.forEach(shangpin => { const decimalPart = (jiage - priceInteger).toFixed(2)
const zhuanquId = shangpin.zhuanqu_id if (decimalPart > 0) {
if (zhuanquId) { priceDecimal = decimalPart.toString().split('.')[1]
if (!shangpinByZhuanqu[zhuanquId]) { }
shangpinByZhuanqu[zhuanquId] = [] shangpinByZhuanqu[zhuanquId].push({
zhuanquGoodsCount[zhuanquId] = 0 id: shangpin.id,
} biaoqian: shangpin.biaoqian,
const jiage = parseFloat(shangpin.jiage) || 0 jiage: jiage,
const priceInteger = Math.floor(jiage) priceInteger: priceInteger,
let priceDecimal = '00' priceDecimal: priceDecimal,
const decimalPart = (jiage - priceInteger).toFixed(2) tupian_url: shangpin.tupian_url,
if (decimalPart > 0) { duiwai_xiaoliang: shangpin.duiwai_xiaoliang || 0
priceDecimal = decimalPart.toString().split('.')[1] })
} zhuanquGoodsCount[zhuanquId] += 1
shangpinByZhuanqu[zhuanquId].push({ }
id: shangpin.id, })
biaoqian: shangpin.biaoqian,
jiage: jiage, const firstLeixing = leixingList[0]
priceInteger: priceInteger, let firstZhuanquId = null
priceDecimal: priceDecimal, if (firstLeixing && zhuanquByLeixing[firstLeixing.id]) {
tupian_url: shangpin.tupian_url, firstZhuanquId = zhuanquByLeixing[firstLeixing.id][0]?.id || null
duiwai_xiaoliang: shangpin.duiwai_xiaoliang || 0 }
})
zhuanquGoodsCount[zhuanquId] += 1 this.setData({
} shangpinleixing: leixingList,
}) shangpinzhuanqu: zhuanquList,
shangpinliebiao: shangpinList,
const firstLeixing = leixingList[0] zhuanquByLeixing: zhuanquByLeixing,
let firstZhuanquId = null shangpinByZhuanqu: shangpinByZhuanqu,
if (firstLeixing && zhuanquByLeixing[firstLeixing.id]) { zhuanquGoodsCount: zhuanquGoodsCount,
firstZhuanquId = zhuanquByLeixing[firstLeixing.id][0]?.id || null selectedLeixingId: firstLeixing?.id || null,
} selectedZhuanquId: firstZhuanquId
}, () => {
this.setData({ this.filterData()
shangpinleixing: leixingList, this.setData({ isLoading: false, hasInitialized: true })
shangpinzhuanqu: zhuanquList, })
shangpinliebiao: shangpinList, },
zhuanquByLeixing: zhuanquByLeixing,
shangpinByZhuanqu: shangpinByZhuanqu, loadAllShangpinData() {
zhuanquGoodsCount: zhuanquGoodsCount, const that = this
selectedLeixingId: firstLeixing?.id || null, wx.request({
selectedZhuanquId: firstZhuanquId url: app.globalData.apiBaseUrl + '/shangpin/shangpinhuoqu/',
}, () => { method: 'POST',
this.filterData() header: {
this.setData({ isLoading: false, hasInitialized: true }) 'content-type': 'application/json'
}) },
}, success(res) {
if (res.statusCode === 200 && res.data) {
loadAllShangpinData() { const data = res.data
const that = this app.globalData.shangpinleixing = data.shangpinleixing || []
wx.request({ app.globalData.shangpinzhuanqu = data.shangpinzhuanqu || []
url: app.globalData.apiBaseUrl + '/shangpin/shangpinhuoqu/', app.globalData.shangpinliebiao = data.shangpinliebiao || []
method: 'POST', that.initFromGlobalData()
header: { } else {
'content-type': 'application/json' that.handleLoadError()
}, }
success(res) { },
if (res.statusCode === 200 && res.data) { fail(error) {
const data = res.data console.error('加载商品数据失败:', error)
app.globalData.shangpinleixing = data.shangpinleixing || [] that.handleLoadError()
app.globalData.shangpinzhuanqu = data.shangpinzhuanqu || [] }
app.globalData.shangpinliebiao = data.shangpinliebiao || [] })
that.initFromGlobalData() },
} else {
that.handleLoadError() handleLoadError() {
} wx.showToast({
}, title: '加载失败,请重试',
fail(error) { icon: 'none'
console.error('加载商品数据失败:', error) })
that.handleLoadError() this.setData({ isLoading: false })
} },
})
}, filterData() {
const { selectedLeixingId, selectedZhuanquId, zhuanquByLeixing, shangpinByZhuanqu } = this.data
handleLoadError() { const filteredZhuanquList = zhuanquByLeixing[selectedLeixingId] || []
wx.showToast({ let filteredShangpinList = []
title: '加载失败,请重试', if (selectedZhuanquId) {
icon: 'none' filteredShangpinList = shangpinByZhuanqu[selectedZhuanquId] || []
}) } else if (filteredZhuanquList.length > 0) {
this.setData({ isLoading: false }) const firstZhuanquId = filteredZhuanquList[0].id
}, filteredShangpinList = shangpinByZhuanqu[firstZhuanquId] || []
this.setData({ selectedZhuanquId: firstZhuanquId })
filterData() { }
const { selectedLeixingId, selectedZhuanquId, zhuanquByLeixing, shangpinByZhuanqu } = this.data this.setData({
const filteredZhuanquList = zhuanquByLeixing[selectedLeixingId] || [] filteredZhuanquList: filteredZhuanquList,
let filteredShangpinList = [] filteredShangpinList: filteredShangpinList
if (selectedZhuanquId) { })
filteredShangpinList = shangpinByZhuanqu[selectedZhuanquId] || [] },
} else if (filteredZhuanquList.length > 0) {
const firstZhuanquId = filteredZhuanquList[0].id selectLeixing(e) {
filteredShangpinList = shangpinByZhuanqu[firstZhuanquId] || [] const leixingId = e.currentTarget.dataset.id
this.setData({ selectedZhuanquId: firstZhuanquId }) if (this.data.selectedLeixingId === leixingId) {
} return
this.setData({ }
filteredZhuanquList: filteredZhuanquList, const zhuanquList = this.data.zhuanquByLeixing[leixingId] || []
filteredShangpinList: filteredShangpinList const firstZhuanquId = zhuanquList.length > 0 ? zhuanquList[0].id : null
}) this.setData({
}, selectedLeixingId: leixingId,
selectedZhuanquId: firstZhuanquId
selectLeixing(e) { }, () => {
const leixingId = e.currentTarget.dataset.id this.filterData()
if (this.data.selectedLeixingId === leixingId) { })
return },
}
const zhuanquList = this.data.zhuanquByLeixing[leixingId] || [] selectZhuanqu(e) {
const firstZhuanquId = zhuanquList.length > 0 ? zhuanquList[0].id : null const zhuanquId = e.currentTarget.dataset.id
this.setData({ if (this.data.selectedZhuanquId === zhuanquId) {
selectedLeixingId: leixingId, return
selectedZhuanquId: firstZhuanquId }
}, () => { this.setData({
this.filterData() selectedZhuanquId: zhuanquId
}) }, () => {
}, const filteredShangpinList = this.data.shangpinByZhuanqu[zhuanquId] || []
this.setData({
selectZhuanqu(e) { filteredShangpinList: filteredShangpinList
const zhuanquId = e.currentTarget.dataset.id })
if (this.data.selectedZhuanquId === zhuanquId) { })
return },
}
this.setData({ onSearchInput(e) {
selectedZhuanquId: zhuanquId this.setData({
}, () => { searchText: e.detail.value
const filteredShangpinList = this.data.shangpinByZhuanqu[zhuanquId] || [] })
this.setData({ },
filteredShangpinList: filteredShangpinList
}) onSearchTap() {
}) const searchText = this.data.searchText.trim()
}, if (!searchText) {
wx.showToast({
onSearchInput(e) { title: '请输入搜索内容',
this.setData({ icon: 'none'
searchText: e.detail.value })
}) return
}, }
this.startSearch(searchText)
onSearchTap() { },
const searchText = this.data.searchText.trim()
if (!searchText) { onSearchConfirm(e) {
wx.showToast({ const searchText = e.detail.value.trim()
title: '请输入搜索内容', if (!searchText) {
icon: 'none' wx.showToast({
}) title: '请输入搜索内容',
return icon: 'none'
} })
this.startSearch(searchText) return
}, }
this.setData({
onSearchConfirm(e) { searchText: searchText
const searchText = e.detail.value.trim() })
if (!searchText) { this.startSearch(searchText)
wx.showToast({ },
title: '请输入搜索内容',
icon: 'none' startSearch(searchText) {
}) this.setData({
return isSearching: true,
} showSearchResult: true
this.setData({ })
searchText: searchText const allShangpin = this.data.shangpinliebiao
}) const results = []
this.startSearch(searchText) for (const item of allShangpin) {
}, if (item.biaoqian && item.biaoqian.toLowerCase().includes(searchText.toLowerCase())) {
const jiage = parseFloat(item.jiage) || 0
startSearch(searchText) { const priceInteger = Math.floor(jiage)
this.setData({ let priceDecimal = '00'
isSearching: true, const decimalPart = (jiage - priceInteger).toFixed(2)
showSearchResult: true if (decimalPart > 0) {
}) priceDecimal = decimalPart.toString().split('.')[1]
const allShangpin = this.data.shangpinliebiao }
const results = [] results.push({
for (const item of allShangpin) { id: item.id,
if (item.biaoqian && item.biaoqian.toLowerCase().includes(searchText.toLowerCase())) { biaoqian: item.biaoqian,
const jiage = parseFloat(item.jiage) || 0 jiage: jiage,
const priceInteger = Math.floor(jiage) priceInteger: priceInteger,
let priceDecimal = '00' priceDecimal: priceDecimal,
const decimalPart = (jiage - priceInteger).toFixed(2) tupian_url: item.tupian_url,
if (decimalPart > 0) { leixing_name: this.getLeixingName(item.leixing_id),
priceDecimal = decimalPart.toString().split('.')[1] zhuanqu_name: this.getZhuanquName(item.zhuanqu_id)
} })
results.push({ }
id: item.id, }
biaoqian: item.biaoqian, this.setData({
jiage: jiage, searchResults: results,
priceInteger: priceInteger, isSearching: false
priceDecimal: priceDecimal, })
tupian_url: item.tupian_url, },
leixing_name: this.getLeixingName(item.leixing_id),
zhuanqu_name: this.getZhuanquName(item.zhuanqu_id) onClearSearch() {
}) this.setData({
} searchText: '',
} showSearchResult: false,
this.setData({ searchResults: []
searchResults: results, })
isSearching: false },
})
}, getLeixingName(leixingId) {
const leixingList = this.data.shangpinleixing
onClearSearch() { for (const item of leixingList) {
this.setData({ if (item.id === leixingId) {
searchText: '', return item.jieshao
showSearchResult: false, }
searchResults: [] }
}) return ''
}, },
getLeixingName(leixingId) { getZhuanquName(zhuanquId) {
const leixingList = this.data.shangpinleixing const zhuanquList = this.data.shangpinzhuanqu
for (const item of leixingList) { for (const item of zhuanquList) {
if (item.id === leixingId) { if (item.id === zhuanquId) {
return item.jieshao return item.mingzi
} }
} }
return '' return ''
}, },
getZhuanquName(zhuanquId) { goToDetail(e) {
const zhuanquList = this.data.shangpinzhuanqu const shangpinId = e.currentTarget.dataset.id
for (const item of zhuanquList) { if (!shangpinId) return
if (item.id === zhuanquId) { wx.navigateTo({
return item.mingzi url: `/pages/product-detail/product-detail?id=${shangpinId}`
} })
} },
return ''
}, onRefresh() {
wx.showLoading({
goToDetail(e) { title: '刷新中...',
const shangpinId = e.currentTarget.dataset.id mask: true
if (!shangpinId) return })
wx.navigateTo({ app.globalData.shangpinleixing = []
url: `/pages/product-detail/product-detail?id=${shangpinId}` app.globalData.shangpinzhuanqu = []
}) app.globalData.shangpinliebiao = []
}, this.loadAllShangpinData()
setTimeout(() => {
onRefresh() { wx.hideLoading()
wx.showLoading({ wx.showToast({
title: '刷新中...', title: '刷新成功',
mask: true icon: 'success',
}) duration: 1500
app.globalData.shangpinleixing = [] })
app.globalData.shangpinzhuanqu = [] }, 1000)
app.globalData.shangpinliebiao = [] },
this.loadAllShangpinData()
setTimeout(() => { onShareAppMessage() {
wx.hideLoading() return {
wx.showToast({ title: '阿龙电竞 - 商品分类',
title: '刷新成功', path: '/pages/category/category'
icon: 'success', }
duration: 1500 }
}) }))
}, 1000)
},
onShareAppMessage() {
return {
title: '阿龙电竞 - 商品分类',
path: '/pages/category/category'
}
}
})
+43 -43
View File
@@ -1,31 +1,31 @@
<view class="chat-page"> <view class="chat-page">
<scroll-view class="msg-list" scroll-y="true" <scroll-view class="chat-msg-list" scroll-y="true"
scroll-into-view="{{scrollToView}}" scroll-into-view="{{scrollToView}}"
refresher-enabled="{{true}}" refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}" refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh" 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) + 20rpx);">
<view class="loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view> <view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<block wx:for="{{messages}}" wx:key="messageId"> <block wx:for="{{messages}}" wx:key="messageId">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction"> <view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view> <view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}"> <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="avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" /> <image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" />
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}" <view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}" data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}" data-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}" bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}"> data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text> <text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" /> <image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="order-bubble"> <view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble">
<text class="order-label">[订单]</text> <text class="chat-order-label">[订单]</text>
<text class="order-info">ID: {{item.payload.orderId}}</text> <text class="chat-order-info">ID: {{item.payload.orderId}}</text>
</view> </view>
</view> </view>
<image wx:if="{{item.senderId === currentUser.id}}" class="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="{{item.senderData.avatar || currentUser.avatar}}" mode="aspectFill" binderror="onSelfAvatarError" data-index="{{index}}" />
</view> </view>
<view wx:if="{{item.senderId === currentUser.id && item.status === 'success'}}" class="read-tag"> <view wx:if="{{item.senderId === currentUser.id && item.status === 'success'}}" class="chat-read-tag">
{{item.read ? '已读' : '未读'}} {{item.read ? '已读' : '未读'}}
</view> </view>
</view> </view>
@@ -33,53 +33,53 @@
<view id="msg-bottom" style="height:20rpx;"></view> <view id="msg-bottom" style="height:20rpx;"></view>
</scroll-view> </scroll-view>
<view class="input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));"> <view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel"> <view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="plus-grid"> <view class="chat-plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus"> <view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text> <text class="chat-plus-icon">😀</text>
<text class="plus-label">表情</text> <text class="chat-plus-label">表情</text>
</view> </view>
<view class="plus-item" catchtap="chooseImage"> <view class="chat-plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text> <text class="chat-plus-icon">📷</text>
<text class="plus-label">图片</text> <text class="chat-plus-label">图片</text>
</view> </view>
<view class="plus-item" catchtap="openOrderSender"> <view class="chat-plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text> <text class="chat-plus-icon">📋</text>
<text class="plus-label">订单</text> <text class="chat-plus-label">订单</text>
</view> </view>
</view> </view>
</view> </view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view> <view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}"> <view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll"> <scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="emoji-grid"> <view class="chat-emoji-grid">
<block wx:for="{{emojiList}}" wx:key="*this"> <block wx:for="{{emojiList}}" wx:key="*this">
<view class="emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view> <view class="chat-emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
</block> </block>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
<view class="pending-image" wx:if="{{pendingImage}}"> <view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" /> <image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view> <view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view> </view>
<view class="write-row"> <view class="chat-write-row">
<input class="text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/> <input class="chat-text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view> <view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel"> <view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single">➕</text> <text class="chat-plus-icon-single">➕</text>
</view> </view>
</view> </view>
</view> </view>
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail"> <view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop"> <view class="chat-detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text> <text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button> <button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view> </view>
</view> </view>
+1 -98
View File
@@ -1,98 +1 @@
.chat-page { width:100%; height:100vh; background:#f5f5f5; } /* 使用全局 chat-* 样式 */
.msg-list {
position: absolute;
left:0; right:0;
padding: 0 20rpx;
box-sizing: border-box;
overflow-y: auto;
}
.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
.msg-wrapper { margin-bottom:12rpx; }
.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
.right { justify-content:flex-end; }
.left { justify-content:flex-start; }
.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
.bubble-left { background:#fff; border-top-left-radius:4rpx; }
.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
.msg-text { color:inherit; }
.msg-image { max-width:240rpx; border-radius:10rpx; }
.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; }
.order-info { font-size:24rpx; color:#333; margin:6rpx 0; }
.read-tag {
font-size:20rpx;
color:#888;
text-align:right;
padding:4rpx 12rpx 0 0;
margin-top: -4rpx;
}
.input-area {
position: fixed;
left:0; right:0;
background:#f7f7f7;
border-top:1rpx solid #ddd;
z-index:10;
}
.plus-panel {
position: absolute;
bottom:100%; left:0; right:0;
background:#fff;
border-top:1rpx solid #e0e0e0;
padding: 20rpx;
transform: translateY(100%);
opacity:0;
transition: transform 0.3s, opacity 0.3s;
pointer-events:none;
z-index:9;
}
.plus-panel.show { transform: translateY(0); opacity:1; pointer-events:auto; }
.plus-grid { display: flex; justify-content: space-around; }
.plus-item { display: flex; flex-direction: column; align-items: center; width: 120rpx; }
.plus-icon { font-size: 60rpx; }
.plus-label { font-size: 24rpx; color: #666; margin-top: 8rpx; }
.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
.emoji-panel {
position: absolute; bottom:100%; left:0; right:0;
background:#fff; border-top:1rpx solid #e0e0e0;
height:380rpx;
transform: translateY(100%); opacity:0; transition:0.3s;
pointer-events:none; z-index:9;
}
.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
.emoji-scroll { height:100%; }
.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
.text-input {
flex:1; height:76rpx; background:#fff; border-radius:12rpx;
padding:0 20rpx; font-size:30rpx; margin-right:12rpx;
}
.send-btn {
width:120rpx; height:76rpx; background:#00aaff; color:#fff;
font-size:28rpx; font-weight:500; border-radius:12rpx;
display:flex; align-items:center; justify-content:center;
}
.plus-btn {
width:56rpx; height:56rpx;
display:flex; align-items:center; justify-content:center;
margin-left: 10rpx;
}
.plus-icon-single { font-size:46rpx; color:#00aaff; }
.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
+55 -55
View File
@@ -1,37 +1,37 @@
<view class="chat-page"> <view class="chat-page">
<scroll-view class="msg-list" scroll-y="true" <scroll-view class="chat-msg-list" scroll-y="true"
scroll-into-view="{{scrollToView}}" scroll-into-view="{{scrollToView}}"
refresher-enabled="{{true}}" refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}" refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh" 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) + 20rpx);">
<view class="loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view> <view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<block wx:for="{{messages}}" wx:key="messageId"> <block wx:for="{{messages}}" wx:key="messageId">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction"> <view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view> <view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}"> <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="avatar" src="{{teamAvatar}}" mode="aspectFill" /> <image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{teamAvatar}}" mode="aspectFill" />
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}" <view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}" data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}" data-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}" bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}"> data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text> <text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" data-url="{{item.payload.url}}" bindtap="previewImage"/> <image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" data-url="{{item.payload.url}}" bindtap="previewImage"/>
<view wx:elif="{{item.type === 'order'}}" class="order-bubble"> <view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble">
<text class="order-label">📋 订单消息</text> <text class="chat-order-label">📋 订单消息</text>
<view class="order-brief"> <view class="chat-order-info">
<text>订单号:{{item.payload.dingdan_id || '无'}}</text> <text>订单号:{{item.payload.dingdan_id || '无'}}</text>
<text>服务:{{item.payload.jieshao || ''}}</text> <text>服务:{{item.payload.jieshao || ''}}</text>
<text class="order-price">¥{{item.payload.jine}}</text> <text style="color:#e6a23c;font-weight:bold">¥{{item.payload.jine}}</text>
</view> </view>
<button class="order-detail-btn" catchtap="showOrderDetailPopup" data-order="{{item.payload}}" size="mini">查看详情</button> <button class="chat-copy-btn" catchtap="showOrderDetailPopup" data-order="{{item.payload}}" size="mini">查看详情</button>
</view> </view>
<text wx:else class="msg-text">{{item.payload.text || '[未知消息]'}}</text> <text wx:else class="chat-msg-text">{{item.payload.text || '[未知消息]'}}</text>
</view> </view>
<!-- 自己头像 --> <!-- 自己头像 -->
<image wx:if="{{item.senderId === currentUser.id}}" class="avatar" src="{{currentUser.avatar}}" mode="aspectFill" /> <image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar}}" mode="aspectFill" />
</view> </view>
<!-- 🔥 已读/未读标签已彻底移除 --> <!-- 🔥 已读/未读标签已彻底移除 -->
</view> </view>
@@ -40,70 +40,70 @@
</scroll-view> </scroll-view>
<!-- 底部输入区域(保持不变) --> <!-- 底部输入区域(保持不变) -->
<view class="input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));"> <view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel"> <view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="plus-grid"> <view class="chat-plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus"> <view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text> <text class="chat-plus-icon">😀</text>
<text class="plus-label">表情</text> <text class="chat-plus-label">表情</text>
</view> </view>
<view class="plus-item" catchtap="chooseImage"> <view class="chat-plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text> <text class="chat-plus-icon">📷</text>
<text class="plus-label">图片</text> <text class="chat-plus-label">图片</text>
</view> </view>
<view class="plus-item" catchtap="openOrderSender"> <view class="chat-plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text> <text class="chat-plus-icon">📋</text>
<text class="plus-label">订单</text> <text class="chat-plus-label">订单</text>
</view> </view>
</view> </view>
</view> </view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view> <view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}"> <view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll"> <scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="emoji-grid"> <view class="chat-emoji-grid">
<block wx:for="{{emojiList}}" wx:key="*this"> <block wx:for="{{emojiList}}" wx:key="*this">
<view class="emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view> <view class="chat-emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
</block> </block>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
<view class="pending-image" wx:if="{{pendingImage}}"> <view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" /> <image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view> <view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view> </view>
<view class="write-row"> <view class="chat-write-row">
<input class="text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/> <input class="chat-text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view> <view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel"> <view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single">➕</text> <text class="chat-plus-icon-single">➕</text>
</view> </view>
</view> </view>
</view> </view>
<!-- 文本双击放大弹窗 --> <!-- 文本双击放大弹窗 -->
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail"> <view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop"> <view class="chat-detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text> <text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button> <button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view> </view>
</view> </view>
<!-- 订单详情弹窗 --> <!-- 订单详情弹窗 -->
<view class="order-detail-mask" wx:if="{{showOrderDetail}}" catchtap="hideOrderDetail"> <view class="chat-detail-modal" wx:if="{{showOrderDetail}}" catchtap="hideOrderDetail">
<view class="order-detail-panel" catchtap="noop"> <view class="chat-detail-content" catchtap="noop">
<view class="order-detail-title">订单详情</view> <view class="chat-detail-text">订单详情</view>
<view class="order-detail-item">订单号:{{detailOrder.dingdan_id}}</view> <view class="chat-detail-text">订单号:{{detailOrder.dingdan_id}}</view>
<view class="order-detail-item">服务描述:{{detailOrder.jieshao}}</view> <view class="chat-detail-text">服务描述:{{detailOrder.jieshao}}</view>
<view class="order-detail-item">金额:¥{{detailOrder.jine}}</view> <view class="chat-detail-text">金额:¥{{detailOrder.jine}}</view>
<view class="order-detail-item">类型:{{detailOrder.leixing || '无'}}</view> <view class="chat-detail-text">类型:{{detailOrder.leixing || '无'}}</view>
<view class="order-detail-item">时间:{{detailOrder.shijian || '无'}}</view> <view class="chat-detail-text">时间:{{detailOrder.shijian || '无'}}</view>
<button class="order-detail-close-btn" catchtap="hideOrderDetail">关闭</button> <button class="chat-copy-btn" catchtap="hideOrderDetail">关闭</button>
</view> </view>
</view> </view>
<!-- 订单发送组件 --> <!-- 订单发送组件 -->
<order-sender visible="{{showOrderSender}}" bind:send="onSendOrder" bind:close="closeOrderSender" /> <order-sender visible="{{showOrderSender}}" bind:send="onSendOrder" bind:close="closeOrderSender" />
</view> </view>
+1 -67
View File
@@ -1,67 +1 @@
.chat-page { width:100%; height:100vh; background:#f5f5f5; } /* 使用全局 chat-* 样式 */
.msg-list { position:absolute; left:0; right:0; padding:0 20rpx; box-sizing:border-box; overflow-y:auto; }
.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
.msg-wrapper { margin-bottom:12rpx; }
.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
.right { justify-content:flex-end; }
.left { justify-content:flex-start; }
.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
.bubble-left { background:#fff; border-top-left-radius:4rpx; }
.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
.msg-text { color:inherit; }
.msg-image { max-width:240rpx; border-radius:10rpx; }
/* 已读/未读标签:靠右显示 */
.read-tag {
display: flex;
justify-content: flex-end;
font-size: 20rpx;
color: #888;
padding: 4rpx 12rpx 0 0;
margin-top: 2rpx;
}
/* 订单气泡 */
.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; margin-bottom:8rpx; }
.order-brief { font-size:24rpx; color:#333; line-height:1.6; }
.order-price { color:#e6a23c; font-weight:bold; }
.order-detail-btn { margin-top:10rpx; background:#e6a23c; color:#fff; border-radius:8rpx; }
/* 订单详情弹窗 */
.order-detail-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.6); z-index:999; display:flex; justify-content:center; align-items:center; }
.order-detail-panel { width:80%; background:#fff; border-radius:20rpx; padding:30rpx; }
.order-detail-title { font-size:34rpx; font-weight:bold; margin-bottom:20rpx; color:#333; }
.order-detail-item { font-size:28rpx; margin:16rpx 0; color:#555; }
.order-detail-close-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:30rpx; font-size:30rpx; }
/* 底部输入区域 */
.input-area { position:fixed; left:0; right:0; background:#f7f7f7; border-top:1rpx solid #ddd; z-index:10; }
.plus-panel { position:absolute; bottom:100%; left:0; right:0; background:#fff; border-top:1rpx solid #e0e0e0; padding:20rpx; transform:translateY(100%); opacity:0; transition:0.3s; pointer-events:none; z-index:9; }
.plus-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
.plus-grid { display:flex; justify-content:space-around; }
.plus-item { display:flex; flex-direction:column; align-items:center; width:120rpx; }
.plus-icon { font-size:60rpx; }
.plus-label { font-size:24rpx; color:#666; margin-top:8rpx; }
.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
.emoji-panel { position:absolute; bottom:100%; left:0; right:0; background:#fff; border-top:1rpx solid #e0e0e0; height:380rpx; transform:translateY(100%); opacity:0; transition:0.3s; pointer-events:none; z-index:9; }
.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
.emoji-scroll { height:100%; }
.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
.text-input { flex:1; height:76rpx; background:#fff; border-radius:12rpx; padding:0 20rpx; font-size:30rpx; margin-right:12rpx; }
.send-btn { width:120rpx; height:76rpx; background:#00aaff; color:#fff; font-size:28rpx; font-weight:500; border-radius:12rpx; display:flex; align-items:center; justify-content:center; }
.plus-btn { width:56rpx; height:56rpx; display:flex; align-items:center; justify-content:center; margin-left:10rpx; }
.plus-icon-single { font-size:46rpx; color:#00aaff; }
/* 双击放大文本弹窗 */
.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
+7 -7
View File
@@ -215,14 +215,14 @@
<!-- 弹窗底部按钮 --> <!-- 弹窗底部按钮 -->
<view class="modal-footer"> <view class="modal-footer">
<view class="btn-group"> <view class="cbtn-group">
<!-- 联系客服按钮(始终显示) --> <!-- 联系客服按钮(始终显示) -->
<view class="btn btn-kefu" bindtap="lianxiKefu"> <view class="cbtn cbtn-kefu" bindtap="lianxiKefu">
<text>联系客服</text> <text>联系客服</text>
</view> </view>
<!-- 申诉按钮(只有待处罚状态可点击) --> <!-- 申诉按钮(只有待处罚状态可点击) -->
<view <view
class="btn {{ xuanzhongChufa.sqzhuangtai === 0 ? 'btn-shensu' : 'btn-shensu-disabled' }}" class="cbtn {{ xuanzhongChufa.sqzhuangtai === 0 ? 'cbtn-shensu' : 'cbtn-shensu-disabled' }}"
bindtap="{{ xuanzhongChufa.sqzhuangtai === 0 ? 'openShensuModal' : '' }}" bindtap="{{ xuanzhongChufa.sqzhuangtai === 0 ? 'openShensuModal' : '' }}"
> >
<text>申诉</text> <text>申诉</text>
@@ -299,11 +299,11 @@
</view> </view>
</view> </view>
<view class="modal-footer"> <view class="modal-footer">
<view class="btn-group"> <view class="cbtn-group">
<view class="btn btn-quxiao" bindtap="closeShensuModal"> <view class="cbtn cbtn-quxiao" bindtap="closeShensuModal">
<text>取消</text> <text>取消</text>
</view> </view>
<view class="btn btn-queren" bindtap="submitShensu"> <view class="cbtn cbtn-queren" bindtap="submitShensu">
<text>提交申诉</text> <text>提交申诉</text>
</view> </view>
</view> </view>
+8 -8
View File
@@ -625,12 +625,12 @@
flex-shrink: 0; flex-shrink: 0;
} }
.btn-group { .cbtn-group {
display: flex; display: flex;
gap: 20rpx; gap: 20rpx;
} }
.btn { .cbtn {
flex: 1; flex: 1;
height: 90rpx; height: 90rpx;
display: flex; display: flex;
@@ -645,37 +645,37 @@
color: var(--cyber-text) !important; color: var(--cyber-text) !important;
} }
.btn:active { .cbtn:active {
transform: scale(0.98); transform: scale(0.98);
} }
/* 按钮样式 */ /* 按钮样式 */
.btn-kefu { .cbtn-kefu {
background: rgba(0, 243, 255, 0.1); background: rgba(0, 243, 255, 0.1);
border-color: var(--cyber-blue); border-color: var(--cyber-blue);
box-shadow: 0 0 12rpx rgba(0, 243, 255, 0.2); box-shadow: 0 0 12rpx rgba(0, 243, 255, 0.2);
} }
.btn-shensu { .cbtn-shensu {
background: rgba(255, 165, 0, 0.1); background: rgba(255, 165, 0, 0.1);
border-color: #FFA500; border-color: #FFA500;
box-shadow: 0 0 12rpx rgba(255, 165, 0, 0.2); box-shadow: 0 0 12rpx rgba(255, 165, 0, 0.2);
} }
.btn-shensu-disabled { .cbtn-shensu-disabled {
background: rgba(102, 102, 102, 0.1); background: rgba(102, 102, 102, 0.1);
border-color: #666; border-color: #666;
box-shadow: 0 0 12rpx rgba(102, 102, 102, 0.2); box-shadow: 0 0 12rpx rgba(102, 102, 102, 0.2);
opacity: 0.5; opacity: 0.5;
} }
.btn-quxiao { .cbtn-quxiao {
background: rgba(255, 255, 255, 0.07); background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 12rpx rgba(255, 255, 255, 0.1); box-shadow: 0 0 12rpx rgba(255, 255, 255, 0.1);
} }
.btn-queren { .cbtn-queren {
background: rgba(0, 255, 157, 0.1); background: rgba(0, 255, 157, 0.1);
border-color: var(--cyber-green); border-color: var(--cyber-green);
box-shadow: 0 0 12rpx rgba(0, 255, 157, 0.2); box-shadow: 0 0 12rpx rgba(0, 255, 157, 0.2);
+289 -307
View File
@@ -1,307 +1,289 @@
const app = getApp(); const app = getApp();
import request from '../../utils/request.js'; import { createPage, request } from '../../utils/base-page.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js'; import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getOrderStatusText } from '../../utils/api-helper.js';
Page({
data: { Page(createPage({
// 商品类型 data: {
shangpinleixing: [], // 商品类型
xuanzhongLeixingId: null, shangpinleixing: [],
xuanzhongLeixingId: null,
// 订单类型: normal 普通, peihu 陪护
orderType: 'normal', // 订单类型: normal 普通, peihu 陪护
orderType: 'normal',
// 搜索关键字
searchKeyword: '', // 搜索关键字
searchKeyword: '',
// 状态列表(增加全部)
statusList: [ // 状态列表(增加全部)
{ name: '全部', key: 'all', zhuangtaiList: [], color: '#333333' }, statusList: [
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' }, { name: '全部', key: 'all', zhuangtaiList: [], color: '#333333' },
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' }, { name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
{ name: '已退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' }, { name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' }, { name: '已退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' }, { name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' },
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' } { name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' },
], { name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' }
currentStatusKey: 'all', ],
currentStatusKey: 'all',
// 每个状态独立的数据集
dsDingdanShuju: { // 每个状态独立的数据集
all: { list: [], page: 1, hasMore: true, isLoading: false }, dsDingdanShuju: {
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false }, all: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }, jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false }, tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false }, yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false }, tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false } yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
}, jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
},
currentList: [],
hasMore: true, currentList: [],
isLoading: false, hasMore: true,
isLoadingMore: false, isLoading: false,
scrollViewRefreshing: false, isLoadingMore: false,
defaultImg: '/images/default-order.png', scrollViewRefreshing: false,
ossImageUrl: app.globalData.ossImageUrl || '', defaultImg: '/images/default-order.png',
}, ossImageUrl: app.globalData.ossImageUrl || '',
},
onLoad() {
wx.setNavigationBarTitle({ title: '我的接单' }); onLoad() {
this.loadShangpinLeixing(); wx.setNavigationBarTitle({ title: '我的接单' });
this.registerNotificationComponent(); this.loadShangpinLeixing();
}, this.registerNotificationComponent();
},
onShow() {
this.registerNotificationComponent(); onShow() {
if (wx.getStorageSync('uid')) { this.registerNotificationComponent();
reconnectForRole('dashou'); if (wx.getStorageSync('uid')) {
if (app.startImWhenReady) app.startImWhenReady(); reconnectForRole('dashou');
} if (app.startImWhenReady) app.startImWhenReady();
if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) { }
this.loadCurrentStatusOrders(true); if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) {
} this.loadCurrentStatusOrders(true);
}, }
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification'); // 加载商品类型(图片拼接)
if (notificationComp && notificationComp.showNotification) { async loadShangpinLeixing() {
app.globalData.globalNotification = { try {
show: (data) => notificationComp.showNotification(data), const res = await request({
hide: () => notificationComp.hideNotification() url: '/dingdan/dsqdhqddlx',
}; method: 'POST',
} header: { 'content-type': 'application/json' }
}, });
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
// 加载商品类型(图片拼接) let list = res.data.data.list || res.data.data || [];
async loadShangpinLeixing() { if (!Array.isArray(list)) list = [];
try { const oss = this.data.ossImageUrl;
const res = await request({ const processed = list.map(item => ({
url: '/dingdan/dsqdhqddlx', ...item,
method: 'POST', full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http')
header: { 'content-type': 'application/json' } ? oss + item.tupian_url
}); : (item.tupian_url || '/images/default-type.png')
if (res.data && (res.data.code === 200 || res.data.code === 0)) { }));
let list = res.data.data.list || res.data.data || []; this.setData({
if (!Array.isArray(list)) list = []; shangpinleixing: processed,
const oss = this.data.ossImageUrl; xuanzhongLeixingId: processed[0]?.id || null
const processed = list.map(item => ({ });
...item, this.loadCurrentStatusOrders(true);
full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http') }
? oss + item.tupian_url } catch (e) {
: (item.tupian_url || '/images/default-type.png') console.error('加载商品类型失败', e);
})); }
this.setData({ },
shangpinleixing: processed,
xuanzhongLeixingId: processed[0]?.id || null selectLeixing(e) {
}); const id = e.currentTarget.dataset.id;
this.loadCurrentStatusOrders(true); if (id === this.data.xuanzhongLeixingId) return;
} this.setData({ xuanzhongLeixingId: id });
} catch (e) { this.resetCurrentStatusData();
console.error('加载商品类型失败', e); this.loadCurrentStatusOrders(true);
} },
},
switchOrderType(e) {
selectLeixing(e) { const type = e.currentTarget.dataset.type;
const id = e.currentTarget.dataset.id; if (type === this.data.orderType) return;
if (id === this.data.xuanzhongLeixingId) return; this.setData({ orderType: type });
this.setData({ xuanzhongLeixingId: id }); this.resetCurrentStatusData();
this.resetCurrentStatusData(); this.loadCurrentStatusOrders(true);
this.loadCurrentStatusOrders(true); },
},
onSearchInput(e) {
switchOrderType(e) { this.setData({ searchKeyword: e.detail.value });
const type = e.currentTarget.dataset.type; if (this._searchTimer) clearTimeout(this._searchTimer);
if (type === this.data.orderType) return; this._searchTimer = setTimeout(() => {
this.setData({ orderType: type }); this.onSearchConfirm();
this.resetCurrentStatusData(); }, 500);
this.loadCurrentStatusOrders(true); },
},
onSearchConfirm() {
onSearchInput(e) { this.resetCurrentStatusData();
this.setData({ searchKeyword: e.detail.value }); this.loadCurrentStatusOrders(true);
if (this._searchTimer) clearTimeout(this._searchTimer); },
this._searchTimer = setTimeout(() => {
this.onSearchConfirm(); clearSearch() {
}, 500); this.setData({ searchKeyword: '' });
}, this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
onSearchConfirm() { },
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true); switchStatus(e) {
}, const key = e.currentTarget.dataset.key;
if (key === this.data.currentStatusKey) return;
clearSearch() { this.setData({ currentStatusKey: key });
this.setData({ searchKeyword: '' }); const tabData = this.data.dsDingdanShuju[key];
this.resetCurrentStatusData(); if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) {
this.loadCurrentStatusOrders(true); this.loadCurrentStatusOrders(true);
}, } else {
this.refreshCurrentListView();
switchStatus(e) { }
const key = e.currentTarget.dataset.key; },
if (key === this.data.currentStatusKey) return;
this.setData({ currentStatusKey: key }); // 加载订单(核心)
const tabData = this.data.dsDingdanShuju[key]; async loadCurrentStatusOrders(isRefresh = false) {
if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) { const key = this.data.currentStatusKey;
this.loadCurrentStatusOrders(true); const tabData = this.data.dsDingdanShuju[key];
} else { if (tabData.isLoading) return;
this.refreshCurrentListView(); const page = isRefresh ? 1 : tabData.page;
}
}, this.setData({
[`dsDingdanShuju.${key}.isLoading`]: true,
// 加载订单(核心) isLoading: true,
async loadCurrentStatusOrders(isRefresh = false) { isLoadingMore: !isRefresh
const key = this.data.currentStatusKey; });
const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading) return; try {
const page = isRefresh ? 1 : tabData.page; const apiUrl = this.data.orderType === 'peihu'
? '/dingdan/phddlbhq'
this.setData({ : '/dingdan/dshqdingdan';
[`dsDingdanShuju.${key}.isLoading`]: true,
isLoading: true, const statusItem = this.data.statusList.find(s => s.key === key);
isLoadingMore: !isRefresh const zhuangtaiList = statusItem.zhuangtaiList.length > 0 ? statusItem.zhuangtaiList : undefined;
}); const params = {
zhuangtai_list: zhuangtaiList,
try { page: page,
const apiUrl = this.data.orderType === 'peihu' page_size: 5,
? '/dingdan/phddlbhq' leixing_id: this.data.xuanzhongLeixingId,
: '/dingdan/dshqdingdan'; keyword: this.data.searchKeyword || undefined
};
const statusItem = this.data.statusList.find(s => s.key === key);
const zhuangtaiList = statusItem.zhuangtaiList.length > 0 ? statusItem.zhuangtaiList : undefined; const res = await request({
const params = { url: apiUrl,
zhuangtai_list: zhuangtaiList, method: 'POST',
page: page, data: params,
page_size: 5, header: { 'content-type': 'application/json' }
leixing_id: this.data.xuanzhongLeixingId, });
keyword: this.data.searchKeyword || undefined
}; const code = res.data.code;
if (code === 200 || code === 0) {
const res = await request({ const newList = res.data.data.list || [];
url: apiUrl, const hasMore = res.data.data.has_more || false;
method: 'POST',
data: params, // ✅ 图片URL拼接(核心)
header: { 'content-type': 'application/json' } const processed = newList.map(item => ({
}); ...item,
zhuangtaiZh: getOrderStatusText(item.zhuangtai),
const code = res.data.code; zhuangtaiColor: statusItem.color,
if (code === 200 || code === 0) { tupian: item.tupian
const newList = res.data.data.list || []; ? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian)
const hasMore = res.data.data.has_more || false; : this.data.defaultImg
}));
// ✅ 图片URL拼接(核心)
const processed = newList.map(item => ({ const currentList = this.data.dsDingdanShuju[key].list;
...item, const updatedList = isRefresh ? processed : [...currentList, ...processed];
zhuangtaiZh: this.getZhuangtaiZh(item.zhuangtai),
zhuangtaiColor: statusItem.color, this.setData({
tupian: item.tupian [`dsDingdanShuju.${key}.list`]: updatedList,
? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian) [`dsDingdanShuju.${key}.page`]: page,
: this.data.defaultImg [`dsDingdanShuju.${key}.hasMore`]: hasMore,
})); [`dsDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
const currentList = this.data.dsDingdanShuju[key].list; isLoadingMore: false,
const updatedList = isRefresh ? processed : [...currentList, ...processed]; scrollViewRefreshing: false
});
this.setData({ this.refreshCurrentListView();
[`dsDingdanShuju.${key}.list`]: updatedList, } else {
[`dsDingdanShuju.${key}.page`]: page, throw new Error(res.data.msg || '加载失败');
[`dsDingdanShuju.${key}.hasMore`]: hasMore, }
[`dsDingdanShuju.${key}.isLoading`]: false, } catch (err) {
isLoading: false, console.error('加载订单失败', err);
isLoadingMore: false, wx.showToast({ title: err.message || '加载失败', icon: 'none' });
scrollViewRefreshing: false this.setData({
}); [`dsDingdanShuju.${key}.isLoading`]: false,
this.refreshCurrentListView(); isLoading: false,
} else { isLoadingMore: false,
throw new Error(res.data.msg || '加载失败'); scrollViewRefreshing: false
} });
} catch (err) { this.refreshCurrentListView();
console.error('加载订单失败', err); }
wx.showToast({ title: err.message || '加载失败', icon: 'none' }); },
this.setData({
[`dsDingdanShuju.${key}.isLoading`]: false, refreshCurrentListView() {
isLoading: false, const key = this.data.currentStatusKey;
isLoadingMore: false, const tabData = this.data.dsDingdanShuju[key];
scrollViewRefreshing: false this.setData({
}); currentList: tabData.list,
this.refreshCurrentListView(); hasMore: tabData.hasMore,
} isLoading: tabData.isLoading
}, });
},
refreshCurrentListView() {
const key = this.data.currentStatusKey; resetCurrentStatusData() {
const tabData = this.data.dsDingdanShuju[key]; const key = this.data.currentStatusKey;
this.setData({ this.setData({
currentList: tabData.list, [`dsDingdanShuju.${key}.list`]: [],
hasMore: tabData.hasMore, [`dsDingdanShuju.${key}.page`]: 1,
isLoading: tabData.isLoading [`dsDingdanShuju.${key}.hasMore`]: true,
}); [`dsDingdanShuju.${key}.isLoading`]: false
}, });
this.refreshCurrentListView();
resetCurrentStatusData() { },
const key = this.data.currentStatusKey;
this.setData({ // 下拉刷新(由 scroll-view 触发)
[`dsDingdanShuju.${key}.list`]: [], onPullDownRefresh() {
[`dsDingdanShuju.${key}.page`]: 1, if (this.data.isLoading) {
[`dsDingdanShuju.${key}.hasMore`]: true, this.setData({ scrollViewRefreshing: false });
[`dsDingdanShuju.${key}.isLoading`]: false return;
}); }
this.refreshCurrentListView(); this.setData({ scrollViewRefreshing: true });
}, this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
// 下拉刷新(由 scroll-view 触发) },
onPullDownRefresh() {
if (this.data.isLoading) { // 上拉加载更多(由 scroll-view 触发)
this.setData({ scrollViewRefreshing: false }); onReachBottom() {
return; const key = this.data.currentStatusKey;
} const tabData = this.data.dsDingdanShuju[key];
this.setData({ scrollViewRefreshing: true }); if (tabData.isLoading || !tabData.hasMore) return;
this.resetCurrentStatusData(); this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(true); this.loadCurrentStatusOrders(false);
}, },
// 上拉加载更多(由 scroll-view 触发) // ✅ 手动点击"加载更多"按钮
onReachBottom() { onLoadMoreTap() {
const key = this.data.currentStatusKey; const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key]; const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return; if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 }); this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false); this.loadCurrentStatusOrders(false);
}, },
// ✅ 手动点击“加载更多”按钮 goToDsDingdanXiangqing(e) {
onLoadMoreTap() { const item = e.currentTarget.dataset.item;
const key = this.data.currentStatusKey; if (!item) return;
const tabData = this.data.dsDingdanShuju[key]; const dataStr = encodeURIComponent(JSON.stringify(item));
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 }); if (this.data.orderType === 'peihu') {
this.loadCurrentStatusOrders(false); wx.navigateTo({
}, url: `/pages/escort-orders/escort-orders?dingdanData=${dataStr}`
});
getZhuangtaiZh(zhuangtai) { } else {
const map = { wx.navigateTo({
1: '已下单', 2: '进行中', 3: '已完成', url: `/pages/fighter-order-detail/fighter-order-detail?dingdanData=${dataStr}`
4: '退款中', 5: '已退款', 6: '退款失败', });
7: '指定中', 8: '结算中' }
}; },
return map[zhuangtai] || '未知状态';
}, onImageError() {}
}))
goToDsDingdanXiangqing(e) {
const item = e.currentTarget.dataset.item;
if (!item) return;
const dataStr = encodeURIComponent(JSON.stringify(item));
if (this.data.orderType === 'peihu') {
wx.navigateTo({
url: `/pages/escort-orders/escort-orders?dingdanData=${dataStr}`
});
} else {
wx.navigateTo({
url: `/pages/fighter-order-detail/fighter-order-detail?dingdanData=${dataStr}`
});
}
},
onImageError() {}
});
+1 -1
View File
@@ -503,7 +503,7 @@
<!-- ======================= --> <!-- ======================= -->
<view class="tech-footer"> <view class="tech-footer">
<view class="footer-line"></view> <view class="footer-line"></view>
<view class="footer-text">星阙网络 © 7891 科技</view> <view class="footer-text">星阙网络 © 科技</view>
</view> </view>
</view> </view>
+37 -159
View File
@@ -1,26 +1,15 @@
// pages/dashouzhongxin/dashouzhongxin.js // pages/dashouzhongxin/dashouzhongxin.js
import request from '../../utils/request.js' import { createPage, request, isRoleStatusActive, isCenterPageActive, syncRoleStatuses, syncProfileFields, syncGroupFields, ensureRoleOnCenterPage, clearCacheAndEnterNormal, lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole, parseSceneOptions } from '../../utils/base-page.js'
import popupService from '../../services/popupService.js' import { resolveAvatarUrl } from '../../utils/avatar.js'
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
syncGroupFields,
ensureRoleOnCenterPage,
clearCacheAndEnterNormal,
} from '../../utils/role-tab-bar.js'
import { lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole } from '../../utils/primary-role.js'
import { resolveAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js'
import { openPrivateChat, buildInviterPeerId } from '../../utils/im-user.js' import { openPrivateChat, buildInviterPeerId } from '../../utils/im-user.js'
const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC' const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC'
Page({ Page(createPage({
data: { data: {
// 图片路径对象 // 图片路径对象
imgUrls: {}, imgUrls: {},
isDashou: false, isDashou: false,
isGuanshi: false, isGuanshi: false,
isZuzhang: false, isZuzhang: false,
@@ -31,7 +20,6 @@ Page({
assetBreakdown: [], assetBreakdown: [],
pendingAuthList: [], pendingAuthList: [],
showAuthSection: false, showAuthSection: false,
isLoading: false,
inviteCode: '', inviteCode: '',
guanshiInviteCode: '', guanshiInviteCode: '',
zuzhangInviteCode: '', zuzhangInviteCode: '',
@@ -48,8 +36,6 @@ Page({
clumber: [], clumber: [],
chengjiaoliang: '', chengjiaoliang: '',
zaixianZhuangtai: 0, zaixianZhuangtai: 0,
lastRefreshTime: 0,
canRefresh: true,
isAutoRegistering: false, isAutoRegistering: false,
inviterCache: null, inviterCache: null,
zuzhangInviterCache: null, zuzhangInviterCache: null,
@@ -165,17 +151,10 @@ Page({
}); });
this.checkRoleStatuses(); this.checkRoleStatuses();
const registerType = options.registerType || 'dashou'; const parsed = parseSceneOptions(options);
if (options.scene) { const registerType = parsed.registerType || 'dashou';
try { const { inviteCode } = parsed;
const scene = decodeURIComponent(options.scene);
options.inviteCode = scene;
} catch (e) {
console.error('scene解码失败', e);
}
}
this.checkDashouStatus(); this.checkDashouStatus();
const { inviteCode } = options || {};
if (inviteCode) { if (inviteCode) {
if (registerType === 'guanshi') { if (registerType === 'guanshi') {
this.setData({ guanshiInviteCode: inviteCode }); this.setData({ guanshiInviteCode: inviteCode });
@@ -189,26 +168,7 @@ Page({
}, 500); }, 500);
} }
} }
// 注意:不再重复声明 app this.checkColdStartPopup('dashouduan');
if (!app.globalData.hasShownPopupOnColdStart) {
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => {
popupService.checkAndShow(this, 'dashouduan');
}, 300);
}
},
onReady() {
if (isCenterPageActive(this, 'isDashou', 'dashoustatus')) {
ensureRoleOnCenterPage(this, 'dashou');
}
},
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
}, },
onShow() { onShow() {
@@ -324,60 +284,36 @@ Page({
}); });
}, },
_isApiSuccess(body) {
if (!body) return false;
const code = Number(body.code);
return code === 200 || code === 0;
},
_getApiMsg(body) {
if (!body) return '';
return body.msg || body.message || '';
},
_extractUserData(body) {
if (!body) return {};
if (body.data && typeof body.data === 'object' && !Array.isArray(body.data)) {
return body.data;
}
const userData = { ...body };
delete userData.code;
delete userData.msg;
delete userData.message;
return userData;
},
async refreshAllInfo(showToast = true) { async refreshAllInfo(showToast = true) {
const now = Date.now(); this.throttledRefresh(async () => {
if (!this.data.canRefresh || (now - this.data.lastRefreshTime < 3000)) return; this.setData({ isLoading: true });
this.setData({ lastRefreshTime: now, canRefresh: false, isLoading: true }); try {
try { this.checkRoleStatuses();
this.checkRoleStatuses(); const results = await Promise.allSettled([
const results = await Promise.allSettled([ this._fetchDashouInfoSilent(),
this._fetchDashouInfoSilent(), this.fetchChenghaoList(),
this.fetchChenghaoList(), this._fetchGuanshiInfoSilent(),
this._fetchGuanshiInfoSilent(), this.fetchGuanshiChenghaoList(),
this.fetchGuanshiChenghaoList(), this._fetchZuzhangInfoSilent(),
this._fetchZuzhangInfoSilent(), this._fetchKaoheguanInfoSilent(),
this._fetchKaoheguanInfoSilent(), ]);
]); this.checkRoleStatuses();
this.checkRoleStatuses(); const dashouOk = results[0].status === 'fulfilled';
const dashouOk = results[0].status === 'fulfilled'; if (showToast) {
if (showToast) { wx.showToast({
wx.showToast({ title: dashouOk ? '刷新成功' : '刷新失败',
title: dashouOk ? '刷新成功' : '刷新失败', icon: dashouOk ? 'success' : 'none',
icon: dashouOk ? 'success' : 'none', duration: 1500,
duration: 1500, });
}); }
} catch (e) {
if (showToast) {
wx.showToast({ title: '刷新失败', icon: 'none' });
}
} finally {
this.setData({ isLoading: false });
} }
} catch (e) { }, 3000);
if (showToast) {
wx.showToast({ title: '刷新失败', icon: 'none' });
}
} finally {
this.setData({ isLoading: false });
setTimeout(() => this.setData({ canRefresh: true }), 3000);
}
}, },
refreshDashouInfo() { refreshDashouInfo() {
@@ -875,17 +811,6 @@ Page({
}); });
}, },
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
checkDashouStatus() { checkDashouStatus() {
try { try {
const dashoustatus = wx.getStorageSync('dashoustatus'); const dashoustatus = wx.getStorageSync('dashoustatus');
@@ -905,11 +830,6 @@ Page({
} }
}, },
loadAvatar() {
const avatarUrl = resolveAvatarFromStorage(getApp());
this.setData({ avatarUrl });
},
checkGlobalData() { checkGlobalData() {
const app = getApp(); const app = getApp();
const globalData = app.globalData || {}; const globalData = app.globalData || {};
@@ -1058,19 +978,6 @@ Page({
ensureRoleOnCenterPage(this, 'dashou'); ensureRoleOnCenterPage(this, 'dashou');
}, },
copyUid() {
const { uid } = this.data;
if (!uid) {
wx.showToast({ title: 'UID不存在', icon: 'none', duration: 1500 });
return;
}
wx.setClipboardData({
data: uid,
success: () => wx.showToast({ title: '已复制UID', icon: 'success', duration: 1500 }),
fail: () => wx.showToast({ title: '复制失败', icon: 'error', duration: 1500 })
});
},
previewAvatar() { previewAvatar() {
const { avatarUrl } = this.data; const { avatarUrl } = this.data;
if (avatarUrl) wx.previewImage({ urls: [avatarUrl] }); if (avatarUrl) wx.previewImage({ urls: [avatarUrl] });
@@ -1079,19 +986,6 @@ Page({
goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }) }, goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }) },
goToTotalWithdraw() { this.goToWithdraw(); }, goToTotalWithdraw() { this.goToWithdraw(); },
goToKefu() {
const app = getApp();
const { link, enterpriseId } = app.globalData.kefuConfig || {};
if (!link || !enterpriseId) {
wx.showToast({ title: '客服配置未加载', icon: 'none' });
return;
}
wx.openCustomerServiceChat({
extInfo: { url: link },
corpId: enterpriseId,
});
},
goToAuth(e) { goToAuth(e) {
const type = e.currentTarget.dataset.type; const type = e.currentTarget.dataset.type;
if (!type || !['dashou', 'shangjia', 'guanshi', 'zuzhang'].includes(type)) return; if (!type || !['dashou', 'shangjia', 'guanshi', 'zuzhang'].includes(type)) return;
@@ -1171,20 +1065,4 @@ Page({
}); });
}, },
clearCache() { }, { roleConfig: { role: 'dashou', statusKey: 'dashoustatus', dataFlag: 'isDashou', pageId: 'dashouduan', apiUrl: '/yonghu/dashouxinxi' } }))
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: (r) => {
if (r.confirm) clearCacheAndEnterNormal(this);
},
});
},
switchToNormal() {
lockPrimaryRole('normal');
wx.reLaunch({ url: '/pages/mine/mine' });
},
})
+1 -1
View File
@@ -193,7 +193,7 @@
> >
<view class="rank-cell-inner"> <view class="rank-cell-inner">
<image class="rank-bg-icon" src="{{item.icon}}" mode="aspectFit" /> <image class="rank-bg-icon" src="{{item.icon}}" mode="aspectFit" />
<view class="rank-content"> <view class="rank-cell-content">
<image class="rank-main-icon" src="{{item.icon}}" mode="aspectFit" /> <image class="rank-main-icon" src="{{item.icon}}" mode="aspectFit" />
<text class="rank-cell-name">{{item.name}}</text> <text class="rank-cell-name">{{item.name}}</text>
<text class="rank-cell-sub">{{item.sub}}</text> <text class="rank-cell-sub">{{item.sub}}</text>
+12 -10
View File
@@ -469,7 +469,7 @@ page {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 20rpx; padding: 0 24rpx;
box-sizing: border-box; box-sizing: border-box;
border-radius: 16rpx; border-radius: 16rpx;
overflow: hidden; overflow: hidden;
@@ -477,25 +477,27 @@ page {
.rank-bg-icon { .rank-bg-icon {
position: absolute; position: absolute;
width: 120rpx; width: 100rpx;
height: 120rpx; height: 100rpx;
right: -12rpx; right: 8rpx;
top: 50%; top: 50%;
transform: translateY(-50%) rotate(-10deg); transform: translateY(-50%) rotate(-10deg);
opacity: 0.15; opacity: 0.12;
z-index: 0; z-index: 0;
pointer-events: none; pointer-events: none;
} }
.rank-content { .rank-cell-content {
position: relative; position: relative;
z-index: 1; z-index: 1;
display: flex;
flex-direction: column;
} }
.rank-main-icon { .rank-main-icon {
width: 56rpx; width: 44rpx;
height: 56rpx; height: 44rpx;
margin-bottom: 6rpx; margin-bottom: 8rpx;
} }
.rank-shape-right .rank-cell-inner { .rank-shape-right .rank-cell-inner {
@@ -509,7 +511,7 @@ page {
transform: translateY(-50%) rotate(10deg); transform: translateY(-50%) rotate(10deg);
} }
.rank-shape-right .rank-content { .rank-shape-right .rank-cell-content {
text-align: right; text-align: right;
} }
+46 -46
View File
@@ -1,92 +1,92 @@
<view class="chat-page"> <view class="chat-page">
<scroll-view class="msg-list" scroll-y="true" <scroll-view class="chat-msg-list" scroll-y="true"
scroll-into-view="{{scrollToView}}" scroll-into-view="{{scrollToView}}"
refresher-enabled="{{true}}" refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}" refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh" 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) + 20rpx);">
<view class="loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view> <view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
<block wx:for="{{messages}}" wx:key="messageId"> <block wx:for="{{messages}}" wx:key="messageId">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction"> <view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view> <view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}"> <view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<!-- 对方头像及昵称 --> <!-- 对方头像及昵称 -->
<view wx:if="{{item.senderId !== currentUser.id}}" class="sender-info"> <view wx:if="{{item.senderId !== currentUser.id}}" class="sender-info">
<image class="avatar" src="{{item.senderData.avatar || '/images/default-avatar.png'}}" mode="aspectFill" /> <image class="chat-avatar" src="{{item.senderData.avatar || '/images/default-avatar.png'}}" mode="aspectFill" />
<text class="sender-name">{{item.senderData.name || item.senderId}}</text> <text class="sender-name">{{item.senderData.name || item.senderId}}</text>
</view> </view>
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}" <view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}" data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}" data-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}" bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}"> data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text> <text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" /> <image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="order-bubble" data-payload="{{item.payload}}" bindtap="viewOrderDetail"> <view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble" data-payload="{{item.payload}}" bindtap="viewOrderDetail">
<text class="order-label">[订单]</text> <text class="chat-order-label">[订单]</text>
<text class="order-info">ID: {{item.payload.orderId}}</text> <text class="chat-order-info">ID: {{item.payload.orderId}}</text>
<text class="order-info">内容: {{item.payload.jieshao}}</text> <text class="chat-order-info">内容: {{item.payload.jieshao}}</text>
<text class="order-info">金额: ¥{{item.payload.jine}}</text> <text class="chat-order-info">金额: ¥{{item.payload.jine}}</text>
<view class="order-detail-link">点击查看详情</view> <view class="chat-order-info" style="color:#007aff;margin-top:8rpx">点击查看详情</view>
</view> </view>
</view> </view>
<!-- 自己的头像 --> <!-- 自己的头像 -->
<image wx:if="{{item.senderId === currentUser.id}}" class="avatar" src="{{currentUser.avatar}}" mode="aspectFill" /> <image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{currentUser.avatar}}" mode="aspectFill" />
</view> </view>
</view> </view>
</block> </block>
<view id="msg-bottom" style="height:20rpx;"></view> <view id="msg-bottom" style="height:20rpx;"></view>
</scroll-view> </scroll-view>
<view class="input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));"> <view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel"> <view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="plus-grid"> <view class="chat-plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus"> <view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text> <text class="chat-plus-icon">😀</text>
<text class="plus-label">表情</text> <text class="chat-plus-label">表情</text>
</view> </view>
<view class="plus-item" catchtap="chooseImage"> <view class="chat-plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text> <text class="chat-plus-icon">📷</text>
<text class="plus-label">图片</text> <text class="chat-plus-label">图片</text>
</view> </view>
<view class="plus-item" catchtap="openOrderSender"> <view class="chat-plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text> <text class="chat-plus-icon">📋</text>
<text class="plus-label">订单</text> <text class="chat-plus-label">订单</text>
</view> </view>
</view> </view>
</view> </view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view> <view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}"> <view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll"> <scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="emoji-grid"> <view class="chat-emoji-grid">
<block wx:for="{{emojiList}}" wx:key="*this"> <block wx:for="{{emojiList}}" wx:key="*this">
<view class="emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view> <view class="chat-emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
</block> </block>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
<view class="pending-image" wx:if="{{pendingImage}}"> <view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" /> <image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view> <view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view> </view>
<view class="write-row"> <view class="chat-write-row">
<input class="text-input" placeholder="发送消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/> <input class="chat-text-input" placeholder="发送消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view> <view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel"> <view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single">➕</text> <text class="chat-plus-icon-single">➕</text>
</view> </view>
</view> </view>
</view> </view>
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail"> <view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop"> <view class="chat-detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text> <text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button> <button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view> </view>
</view> </view>
<order-sender visible="{{showOrderSender}}" bind:send="onSendOrder" bind:close="closeOrderSender" /> <order-sender visible="{{showOrderSender}}" bind:send="onSendOrder" bind:close="closeOrderSender" />
</view> </view>
+3 -94
View File
@@ -1,97 +1,6 @@
.chat-page { width:100%; height:100vh; background:#f5f5f5; } /* 使用全局 chat-* 样式 */
.msg-list { /* 发送者昵称样式(群聊特有) */
position: absolute;
left:0; right:0;
padding: 0 20rpx;
box-sizing: border-box;
overflow-y: auto;
}
.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
.msg-wrapper { margin-bottom:12rpx; }
.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
.right { justify-content:flex-end; }
.left { justify-content:flex-start; }
.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
.bubble-left { background:#fff; border-top-left-radius:4rpx; }
.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
.msg-text { color:inherit; }
.msg-image { max-width:240rpx; border-radius:10rpx; }
.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; }
.order-info { font-size:24rpx; color:#333; margin:6rpx 0; }
.order-detail-link { color:#007aff; font-size:24rpx; margin-top:8rpx; }
.input-area {
position: fixed;
left:0; right:0;
background:#f7f7f7;
border-top:1rpx solid #ddd;
z-index:10;
}
.plus-panel {
position: absolute;
bottom:100%; left:0; right:0;
background:#fff;
border-top:1rpx solid #e0e0e0;
padding: 20rpx;
transform: translateY(100%);
opacity:0;
transition: transform 0.3s, opacity 0.3s;
pointer-events:none;
z-index:9;
}
.plus-panel.show { transform: translateY(0); opacity:1; pointer-events:auto; }
.plus-grid { display: flex; justify-content: space-around; }
.plus-item { display: flex; flex-direction: column; align-items: center; width: 120rpx; }
.plus-icon { font-size: 60rpx; }
.plus-label { font-size: 24rpx; color: #666; margin-top: 8rpx; }
.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
.emoji-panel {
position: absolute; bottom:100%; left:0; right:0;
background:#fff; border-top:1rpx solid #e0e0e0;
height:380rpx;
transform: translateY(100%); opacity:0; transition:0.3s;
pointer-events:none; z-index:9;
}
.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
.emoji-scroll { height:100%; }
.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
.text-input {
flex:1; height:76rpx; background:#fff; border-radius:12rpx;
padding:0 20rpx; font-size:30rpx; margin-right:12rpx;
}
.send-btn {
width:120rpx; height:76rpx; background:#00aaff; color:#fff;
font-size:28rpx; font-weight:500; border-radius:12rpx;
display:flex; align-items:center; justify-content:center;
}
.plus-btn {
width:56rpx; height:56rpx;
display:flex; align-items:center; justify-content:center;
margin-left: 10rpx;
}
.plus-icon-single { font-size:46rpx; color:#00aaff; }
.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
/* 发送者昵称样式 */
.sender-info { .sender-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -106,4 +15,4 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
+20 -26
View File
@@ -4,11 +4,12 @@ import { ensureLogin } from '../../utils/login';
import { fetchShopGoods, bindShop } from '../../utils/shop'; import { fetchShopGoods, bindShop } from '../../utils/shop';
import { backfillUserProfileCache, getSessionToken } from '../../utils/role-tab-bar'; import { backfillUserProfileCache, getSessionToken } from '../../utils/role-tab-bar';
import PopupService from '../../services/popupService.js'; import PopupService from '../../services/popupService.js';
import { createPage } from '../../utils/base-page.js';
// 扫码场景参数名 // 扫码场景参数名
const SCENE_PARAM = 'scene'; const SCENE_PARAM = 'scene';
Page({ Page(createPage({
data: { data: {
ossImageUrl: '', ossImageUrl: '',
lunbozhanwei: '', lunbozhanwei: '',
@@ -26,7 +27,7 @@ Page({
isLoading: false, isLoading: false,
isRefreshing: false, isRefreshing: false,
hasInitialized: false, hasInitialized: false,
currentTime: '' currentTime: ''
}, },
@@ -43,7 +44,7 @@ Page({
console.error('scene解码失败', e); console.error('scene解码失败', e);
} }
} }
// 等待全局配置加载完成 // 等待全局配置加载完成
this.waitForConfigAndInit(); this.waitForConfigAndInit();
// 已登录时才检查弹窗 // 已登录时才检查弹窗
@@ -107,16 +108,6 @@ Page({
} }
}, },
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
getCurrentTime() { getCurrentTime() {
const now = new Date(); const now = new Date();
const year = now.getFullYear(); const year = now.getFullYear();
@@ -234,12 +225,12 @@ Page({
*/ */
filterShangpinData() { filterShangpinData() {
const { selectedLeixingId, shangpinzhuanqu, shangpinliebiao } = this.data; const { selectedLeixingId, shangpinzhuanqu, shangpinliebiao } = this.data;
if (!selectedLeixingId || !shangpinzhuanqu || !shangpinliebiao) { if (!selectedLeixingId || !shangpinzhuanqu || !shangpinliebiao) {
this.setData({ filteredData: [] }); this.setData({ filteredData: [] });
return; return;
} }
const zhuanquByLeixing = new Map(); const zhuanquByLeixing = new Map();
shangpinzhuanqu.forEach(zhuanqu => { shangpinzhuanqu.forEach(zhuanqu => {
if (zhuanqu.leixing_id === selectedLeixingId) { if (zhuanqu.leixing_id === selectedLeixingId) {
@@ -249,7 +240,7 @@ Page({
zhuanquByLeixing.get(selectedLeixingId).push(zhuanqu); zhuanquByLeixing.get(selectedLeixingId).push(zhuanqu);
} }
}); });
const shangpinByZhuanqu = new Map(); const shangpinByZhuanqu = new Map();
shangpinliebiao.forEach(shangpin => { shangpinliebiao.forEach(shangpin => {
if (shangpin.leixing_id === selectedLeixingId && shangpin.zhuanqu_id) { if (shangpin.leixing_id === selectedLeixingId && shangpin.zhuanqu_id) {
@@ -257,7 +248,7 @@ Page({
if (!shangpinByZhuanqu.has(zhuanquId)) { if (!shangpinByZhuanqu.has(zhuanquId)) {
shangpinByZhuanqu.set(zhuanquId, []); shangpinByZhuanqu.set(zhuanquId, []);
} }
const jiage = parseFloat(shangpin.jiage) || 0; const jiage = parseFloat(shangpin.jiage) || 0;
const priceInteger = Math.floor(jiage); const priceInteger = Math.floor(jiage);
let priceDecimal = '00'; let priceDecimal = '00';
@@ -265,7 +256,7 @@ Page({
if (decimalPart > 0) { if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1]; priceDecimal = decimalPart.toString().split('.')[1];
} }
shangpinByZhuanqu.get(zhuanquId).push({ shangpinByZhuanqu.get(zhuanquId).push({
id: shangpin.id, id: shangpin.id,
biaoqian: shangpin.biaoqian, biaoqian: shangpin.biaoqian,
@@ -278,10 +269,10 @@ Page({
}); });
} }
}); });
const filteredData = []; const filteredData = [];
const currentZhuanquList = zhuanquByLeixing.get(selectedLeixingId) || []; const currentZhuanquList = zhuanquByLeixing.get(selectedLeixingId) || [];
currentZhuanquList.forEach(zhuanqu => { currentZhuanquList.forEach(zhuanqu => {
const shangpinList = shangpinByZhuanqu.get(zhuanqu.id) || []; const shangpinList = shangpinByZhuanqu.get(zhuanqu.id) || [];
if (shangpinList.length > 0) { if (shangpinList.length > 0) {
@@ -295,14 +286,14 @@ Page({
}); });
} }
}); });
const sortedFilteredData = filteredData.sort((a, b) => { const sortedFilteredData = filteredData.sort((a, b) => {
const paixuA = a.zhuanqu.paixu || 0; const paixuA = a.zhuanqu.paixu || 0;
const paixuB = b.zhuanqu.paixu || 0; const paixuB = b.zhuanqu.paixu || 0;
if (paixuB !== paixuA) return paixuB - paixuA; if (paixuB !== paixuA) return paixuB - paixuA;
return a.zhuanqu.id - b.zhuanqu.id; return a.zhuanqu.id - b.zhuanqu.id;
}); });
this.setData({ filteredData: sortedFilteredData }); this.setData({ filteredData: sortedFilteredData });
}, },
@@ -315,18 +306,21 @@ Page({
}, },
showGonggaoDetail() { showGonggaoDetail() {
this.setData({ this.setData({
showGonggaoModal: true, showGonggaoModal: true,
_modalLeaving: false,
gonggaoAnim: false, gonggaoAnim: false,
currentTime: this.getCurrentTime() currentTime: this.getCurrentTime()
}); });
}, },
hideGonggaoDetail() { hideGonggaoDetail() {
this.setData({ this.setData({
showGonggaoModal: false, showGonggaoModal: false,
gonggaoAnim: true _modalLeaving: true,
gonggaoAnim: true
}); });
setTimeout(() => this.setData({ _modalLeaving: false }), 300);
}, },
/** /**
@@ -388,4 +382,4 @@ Page({
onUnload() { onUnload() {
this.setData({ gonggaoAnim: false }); this.setData({ gonggaoAnim: false });
} }
}); }))
+3 -3
View File
@@ -146,9 +146,9 @@
</view> </view>
<!-- 公告详情弹窗 --> <!-- 公告详情弹窗 -->
<view class="gonggao-modal" wx:if="{{showGonggaoModal}}"> <view class="gonggao-modal {{showGonggaoModal ? 'gonggao-modal--show' : 'gonggao-modal--hide'}}" wx:if="{{showGonggaoModal || _modalLeaving}}">
<view class="modal-mask" bindtap="hideGonggaoDetail"></view> <view class="modal-mask {{showGonggaoModal ? 'modal-mask--in' : 'modal-mask--out'}}" bindtap="hideGonggaoDetail"></view>
<view class="modal-content"> <view class="modal-content {{showGonggaoModal ? 'modal-content--in' : 'modal-content--out'}}">
<view class="modal-header"> <view class="modal-header">
<text class="modal-title">公告详情</text> <text class="modal-title">公告详情</text>
<view class="modal-close" bindtap="hideGonggaoDetail">×</view> <view class="modal-close" bindtap="hideGonggaoDetail">×</view>
+79
View File
@@ -411,3 +411,82 @@
align-items: center; align-items: center;
} }
.gonggao-modal .modal-mask {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 0;
transition: opacity 0.25s ease;
}
.gonggao-modal .modal-mask--in {
opacity: 1;
}
.gonggao-modal .modal-mask--out {
opacity: 0;
}
.gonggao-modal .modal-content {
position: relative;
z-index: 1;
width: 620rpx;
max-height: 70vh;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
transition: transform 0.25s ease, opacity 0.25s ease;
}
.gonggao-modal .modal-content--in {
transform: scale(1);
opacity: 1;
}
.gonggao-modal .modal-content--out {
transform: scale(0.9);
opacity: 0;
}
.gonggao-modal .modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 32rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.gonggao-modal .modal-title {
font-size: 30rpx;
font-weight: 700;
color: #1a1a1a;
}
.gonggao-modal .modal-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
color: #999;
border-radius: 50%;
background: #f5f5f5;
line-height: 1;
}
.gonggao-modal .modal-body {
flex: 1;
padding: 28rpx 32rpx 36rpx;
max-height: 55vh;
}
.gonggao-modal .modal-text {
font-size: 28rpx;
color: #444;
line-height: 1.8;
word-break: break-all;
}
+135 -225
View File
@@ -1,225 +1,135 @@
// pages/leader/leader.js // pages/leader/leader.js
import request from '../../utils/request.js' import { createPage, request, syncRoleStatuses, syncProfileFields, ensureRoleOnCenterPage, isCenterPageActive, parseSceneOptions } from '../../utils/base-page.js'
import {
isRoleStatusActive, Page(createPage({
isCenterPageActive, data: {
syncRoleStatuses, isZuzhang: false,
syncProfileFields,
ensureRoleOnCenterPage, // 邀请码
} from '../../utils/role-tab-bar.js' inviteCode: '',
Page({ // 用户信息
data: { uid: '',
// 页面状态 nicheng: '',
isZuzhang: false, avatarUrl: '',
isLoading: false,
// 组长信息
// 邀请码 ketixian: '0.00',
inviteCode: '', guanshiCount: 0,
fenhongZonge: '0.00',
// 用户信息 },
uid: '',
nicheng: '', onLoad(options) {
avatarUrl: '', const parsed = parseSceneOptions(options)
if (parsed.inviteCode) {
// 组长信息 this.setData({ inviteCode: parsed.inviteCode })
ketixian: '0.00', }
guanshiCount: 0, wx.redirectTo({ url: '/pages/fighter/fighter' })
fenhongZonge: '0.00', return
}, // 以下保留供恢复参考
this.checkLoginStatus()
onLoad() { },
wx.redirectTo({ url: '/pages/fighter/fighter' });
return; onShow() {
// 以下保留供恢复参考 this.registerNotificationComponent()
this.checkLoginStatus() this.checkColdStartPopup('zuzhangduan')
}, if (isCenterPageActive(this, 'isZuzhang', 'zuzhangstatus')) {
if (!this.data.isZuzhang) this.setData({ isZuzhang: true })
onReady() { ensureRoleOnCenterPage(this, 'zuzhang')
if (isCenterPageActive(this, 'isZuzhang', 'zuzhangstatus')) { this.getZuzhangInfo()
ensureRoleOnCenterPage(this, 'zuzhang') }
} },
},
// 检查登录状态
onShow() { checkLoginStatus() {
this.registerNotificationComponent() const token = wx.getStorageSync('token')
if (isCenterPageActive(this, 'isZuzhang', 'zuzhangstatus')) { if (!token) {
if (!this.data.isZuzhang) this.setData({ isZuzhang: true }) wx.showModal({
ensureRoleOnCenterPage(this, 'zuzhang') title: '提示',
this.getZuzhangInfo() content: '您尚未登录,请先登录',
} confirmText: '去登录',
}, success: (res) => {
if (res.confirm) {
// 检查登录状态 wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' })
checkLoginStatus() { }
const token = wx.getStorageSync('token') }
if (!token) { })
wx.showModal({ return false
title: '提示', }
content: '您尚未登录,请先登录', this.checkRoleStatus()
confirmText: '去登录', return true
success: (res) => { },
if (res.confirm) {
wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' }) // 角色状态检查后的额外数据加载(由 mixin 的 checkRoleStatus 调用)
} checkGlobalData() {
} this.loadUserInfo()
}) this.getZuzhangInfo()
return false },
}
this.checkZuzhangStatus() // 获取组长信息(刷新)
return true async getZuzhangInfo() {
}, this.loadData({
url: '/yonghu/zuzhangxinxi',
// 检查组长状态 method: 'POST',
checkZuzhangStatus() { errorMsg: '获取信息失败',
try { onSuccess: (data) => {
const zuzhangstatus = wx.getStorageSync('zuzhangstatus') this.setData({
if (isRoleStatusActive(zuzhangstatus)) { ketixian: data.ketixian || '0.00',
this.setData({ isZuzhang: true }) guanshiCount: data.guanshiCount || 0,
this.loadUserInfo() fenhongZonge: data.fenhongZonge || '0.00',
this.getZuzhangInfo() })
ensureRoleOnCenterPage(this, 'zuzhang') wx.showToast({ title: '刷新成功', icon: 'success', duration: 1500 })
} else { }
this.setData({ isZuzhang: false }) })
} },
} catch (error) {
console.error('读取缓存失败:', error) // 刷新按钮点击
this.setData({ isZuzhang: false }) refreshZuzhangInfo() {
} this.throttledRefresh(this.getZuzhangInfo)
}, },
// 加载用户信息 // 邀请码输入
loadUserInfo() { onInviteCodeInput(e) {
const app = getApp() this.setData({ inviteCode: e.detail.value.trim() })
const globalData = app.globalData || {} },
const touxiang = wx.getStorageSync('touxiang') // 注册组长
const ossImageUrl = globalData.ossImageUrl || '' onRegister() {
const morentouxiang = globalData.morentouxiang || 'avatar/default.jpg' const { inviteCode } = this.data
this.registerWithInviteCode({
let avatarUrl = '' inviteCode,
if (touxiang) { apiUrl: '/yonghu/zuzhangzhuce',
avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang) role: 'zuzhang',
} else { statusKey: 'zuzhangstatus',
avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang) successMsg: '注册成功!',
} onSuccess: (userData) => {
this.setData({
const uid = wx.getStorageSync('uid') || '' isZuzhang: true,
const nicheng = wx.getStorageSync('nicheng') || globalData.nicheng || '' ketixian: userData.ketixian || '0.00',
guanshiCount: userData.guanshiCount || 0,
this.setData({ avatarUrl, uid, nicheng }) fenhongZonge: userData.fenhongZonge || '0.00',
}, })
this.loadUserInfo()
// 注册通知组件 ensureRoleOnCenterPage(this, 'zuzhang')
registerNotificationComponent() { }
const app = getApp() })
const notificationComp = this.selectComponent('#global-notification') },
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = { // 跳转到提现页面
show: (data) => notificationComp.showNotification(data), goToWithdraw() {
hide: () => notificationComp.hideNotification() const { ketixian } = this.data
} wx.navigateTo({
} url: `/pages/withdraw/withdraw?from=zuzhang&amount=${ketixian}`
}, })
},
// 获取组长信息(刷新)
async getZuzhangInfo() { // 跳转到分红记录
this.setData({ isLoading: true }) goToFenhongJilu() {
try { wx.navigateTo({ url: '/pages/leader-bonus-log/leader-bonus-log' })
const res = await request({ },
url: '/yonghu/zuzhangxinxi',
method: 'POST' // 跳转到邀请管事
}) goToYaoqingGuanshi() {
if (res && res.data.code === 200) { wx.navigateTo({ url: '/pages/invite-manager/invite-manager' })
const data = res.data.data }
this.setData({ }, { roleConfig: { role: 'dashou', statusKey: 'zuzhangstatus', dataFlag: 'isZuzhang', pageId: 'zuzhangduan', apiUrl: '/yonghu/zuzhangxinxi' } }))
ketixian: data.ketixian || '0.00',
guanshiCount: data.guanshiCount || 0,
fenhongZonge: data.fenhongZonge || '0.00',
isLoading: false
})
wx.showToast({ title: '刷新成功', icon: 'success', duration: 1500 })
} else {
throw new Error(res?.data?.msg || '获取信息失败')
}
} catch (error) {
console.error('获取组长信息失败:', error)
this.setData({ isLoading: false })
wx.showToast({ title: error.message || '刷新失败', icon: 'none' })
}
},
// 刷新按钮点击
refreshZuzhangInfo() {
this.getZuzhangInfo()
},
// 邀请码输入
onInviteCodeInput(e) {
this.setData({ inviteCode: e.detail.value.trim() })
},
// 注册组长
async onRegister() {
const { inviteCode } = this.data
if (!inviteCode) {
wx.showToast({ title: '请输入邀请码', icon: 'none', duration: 2000 })
return
}
if (inviteCode.length > 100) {
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none', duration: 2000 })
return
}
this.setData({ isLoading: true })
try {
const res = await request({
url: '/yonghu/zuzhangzhuce',
method: 'POST',
data: { inviteCode }
})
if (res && res.data.code === 200) {
const data = res.data.data || {}
syncRoleStatuses(data)
syncProfileFields(data)
if (!Object.prototype.hasOwnProperty.call(data, 'zuzhangstatus')) {
wx.setStorageSync('zuzhangstatus', 1)
}
this.setData({
isZuzhang: true,
ketixian: data.ketixian || '0.00',
guanshiCount: data.guanshiCount || 0,
fenhongZonge: data.fenhongZonge || '0.00',
isLoading: false
})
this.loadUserInfo()
ensureRoleOnCenterPage(this, 'zuzhang')
wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 })
} else {
throw new Error(res?.data?.msg || '注册失败')
}
} catch (error) {
console.error('注册失败:', error)
this.setData({ isLoading: false })
wx.showToast({ title: error.message || '注册失败', icon: 'error' })
}
},
// 跳转到提现页面
goToWithdraw() {
const { ketixian } = this.data
wx.navigateTo({
url: `/pages/withdraw/withdraw?from=zuzhang&amount=${ketixian}`
})
},
// 跳转到分红记录
goToFenhongJilu() {
wx.navigateTo({ url: '/pages/leader-bonus-log/leader-bonus-log' })
},
// 跳转到邀请管事
goToYaoqingGuanshi() {
wx.navigateTo({ url: '/pages/invite-manager/invite-manager' })
}
})
+165 -199
View File
@@ -1,235 +1,205 @@
<!-- pages/guanshi-paihang/guanshi-paihang.wxml --> <!-- pages/manager-rank/manager-rank.wxml -->
<view class="guanshi-paihang-page"> <view class="rank-page">
<!-- 赛博朋克背景元素 --> <!-- 赛博朋克背景元素 -->
<view class="cyber-bg"> <view class="rank-cyber-bg">
<!-- 网格线 --> <!-- 网格线 -->
<view class="cyber-grid"></view> <view class="rank-cyber-grid"></view>
<!-- 扫描线 --> <!-- 扫描线 -->
<view class="cyber-scanline"></view> <view class="rank-cyber-scanline"></view>
<!-- 流动数据流 --> <!-- 流动数据流 -->
<view class="data-streams"> <view class="rank-data-streams">
<view class="stream stream-1"></view> <view class="rank-stream rank-stream--1"></view>
<view class="stream stream-2"></view> <view class="rank-stream rank-stream--2"></view>
<view class="stream stream-3"></view> <view class="rank-stream rank-stream--3"></view>
</view> </view>
<!-- 浮动粒子 --> <!-- 浮动粒子 -->
<view class="floating-particles"> <view class="rank-particle" wx:for="{{12}}" wx:key="index"></view>
<view class="particle" wx:for="{{12}}" wx:key="index"></view>
</view>
</view> </view>
<!-- 页面头部 --> <!-- 页面头部 -->
<view class="page-header"> <view class="rank-page-header">
<!-- 标题 --> <!-- 标题 -->
<view class="title-container"> <view class="rank-title-container">
<text class="main-title">管事排行榜</text> <text class="rank-main-title">管事排行榜</text>
<view class="title-line"></view> <view class="rank-title-line"></view>
<text class="sub-title">GUANSHI RANKING</text> <text class="rank-sub-title">GUANSHI RANKING</text>
</view> </view>
<!-- 切换按钮 --> <!-- 切换按钮 -->
<view class="toggle-container"> <view class="rank-toggle-container">
<view class="toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view> <view class="rank-toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view <view
class="toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}" class="rank-toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="1" data-leixing="1"
bindtap="qiehuangPaihangLeixing" bindtap="qiehuangPaihangLeixing"
> >
<text class="toggle-text">今日充值人数</text> <text class="rank-toggle-text">今日充值人数</text>
<view class="toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view> <view class="rank-toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
</view> </view>
<view <view
class="toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}" class="rank-toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="2" data-leixing="2"
bindtap="qiehuangPaihangLeixing" bindtap="qiehuangPaihangLeixing"
> >
<text class="toggle-text">今月充值人数</text> <text class="rank-toggle-text">今月充值人数</text>
<view class="toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view> <view class="rank-toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
</view> </view>
</view> </view>
</view> </view>
<!-- 加载状态 --> <!-- 加载状态 -->
<view wx:if="{{jiazaiZhuangtai}}" class="loading-state"> <view wx:if="{{jiazaiZhuangtai}}" class="rank-loading-state">
<view class="loading-content"> <view class="rank-loading-spinner">
<view class="loading-spinner"> <view class="rank-spinner-outer"></view>
<view class="spinner-outer"></view> <view class="rank-spinner-inner"></view>
<view class="spinner-inner"></view> <view class="rank-spinner-core"></view>
<view class="spinner-core"></view>
</view>
<text class="loading-text">数据加载中</text>
<text class="loading-sub">Connecting to server...</text>
</view> </view>
<text class="rank-loading-text">数据加载中</text>
<text class="rank-loading-sub">Connecting to server...</text>
</view> </view>
<!-- 排行榜内容 --> <!-- 排行榜内容 -->
<view wx:else class="ranking-content"> <view wx:else class="rank-content">
<!-- 虚拟数据提示 --> <!-- 虚拟数据提示 -->
<view wx:if="{{!xianshiZhenshiShuju}}" class="demo-notice"> <view wx:if="{{!xianshiZhenshiShuju}}" class="rank-demo-notice">
<view class="notice-icon">📡</view> <view class="rank-notice-icon">📡</view>
<view class="notice-content"> <view class="rank-notice-content">
<text class="notice-title">排行榜数据准备中</text> <text class="rank-notice-title">排行榜数据准备中</text>
<text class="notice-desc">当前显示为模拟数据,实时排名即将开放</text> <text class="rank-notice-desc">当前显示为模拟数据,实时排名即将开放</text>
</view> </view>
<view class="notice-glow"></view>
</view> </view>
<!-- ========== 前三名区域 ========== --> <!-- ========== 前三名区域 ========== -->
<view class="top-three-section" wx:if="{{qiansanMing.length > 0}}"> <view class="rank-top-three" wx:if="{{qiansanMing.length > 0}}">
<!-- 第二名 --> <!-- 第二名 -->
<view class="rank-card rank-2 {{flashCardIndex == 1 && flashCardType == 'top3' ? 'flash' : ''}}" <view class="rank-card rank-card--2 {{flashCardIndex == 1 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 1 && flashCardType == 'top3' ? flashAnimation : ''}}" animation="{{flashCardIndex == 1 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="1" data-type="top3" bindtap="handleCardTap"> data-index="1" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-silver"> <view class="rank-podium-base rank-podium-silver">
<view class="podium-glow"></view> <text class="rank-podium-number">2</text>
<text class="podium-number">2</text>
</view> </view>
<view class="rank-content"> <view class="rank-content">
<!-- 头像容器 --> <!-- 头像容器 -->
<view class="avatar-frame frame-silver"> <view class="rank-avatar-frame rank-frame-silver">
<view class="avatar-container"> <view class="rank-avatar-container">
<image class="avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image> <image class="rank-avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-silver"></view> <view class="rank-avatar-halo rank-halo-silver"></view>
<view class="avatar-sparkles"> <view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="sparkle s1"></view> <view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="sparkle s2"></view> <view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="sparkle s3"></view>
</view>
</view> </view>
<view class="rank-badge badge-silver">2</view> <view class="rank-badge rank-badge-silver">2</view>
</view> </view>
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="user-info-card"> <view class="rank-user-info-card">
<view class="info-header"> <text class="rank-user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
<text class="user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text> <view class="rank-info-body">
</view> <view class="rank-uid-row">
<view class="info-body"> <text class="rank-uid-label">ID:</text>
<view class="uid-row"> <text class="rank-uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
</view> </view>
<view class="income-row"> <view class="rank-income-row">
<text class="income-label">充值大手:</text> <text class="rank-income-label">充值大手:</text>
<view class="income-value"> <view class="rank-income-value">
<text class="amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0'}}</text> <text class="rank-amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0'}}</text>
<text class="unit">人</text> <text class="rank-unit">人</text>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<!-- 第一名 --> <!-- 第一名 -->
<view class="rank-card rank-1 {{flashCardIndex == 0 && flashCardType == 'top3' ? 'flash' : ''}}" <view class="rank-card rank-card--1 {{flashCardIndex == 0 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 0 && flashCardType == 'top3' ? flashAnimation : ''}}" animation="{{flashCardIndex == 0 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="0" data-type="top3" bindtap="handleCardTap"> data-index="0" data-type="top3" bindtap="handleCardTap">
<!-- 皇冠 --> <!-- 皇冠 -->
<view class="crown-container" animation="{{crownAnimation}}"> <view class="rank-crown-container" animation="{{crownAnimation}}">
<view class="crown"> <text class="rank-crown-icon">👑</text>
<text class="crown-icon">👑</text> <view class="rank-crown-glow"></view>
<view class="crown-rays">
<view class="ray ray-1"></view>
<view class="ray ray-2"></view>
<view class="ray ray-3"></view>
<view class="ray ray-4"></view>
<view class="ray ray-5"></view>
<view class="ray ray-6"></view>
</view>
<view class="crown-glow"></view>
</view>
</view> </view>
<view class="podium-base podium-gold"> <view class="rank-podium-base rank-podium-gold">
<view class="podium-glow"></view> <text class="rank-podium-number">1</text>
<text class="podium-number">1</text>
</view> </view>
<view class="rank-content"> <view class="rank-content">
<!-- 头像容器 --> <!-- 头像容器 -->
<view class="avatar-frame frame-gold"> <view class="rank-avatar-frame rank-frame-gold">
<view class="avatar-container"> <view class="rank-avatar-container">
<image class="avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image> <image class="rank-avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-gold"></view> <view class="rank-avatar-halo rank-halo-gold"></view>
<view class="avatar-sparkles"> <view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="sparkle s1"></view> <view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="sparkle s2"></view> <view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="sparkle s3"></view> <view class="rank-sparkle" style="bottom:15%;right:15%;animation-delay:1.5s"></view>
<view class="sparkle s4"></view>
</view>
</view> </view>
<view class="rank-badge badge-gold">1</view> <view class="rank-badge rank-badge-gold">1</view>
</view> </view>
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="user-info-card"> <view class="rank-user-info-card">
<view class="info-header"> <text class="rank-user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
<text class="user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text> <view class="rank-info-body">
</view> <view class="rank-uid-row">
<view class="info-body"> <text class="rank-uid-label">ID:</text>
<view class="uid-row"> <text class="rank-uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
</view> </view>
<view class="income-row"> <view class="rank-income-row">
<text class="income-label">充值大手:</text> <text class="rank-income-label">充值大手:</text>
<view class="income-value"> <view class="rank-income-value">
<text class="amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0'}}</text> <text class="rank-amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0'}}</text>
<text class="unit">人</text> <text class="rank-unit">人</text>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<!-- 第三名 --> <!-- 第三名 -->
<view class="rank-card rank-3 {{flashCardIndex == 2 && flashCardType == 'top3' ? 'flash' : ''}}" <view class="rank-card rank-card--3 {{flashCardIndex == 2 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 2 && flashCardType == 'top3' ? flashAnimation : ''}}" animation="{{flashCardIndex == 2 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="2" data-type="top3" bindtap="handleCardTap"> data-index="2" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-bronze"> <view class="rank-podium-base rank-podium-bronze">
<view class="podium-glow"></view> <text class="rank-podium-number">3</text>
<text class="podium-number">3</text>
</view> </view>
<view class="rank-content"> <view class="rank-content">
<!-- 头像容器 --> <!-- 头像容器 -->
<view class="avatar-frame frame-bronze"> <view class="rank-avatar-frame rank-frame-bronze">
<view class="avatar-container"> <view class="rank-avatar-container">
<image class="avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image> <image class="rank-avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-bronze"></view> <view class="rank-avatar-halo rank-halo-bronze"></view>
<view class="avatar-sparkles"> <view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="sparkle s1"></view> <view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="sparkle s2"></view> <view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="sparkle s3"></view>
</view>
</view> </view>
<view class="rank-badge badge-bronze">3</view> <view class="rank-badge rank-badge-bronze">3</view>
</view> </view>
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="user-info-card"> <view class="rank-user-info-card">
<view class="info-header"> <text class="rank-user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
<text class="user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text> <view class="rank-info-body">
</view> <view class="rank-uid-row">
<view class="info-body"> <text class="rank-uid-label">ID:</text>
<view class="uid-row"> <text class="rank-uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
</view> </view>
<view class="income-row"> <view class="rank-income-row">
<text class="income-label">充值大手:</text> <text class="rank-income-label">充值大手:</text>
<view class="income-value"> <view class="rank-income-value">
<text class="amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0'}}</text> <text class="rank-amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0'}}</text>
<text class="unit">人</text> <text class="rank-unit">人</text>
</view> </view>
</view> </view>
</view> </view>
@@ -237,52 +207,48 @@
</view> </view>
</view> </view>
</view> </view>
<!-- ========== 其他排名列表 ========== --> <!-- ========== 其他排名列表 ========== -->
<view class="other-ranks-section" wx:if="{{paihangShuju.length > 0}}"> <view wx:if="{{paihangShuju.length > 0}}">
<view class="section-title"> <view class="rank-section-title">
<view class="title-line"></view> <view class="rank-section-line"></view>
<text class="title-text">其他排名</text> <text class="rank-section-text">其他排名</text>
<view class="title-line"></view> <view class="rank-section-line"></view>
</view> </view>
<view class="rank-list"> <view class="rank-list-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
<view class="rank-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}" wx:for="{{paihangShuju}}" wx:key="index"
wx:for="{{paihangShuju}}" wx:key="index" animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}"
animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}" data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
<view class="rank-number">
<view class="rank-number"> <text class="rank-number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<text class="number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text> <view class="rank-number-glow"></view>
<view class="number-glow"></view> </view>
</view>
<view class="rank-list-avatar">
<view class="rank-avatar"> <image class="rank-list-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<image class="rank-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image> <view class="rank-list-avatar-halo"></view>
<view class="rank-avatar-halo"></view> </view>
</view>
<view class="rank-list-info">
<view class="rank-info"> <text class="rank-list-name">{{item.nicheng}}</text>
<text class="rank-name">{{item.nicheng}}</text> <text class="rank-list-uid">UID: {{item.uid}}</text>
<text class="rank-uid">UID: {{item.uid}}</text> </view>
</view>
<view class="rank-list-income">
<view class="rank-income"> <text class="rank-income-amount {{item.jineClass}}">{{item.jine}}</text>
<text class="income-amount {{item.jineClass}}">{{item.jine}}</text> <text class="rank-income-unit">人</text>
<text class="income-unit">人</text> <view class="rank-income-glow"></view>
<view class="income-glow"></view>
</view>
<view class="rank-hover"></view>
</view> </view>
</view> </view>
</view> </view>
<!-- 空状态 --> <!-- 空状态 -->
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="empty-state"> <view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="rank-empty-state">
<view class="empty-icon">🏆</view> <view class="rank-empty-icon">🏆</view>
<text class="empty-title">暂无排名数据</text> <text class="rank-empty-title">暂无排名数据</text>
<text class="empty-desc">成为第一个登榜的管事吧</text> <text class="rank-empty-desc">成为第一个登榜的管事吧</text>
</view> </view>
</view> </view>
</view> </view>
File diff suppressed because it is too large. Load diff
+496 -677
View File
File diff suppressed because it is too large. Load diff
+406 -418
View File
@@ -1,418 +1,406 @@
// pages/merchant-dispatch/merchant-dispatch.js // pages/merchant-dispatch/merchant-dispatch.js
import request from '../../utils/request.js' import { createPage, request } from '../../utils/base-page.js'
import PopupService from '../../services/popupService.js' // 引入弹窗服务 import PopupService from '../../services/popupService.js' // 引入弹窗服务
Page({ Page(createPage({
data: { data: {
// 商家余额 (从全局变量获取) // 商家余额 (从全局变量获取)
sjyue: '0.00', sjyue: '0.00',
// 商品类型列表 // 商品类型列表
shangpinList: [], shangpinList: [],
// 当前选中的商品类型信息 // 当前选中的商品类型信息
selectedTypeId: null, selectedTypeId: null,
selectedHuiyuanId: null, selectedHuiyuanId: null,
selectedYaoqiuleixing: null, selectedYaoqiuleixing: null,
// 当前商品类型下的称号列表(用于标签选择器) // 当前商品类型下的称号列表(用于标签选择器)
currentTypeChenghaoList: [], currentTypeChenghaoList: [],
// 表单字段 // 表单字段
dingdanJieshao: '', // 订单介绍 dingdanJieshao: '', // 订单介绍
dingdanBeizhu: '', // 订单备注 dingdanBeizhu: '', // 订单备注
laobanName: '', // 老板游戏昵称/UID laobanName: '', // 老板游戏昵称/UID
zhidingUid: '', // 指定打手UID zhidingUid: '', // 指定打手UID
jiage: '', // 订单价格 jiage: '', // 订单价格
// 新增字段:抢单标签(称号) // 新增字段:抢单标签(称号)
selectedLabelId: '', // 选中的标签ID selectedLabelId: '', // 选中的标签ID
selectedLabelName: '', // 选中的标签名称 selectedLabelName: '', // 选中的标签名称
// 新增字段:佣金要求(压金) // 新增字段:佣金要求(压金)
commissionEnabled: false, // 是否开启佣金要求 commissionEnabled: false, // 是否开启佣金要求
commissionValue: '', // 佣金金额(元) commissionValue: '', // 佣金金额(元)
// 页面状态 // 页面状态
isLoading: false, // 加载商品类型中 isLoading: false, // 加载商品类型中
isSubmitting: false, // 提交派单中 isSubmitting: false, // 提交派单中
// 教程按钮状态 // 教程按钮状态
tutorialBtnHidden: false, // false=完全显示,true=缩进隐藏(只露边) tutorialBtnHidden: false, // false=完全显示,true=缩进隐藏(只露边)
// 背景图URL(动态拼接) // 背景图URL(动态拼接)
bgImageUrl: '' bgImageUrl: ''
}, },
onLoad() { onLoad() {
this.loadBgImage() // 拼接背景图 this.loadBgImage() // 拼接背景图
this.loadShangjiaYue() // 从全局变量获取商家余额 this.loadShangjiaYue() // 从全局变量获取商家余额
this.loadShangpinTypes() // 加载商品类型 this.loadShangpinTypes() // 加载商品类型
this.startTutorialBtnTimer() // 启动按钮缩进定时器 this.startTutorialBtnTimer() // 启动按钮缩进定时器
}, },
onShow() { onShow() {
// 每次显示页面时刷新余额(可能从其他页面充值后返回) // 每次显示页面时刷新余额(可能从其他页面充值后返回)
this.loadShangjiaYue() this.loadShangjiaYue()
// 重新拼接背景图(确保最新) // 重新拼接背景图(确保最新)
this.loadBgImage() this.loadBgImage()
// 注册通知组件(用于全局消息提示) // 注册通知组件(用于全局消息提示)
this.registerNotificationComponent() this.registerNotificationComponent()
// 重置按钮缩进定时器(重新开始5秒倒计时) // 重置按钮缩进定时器(重新开始5秒倒计时)
this.startTutorialBtnTimer() this.startTutorialBtnTimer()
}, },
onHide() { onHide() {
// 页面隐藏时重置弹窗服务状态,避免影响其他页面 // 页面隐藏时重置弹窗服务状态,避免影响其他页面
PopupService.reset() PopupService.reset()
}, },
onUnload() { onUnload() {
// 清除缩进定时器,避免内存泄漏 // 清除缩进定时器,避免内存泄漏
if (this.tutorialTimer) { if (this.tutorialTimer) {
clearTimeout(this.tutorialTimer) clearTimeout(this.tutorialTimer)
} }
// 页面卸载时重置弹窗服务 // 页面卸载时重置弹窗服务
PopupService.reset() PopupService.reset()
}, },
// ==================== 背景图动态拼接 ==================== // ==================== 背景图动态拼接 ====================
loadBgImage() { loadBgImage() {
const app = getApp() const app = getApp()
const ossBase = app.globalData.ossImageUrl || 'https://your-oss-bucket.oss-cn-region.aliyuncs.com' const ossBase = app.globalData.ossImageUrl || 'https://your-oss-bucket.oss-cn-region.aliyuncs.com'
// 拼接完整路径:beijing/shangjiapaidan/bg.jpg // 拼接完整路径:beijing/shangjiapaidan/bg.jpg
const bgUrl = ossBase.replace(/\/$/, '') + '/beijing/shangjiapaidan/bg.jpg' const bgUrl = ossBase.replace(/\/$/, '') + '/beijing/shangjiapaidan/bg.jpg'
this.setData({ bgImageUrl: bgUrl }) this.setData({ bgImageUrl: bgUrl })
}, },
// ==================== 基础数据加载 ==================== // ==================== 基础数据加载 ====================
loadShangjiaYue() { loadShangjiaYue() {
const app = getApp() const app = getApp()
const shangjiaData = app.globalData.shangjia || {} const shangjiaData = app.globalData.shangjia || {}
this.setData({ this.setData({
sjyue: shangjiaData.sjyue || '0.00' sjyue: shangjiaData.sjyue || '0.00'
}) })
}, },
// 加载商品类型(与极速派单完全一致) // 加载商品类型(与极速派单完全一致)
async loadShangpinTypes() { async loadShangpinTypes() {
this.setData({ isLoading: true }) this.setData({ isLoading: true })
try { try {
const res = await request({ const res = await request({
url: '/dingdan/sjspleixing', url: '/dingdan/sjspleixing',
method: 'POST' method: 'POST'
}) })
if (res && res.data.code === 200) { if (res && res.data.code === 200) {
let list = res.data.data || [] let list = res.data.data || []
// 强制倒序排列(与极速派单保持一致) // 强制倒序排列(与极速派单保持一致)
list.reverse() list.reverse()
this.setData({ this.setData({
shangpinList: list, shangpinList: list,
isLoading: false isLoading: false
}) })
// 默认选中第一个 // 默认选中第一个
if (list.length > 0) { if (list.length > 0) {
this.setSelectedType(list[0]) this.setSelectedType(list[0])
} }
} else { } else {
throw new Error(res.data.msg || '加载失败') throw new Error(res.data.msg || '加载失败')
} }
} catch (error) { } catch (error) {
console.error('加载商品类型失败:', error) console.error('加载商品类型失败:', error)
this.setData({ isLoading: false }) this.setData({ isLoading: false })
wx.showToast({ wx.showToast({
title: '加载类型失败', title: '加载类型失败',
icon: 'error', icon: 'error',
duration: 2000 duration: 2000
}) })
} }
}, },
// 设置选中的商品类型,并提取该类型下的称号列表 // 设置选中的商品类型,并提取该类型下的称号列表
setSelectedType(item) { setSelectedType(item) {
this.setData({ this.setData({
selectedTypeId: item.id, selectedTypeId: item.id,
selectedHuiyuanId: item.huiyuan_id, selectedHuiyuanId: item.huiyuan_id,
selectedYaoqiuleixing: item.yaoqiuleixing, selectedYaoqiuleixing: item.yaoqiuleixing,
currentTypeChenghaoList: item.chenghaoList || [], currentTypeChenghaoList: item.chenghaoList || [],
// 切换商品类型时,清空之前选择的标签和佣金 // 切换商品类型时,清空之前选择的标签和佣金
selectedLabelId: '', selectedLabelId: '',
selectedLabelName: '', selectedLabelName: '',
commissionEnabled: false, commissionEnabled: false,
commissionValue: '' commissionValue: ''
}) })
}, },
// 注册全局通知组件(用于顶部提示) // ==================== 教程按钮控制 ====================
registerNotificationComponent() { // 启动定时器:5秒后让按钮缩进
const app = getApp() startTutorialBtnTimer() {
const notificationComp = this.selectComponent('#global-notification') // 清除旧的定时器
if (notificationComp && notificationComp.showNotification) { if (this.tutorialTimer) {
app.globalData.globalNotification = { clearTimeout(this.tutorialTimer)
show: (data) => notificationComp.showNotification(data), }
hide: () => notificationComp.hideNotification() // 5秒后将按钮缩进隐藏
} this.tutorialTimer = setTimeout(() => {
} this.setData({ tutorialBtnHidden: true })
}, }, 5000)
},
// ==================== 教程按钮控制 ====================
// 启动定时器:5秒后让按钮缩进 // 重置按钮状态(完全显示)并重新开始5秒倒计时
startTutorialBtnTimer() { resetTutorialBtn() {
// 清除旧的定时器 // 如果按钮当前是缩进状态,先恢复完全显示
if (this.tutorialTimer) { if (this.data.tutorialBtnHidden) {
clearTimeout(this.tutorialTimer) this.setData({ tutorialBtnHidden: false })
} }
// 5秒后将按钮缩进隐藏 // 重新开始计时(清除旧定时器,新定时器5秒后缩进)
this.tutorialTimer = setTimeout(() => { if (this.tutorialTimer) {
this.setData({ tutorialBtnHidden: true }) clearTimeout(this.tutorialTimer)
}, 5000) }
}, this.tutorialTimer = setTimeout(() => {
this.setData({ tutorialBtnHidden: true })
// 重置按钮状态(完全显示)并重新开始5秒倒计时 }, 5000)
resetTutorialBtn() { },
// 如果按钮当前是缩进状态,先恢复完全显示
if (this.data.tutorialBtnHidden) { // 点击教程按钮:重置按钮状态,并调用 PopupService 弹出教程
this.setData({ tutorialBtnHidden: false }) async onTutorialBtnTap() {
} // 首先重置按钮状态(完全显示并重新计时)
// 重新开始计时(清除旧定时器,新定时器5秒后缩进) this.resetTutorialBtn()
if (this.tutorialTimer) { // 调用弹窗服务,页面标识为 'sjpd1'
clearTimeout(this.tutorialTimer) PopupService.checkAndShow(this, 'sjpd1')
} },
this.tutorialTimer = setTimeout(() => {
this.setData({ tutorialBtnHidden: true }) // ==================== 表单输入事件 ====================
}, 5000) onDingdanJieshaoInput(e) {
}, this.setData({ dingdanJieshao: e.detail.value })
},
// 点击教程按钮:重置按钮状态,并调用 PopupService 弹出教程 onDingdanBeizhuInput(e) {
async onTutorialBtnTap() { this.setData({ dingdanBeizhu: e.detail.value })
// 首先重置按钮状态(完全显示并重新计时) },
this.resetTutorialBtn() onLaobanNameInput(e) {
// 调用弹窗服务,页面标识为 'sjpd1' this.setData({ laobanName: e.detail.value })
PopupService.checkAndShow(this, 'sjpd1') },
}, onZhidingUidInput(e) {
this.setData({ zhidingUid: e.detail.value })
// ==================== 表单输入事件 ==================== },
onDingdanJieshaoInput(e) { onJiageInput(e) {
this.setData({ dingdanJieshao: e.detail.value }) let value = e.detail.value
}, // 前端验证:确保是数字且最多两位小数
onDingdanBeizhuInput(e) { if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) {
this.setData({ dingdanBeizhu: e.detail.value }) this.setData({ jiage: value })
}, }
onLaobanNameInput(e) { },
this.setData({ laobanName: e.detail.value })
}, // 标签选择器(picker事件)
onZhidingUidInput(e) { onLabelChange(e) {
this.setData({ zhidingUid: e.detail.value }) const idx = e.detail.value
}, const label = this.data.currentTypeChenghaoList[idx]
onJiageInput(e) { if (label) {
let value = e.detail.value this.setData({
// 前端验证:确保是数字且最多两位小数 selectedLabelId: label.id,
if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) { selectedLabelName: label.mingcheng
this.setData({ jiage: value }) })
} } else {
}, this.setData({
selectedLabelId: '',
// 标签选择器(picker事件) selectedLabelName: ''
onLabelChange(e) { })
const idx = e.detail.value }
const label = this.data.currentTypeChenghaoList[idx] },
if (label) {
this.setData({ // 佣金开关
selectedLabelId: label.id, onCommissionToggle() {
selectedLabelName: label.mingcheng this.setData({ commissionEnabled: !this.data.commissionEnabled })
}) },
} else { onCommissionValueInput(e) {
this.setData({ let value = e.detail.value
selectedLabelId: '', if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) {
selectedLabelName: '' this.setData({ commissionValue: value })
}) }
} },
},
// ==================== 表单验证与提交 ====================
// 佣金开关 validateForm() {
onCommissionToggle() { const {
this.setData({ commissionEnabled: !this.data.commissionEnabled }) selectedTypeId,
}, dingdanJieshao,
onCommissionValueInput(e) { laobanName,
let value = e.detail.value jiage,
if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) { sjyue,
this.setData({ commissionValue: value }) commissionEnabled,
} commissionValue
}, } = this.data
// ==================== 表单验证与提交 ==================== // 1. 验证必填项
validateForm() { if (!selectedTypeId) {
const { wx.showToast({ title: '请选择订单类型', icon: 'none' })
selectedTypeId, return false
dingdanJieshao, }
laobanName, if (!dingdanJieshao.trim()) {
jiage, wx.showToast({ title: '请输入订单介绍', icon: 'none' })
sjyue, return false
commissionEnabled, }
commissionValue if (!laobanName.trim()) {
} = this.data wx.showToast({ title: '请输入老板游戏昵称', icon: 'none' })
return false
// 1. 验证必填项 }
if (!selectedTypeId) { if (!jiage) {
wx.showToast({ title: '请选择订单类型', icon: 'none' }) wx.showToast({ title: '请输入订单价格', icon: 'none' })
return false return false
} }
if (!dingdanJieshao.trim()) {
wx.showToast({ title: '请输入订单介绍', icon: 'none' }) // 2. 验证价格格式和范围
return false const price = parseFloat(jiage)
} if (isNaN(price) || price < 0.1 || price > 10000) {
if (!laobanName.trim()) { wx.showToast({ title: '价格需在0.1-10000元之间', icon: 'none' })
wx.showToast({ title: '请输入老板游戏昵称', icon: 'none' }) return false
return false }
} if (jiage.includes('.') && jiage.split('.')[1].length > 2) {
if (!jiage) { wx.showToast({ title: '最多支持两位小数', icon: 'none' })
wx.showToast({ title: '请输入订单价格', icon: 'none' }) return false
return false }
}
// 3. 验证佣金要求(若开启)
// 2. 验证价格格式和范围 if (commissionEnabled) {
const price = parseFloat(jiage) const commission = parseFloat(commissionValue)
if (isNaN(price) || price < 0.1 || price > 10000) { if (isNaN(commission) || commission <= 0) {
wx.showToast({ title: '价格需在0.1-10000元之间', icon: 'none' }) wx.showToast({ title: '请输入有效的佣金金额', icon: 'none' })
return false return false
} }
if (jiage.includes('.') && jiage.split('.')[1].length > 2) { if (commission > 50000) {
wx.showToast({ title: '最多支持两位小数', icon: 'none' }) wx.showToast({ title: '佣金建议不超过50000元', icon: 'none' })
return false return false
} }
}
// 3. 验证佣金要求(若开启)
if (commissionEnabled) { // 4. 验证余额是否足够
const commission = parseFloat(commissionValue) const balance = parseFloat(sjyue)
if (isNaN(commission) || commission <= 0) { if (price > balance) {
wx.showToast({ title: '请输入有效的佣金金额', icon: 'none' }) wx.showModal({
return false title: '余额不足',
} content: `当前余额${sjyue}元,需要${jiage}元。请先充值。`,
if (commission > 50000) { confirmText: '去充值',
wx.showToast({ title: '佣金建议不超过50000元', icon: 'none' }) success: (res) => {
return false if (res.confirm) {
} wx.navigateTo({
} url: '/pages/merchant-recharge/merchant-recharge'
})
// 4. 验证余额是否足够 }
const balance = parseFloat(sjyue) }
if (price > balance) { })
wx.showModal({ return false
title: '余额不足', }
content: `当前余额${sjyue}元,需要${jiage}元。请先充值。`,
confirmText: '去充值', return true
success: (res) => { },
if (res.confirm) {
wx.navigateTo({ // 重置表单(保留商品类型选中状态)
url: '/pages/merchant-recharge/merchant-recharge' resetForm() {
}) this.setData({
} dingdanJieshao: '',
} dingdanBeizhu: '',
}) laobanName: '',
return false zhidingUid: '',
} jiage: '',
selectedLabelId: '',
return true selectedLabelName: '',
}, commissionEnabled: false,
commissionValue: ''
// 重置表单(保留商品类型选中状态) })
resetForm() { // 重置选中第一个商品类型(可选)
this.setData({ if (this.data.shangpinList.length > 0) {
dingdanJieshao: '', this.setSelectedType(this.data.shangpinList[0])
dingdanBeizhu: '', }
laobanName: '', },
zhidingUid: '',
jiage: '', // 提交派单
selectedLabelId: '', async onSubmit() {
selectedLabelName: '', if (this.data.isSubmitting) return
commissionEnabled: false, if (!this.validateForm()) return
commissionValue: ''
}) this.setData({ isSubmitting: true })
// 重置选中第一个商品类型(可选)
if (this.data.shangpinList.length > 0) { // 准备提交数据(原有字段 + 新增字段)
this.setSelectedType(this.data.shangpinList[0]) const postData = {
} // 原有必传字段
}, shangpinTypeId: this.data.selectedTypeId,
huiyuanId: this.data.selectedHuiyuanId,
// 提交派单 yaoqiuleixing: this.data.selectedYaoqiuleixing,
async onSubmit() { dingdanJieshao: this.data.dingdanJieshao.trim(),
if (this.data.isSubmitting) return dingdanBeizhu: this.data.dingdanBeizhu.trim() || '',
if (!this.validateForm()) return laobanName: this.data.laobanName.trim(),
zhidingUid: this.data.zhidingUid.trim() || '',
this.setData({ isSubmitting: true }) jiage: this.data.jiage,
// 新增字段
// 准备提交数据(原有字段 + 新增字段) labelId: this.data.selectedLabelId || '',
const postData = { labelName: this.data.selectedLabelName || '',
// 原有必传字段 commissionEnabled: this.data.commissionEnabled,
shangpinTypeId: this.data.selectedTypeId, commissionValue: this.data.commissionEnabled ? this.data.commissionValue : ''
huiyuanId: this.data.selectedHuiyuanId, }
yaoqiuleixing: this.data.selectedYaoqiuleixing,
dingdanJieshao: this.data.dingdanJieshao.trim(), try {
dingdanBeizhu: this.data.dingdanBeizhu.trim() || '', const res = await request({
laobanName: this.data.laobanName.trim(), url: '/dingdan/sjpaifa',
zhidingUid: this.data.zhidingUid.trim() || '', method: 'POST',
jiage: this.data.jiage, data: postData
// 新增字段 })
labelId: this.data.selectedLabelId || '',
labelName: this.data.selectedLabelName || '', if (res && res.data.code === 200) {
commissionEnabled: this.data.commissionEnabled, // 派单成功
commissionValue: this.data.commissionEnabled ? this.data.commissionValue : '' wx.showToast({
} title: '派单成功',
icon: 'success',
try { duration: 2000
const res = await request({ })
url: '/dingdan/sjpaifa',
method: 'POST', // 更新全局变量中的商家信息(余额、发单量、流水)
data: postData const orderAmount = parseFloat(this.data.jiage)
}) const app = getApp()
if (app.globalData.shangjia) {
if (res && res.data.code === 200) { const newBalance = parseFloat(app.globalData.shangjia.sjyue || '0') - orderAmount
// 派单成功 app.globalData.shangjia.sjyue = newBalance.toFixed(2)
wx.showToast({ app.globalData.shangjia.fadanzong = (parseInt(app.globalData.shangjia.fadanzong || '0') + 1).toString()
title: '派单成功', app.globalData.shangjia.riliushui = (parseFloat(app.globalData.shangjia.riliushui || '0') + orderAmount).toFixed(2)
icon: 'success', app.globalData.shangjia.yueliushui = (parseFloat(app.globalData.shangjia.yueliushui || '0') + orderAmount).toFixed(2)
duration: 2000 this.setData({ sjyue: app.globalData.shangjia.sjyue })
}) }
// 更新全局变量中的商家信息(余额、发单量、流水) // 延迟重置表单,避免用户连续提交
const orderAmount = parseFloat(this.data.jiage) setTimeout(() => {
const app = getApp() this.resetForm()
if (app.globalData.shangjia) { this.setData({ isSubmitting: false })
const newBalance = parseFloat(app.globalData.shangjia.sjyue || '0') - orderAmount }, 1500)
app.globalData.shangjia.sjyue = newBalance.toFixed(2) } else {
app.globalData.shangjia.fadanzong = (parseInt(app.globalData.shangjia.fadanzong || '0') + 1).toString() wx.showModal({
app.globalData.shangjia.riliushui = (parseFloat(app.globalData.shangjia.riliushui || '0') + orderAmount).toFixed(2) title: '派单失败',
app.globalData.shangjia.yueliushui = (parseFloat(app.globalData.shangjia.yueliushui || '0') + orderAmount).toFixed(2) content: res.data.msg || '请稍后重试',
this.setData({ sjyue: app.globalData.shangjia.sjyue }) showCancel: false
} })
this.setData({ isSubmitting: false })
// 延迟重置表单,避免用户连续提交 }
setTimeout(() => { } catch (error) {
this.resetForm() console.error('派单请求失败:', error)
this.setData({ isSubmitting: false }) wx.showModal({
}, 1500) title: '请求失败',
} else { content: error.message || '网络错误,请检查连接',
wx.showModal({ showCancel: false
title: '派单失败', })
content: res.data.msg || '请稍后重试', this.setData({ isSubmitting: false })
showCancel: false }
}) },
this.setData({ isSubmitting: false })
} // ==================== 商品类型选择 ====================
} catch (error) { onSelectType(e) {
console.error('派单请求失败:', error) const { id, item } = e.currentTarget.dataset
wx.showModal({ this.setSelectedType(item)
title: '请求失败', }
content: error.message || '网络错误,请检查连接', }))
showCancel: false
})
this.setData({ isSubmitting: false })
}
},
// ==================== 商品类型选择 ====================
onSelectType(e) {
const { id, item } = e.currentTarget.dataset
this.setSelectedType(item)
}
})
+307 -327
View File
@@ -1,327 +1,307 @@
// pages/merchant-orders/merchant-orders.js // pages/merchant-orders/merchant-orders.js
const app = getApp(); const app = getApp();
import request from '../../utils/request.js'; import { createPage, request } from '../../utils/base-page.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js'; import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getOrderStatusText } from '../../utils/api-helper.js';
Page({
data: { Page(createPage({
// 商品类型(和打手端用同一个接口) data: {
shangpinleixing: [], // 商品类型(和打手端用同一个接口)
xuanzhongLeixingId: null, shangpinleixing: [],
xuanzhongLeixingId: null,
// 搜索关键字(模糊搜索)
searchKeyword: '', // 搜索关键字(模糊搜索)
searchKeyword: '',
// 状态列表(含全部)
statusList: [ // 状态列表(含全部)
{ name: '全部', key: 'all', zhuangtaiList: [1, 2, 3, 4, 5, 6, 7, 8], color: '#333333' }, statusList: [
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' }, { name: '全部', key: 'all', zhuangtaiList: [1, 2, 3, 4, 5, 6, 7, 8], color: '#333333' },
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' }, { name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
{ name: '已退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' }, { name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' }, { name: '已退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' }, { name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' },
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' } { name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' },
], { name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' }
currentStatusKey: 'all', ],
currentStatusKey: 'all',
// 每个状态独立的数据集
sjDingdanShuju: { // 每个状态独立的数据集
all: { list: [], page: 1, hasMore: true, isLoading: false }, sjDingdanShuju: {
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false }, all: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }, jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false }, tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false }, yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false }, tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false } yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
}, jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
},
currentList: [],
hasMore: true, currentList: [],
isLoading: false, hasMore: true,
isLoadingMore: false, isLoading: false,
scrollViewRefreshing: false, isLoadingMore: false,
defaultImg: '/images/default-order.png', scrollViewRefreshing: false,
ossImageUrl: app.globalData.ossImageUrl || '', defaultImg: '/images/default-order.png',
ossImageUrl: app.globalData.ossImageUrl || '',
// 待结算数量
pendingCount: 0, // 待结算数量
}, pendingCount: 0,
},
onLoad() {
wx.setNavigationBarTitle({ title: '我的派单' }); onLoad() {
this.loadShangpinLeixing(); wx.setNavigationBarTitle({ title: '我的派单' });
this.registerNotificationComponent(); this.loadShangpinLeixing();
}, this.registerNotificationComponent();
},
onShow() {
this.registerNotificationComponent(); onShow() {
this.loadPendingCount(); this.registerNotificationComponent();
if (wx.getStorageSync('uid')) { this.loadPendingCount();
reconnectForRole('shangjia'); if (wx.getStorageSync('uid')) {
if (app.startImWhenReady) app.startImWhenReady(); reconnectForRole('shangjia');
} if (app.startImWhenReady) app.startImWhenReady();
if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) { }
this.loadCurrentStatusOrders(true); if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) {
} this.loadCurrentStatusOrders(true);
}, }
},
// 注册全局通知组件
registerNotificationComponent() { // 🆕 加载商品类型(和打手端用同一个接口 /dingdan/dsqdhqddlx)
const notificationComp = this.selectComponent('#global-notification'); async loadShangpinLeixing() {
if (notificationComp && notificationComp.showNotification) { try {
app.globalData.globalNotification = { const res = await request({
show: (data) => notificationComp.showNotification(data), url: '/dingdan/dsqdhqddlx',
hide: () => notificationComp.hideNotification() method: 'POST',
}; header: { 'content-type': 'application/json' }
} });
}, if (res.data && (res.data.code === 200 || res.data.code === 0)) {
let list = res.data.data.list || res.data.data || [];
// 🆕 加载商品类型(和打手端用同一个接口 /dingdan/dsqdhqddlx) if (!Array.isArray(list)) list = [];
async loadShangpinLeixing() { const oss = this.data.ossImageUrl;
try { // 拼接图片完整URL
const res = await request({ const processed = list.map(item => ({
url: '/dingdan/dsqdhqddlx', ...item,
method: 'POST', full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http')
header: { 'content-type': 'application/json' } ? oss + item.tupian_url
}); : (item.tupian_url || '/images/default-type.png')
if (res.data && (res.data.code === 200 || res.data.code === 0)) { }));
let list = res.data.data.list || res.data.data || []; this.setData({
if (!Array.isArray(list)) list = []; shangpinleixing: processed,
const oss = this.data.ossImageUrl; xuanzhongLeixingId: processed[0]?.id || null
// 拼接图片完整URL });
const processed = list.map(item => ({ // 初始加载全部状态订单
...item, this.loadCurrentStatusOrders(true);
full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http') }
? oss + item.tupian_url } catch (e) {
: (item.tupian_url || '/images/default-type.png') console.error('加载商品类型失败', e);
})); }
this.setData({ },
shangpinleixing: processed,
xuanzhongLeixingId: processed[0]?.id || null // 选择商品类型
}); selectLeixing(e) {
// 初始加载全部状态订单 const id = e.currentTarget.dataset.id;
this.loadCurrentStatusOrders(true); if (id === this.data.xuanzhongLeixingId) return;
} this.setData({ xuanzhongLeixingId: id });
} catch (e) { this.resetCurrentStatusData();
console.error('加载商品类型失败', e); this.loadCurrentStatusOrders(true);
} },
},
// 搜索输入(防抖)
// 选择商品类型 onSearchInput(e) {
selectLeixing(e) { this.setData({ searchKeyword: e.detail.value });
const id = e.currentTarget.dataset.id; if (this._searchTimer) clearTimeout(this._searchTimer);
if (id === this.data.xuanzhongLeixingId) return; this._searchTimer = setTimeout(() => {
this.setData({ xuanzhongLeixingId: id }); this.onSearchConfirm();
this.resetCurrentStatusData(); }, 500);
this.loadCurrentStatusOrders(true); },
},
// 搜索确认
// 搜索输入(防抖) onSearchConfirm() {
onSearchInput(e) { this.resetCurrentStatusData();
this.setData({ searchKeyword: e.detail.value }); this.loadCurrentStatusOrders(true);
if (this._searchTimer) clearTimeout(this._searchTimer); },
this._searchTimer = setTimeout(() => {
this.onSearchConfirm(); // 清除搜索
}, 500); clearSearch() {
}, this.setData({ searchKeyword: '' });
this.resetCurrentStatusData();
// 搜索确认 this.loadCurrentStatusOrders(true);
onSearchConfirm() { },
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true); // 切换状态Tab
}, switchStatus(e) {
const key = e.currentTarget.dataset.key;
// 清除搜索 if (key === this.data.currentStatusKey) return;
clearSearch() { this.setData({ currentStatusKey: key });
this.setData({ searchKeyword: '' }); const tabData = this.data.sjDingdanShuju[key];
this.resetCurrentStatusData(); // 如果该状态没有数据,则加载;否则只刷新视图
this.loadCurrentStatusOrders(true); if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) {
}, this.loadCurrentStatusOrders(true);
} else {
// 切换状态Tab this.refreshCurrentListView();
switchStatus(e) { }
const key = e.currentTarget.dataset.key; },
if (key === this.data.currentStatusKey) return;
this.setData({ currentStatusKey: key }); // 🆕 加载订单(核心方法,接口固定用商家订单接口 /dingdan/sjdingdanhq)
const tabData = this.data.sjDingdanShuju[key]; async loadCurrentStatusOrders(isRefresh = false) {
// 如果该状态没有数据,则加载;否则只刷新视图 const key = this.data.currentStatusKey;
if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) { const tabData = this.data.sjDingdanShuju[key];
this.loadCurrentStatusOrders(true); if (tabData.isLoading) return;
} else { const page = isRefresh ? 1 : tabData.page;
this.refreshCurrentListView();
} this.setData({
}, [`sjDingdanShuju.${key}.isLoading`]: true,
isLoading: true,
// 🆕 加载订单(核心方法,接口固定用商家订单接口 /dingdan/sjdingdanhq) isLoadingMore: !isRefresh
async loadCurrentStatusOrders(isRefresh = false) { });
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key]; try {
if (tabData.isLoading) return; const statusItem = this.data.statusList.find(s => s.key === key);
const page = isRefresh ? 1 : tabData.page; const zhuangtaiList = statusItem.zhuangtaiList;
this.setData({ // 构建请求参数
[`sjDingdanShuju.${key}.isLoading`]: true, const params = {
isLoading: true, zhuangtai_list: zhuangtaiList,
isLoadingMore: !isRefresh page: page,
}); page_size: 5,
leixing_id: this.data.xuanzhongLeixingId, // 🆕 商品类型筛选
try { keyword: this.data.searchKeyword || undefined // 🆕 模糊搜索
const statusItem = this.data.statusList.find(s => s.key === key); };
const zhuangtaiList = statusItem.zhuangtaiList;
const res = await request({
// 构建请求参数 url: '/dingdan/sjdingdanhq', // 商家订单接口(和原来一样)
const params = { method: 'POST',
zhuangtai_list: zhuangtaiList, data: params,
page: page, header: { 'content-type': 'application/json' }
page_size: 5, });
leixing_id: this.data.xuanzhongLeixingId, // 🆕 商品类型筛选
keyword: this.data.searchKeyword || undefined // 🆕 模糊搜索 const code = res.data.code;
}; if (code === 200 || code === 0) {
const newList = res.data.data.list || [];
const res = await request({ const hasMore = res.data.data.has_more || false;
url: '/dingdan/sjdingdanhq', // 商家订单接口(和原来一样)
method: 'POST', // 处理数据:拼接图片、转换状态中文
data: params, const processed = newList.map(item => ({
header: { 'content-type': 'application/json' } ...item,
}); zhuangtaiZh: getOrderStatusText(item.zhuangtai),
zhuangtaiColor: statusItem.color,
const code = res.data.code; // 如果有商品图片则用商品图片,否则用默认图
if (code === 200 || code === 0) { tupian: item.tupian
const newList = res.data.data.list || []; ? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian)
const hasMore = res.data.data.has_more || false; : this.data.defaultImg
}));
// 处理数据:拼接图片、转换状态中文
const processed = newList.map(item => ({ const currentList = this.data.sjDingdanShuju[key].list;
...item, const updatedList = isRefresh ? processed : [...currentList, ...processed];
zhuangtaiZh: this.getZhuangtaiZh(item.zhuangtai),
zhuangtaiColor: statusItem.color, this.setData({
// 如果有商品图片则用商品图片,否则用默认图 [`sjDingdanShuju.${key}.list`]: updatedList,
tupian: item.tupian [`sjDingdanShuju.${key}.page`]: page,
? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian) [`sjDingdanShuju.${key}.hasMore`]: hasMore,
: this.data.defaultImg [`sjDingdanShuju.${key}.isLoading`]: false,
})); isLoading: false,
isLoadingMore: false,
const currentList = this.data.sjDingdanShuju[key].list; scrollViewRefreshing: false
const updatedList = isRefresh ? processed : [...currentList, ...processed]; });
this.refreshCurrentListView();
this.setData({ } else {
[`sjDingdanShuju.${key}.list`]: updatedList, throw new Error(res.data.msg || '加载失败');
[`sjDingdanShuju.${key}.page`]: page, }
[`sjDingdanShuju.${key}.hasMore`]: hasMore, } catch (err) {
[`sjDingdanShuju.${key}.isLoading`]: false, console.error('加载订单失败', err);
isLoading: false, wx.showToast({ title: err.message || '加载失败', icon: 'none' });
isLoadingMore: false, this.setData({
scrollViewRefreshing: false [`sjDingdanShuju.${key}.isLoading`]: false,
}); isLoading: false,
this.refreshCurrentListView(); isLoadingMore: false,
} else { scrollViewRefreshing: false
throw new Error(res.data.msg || '加载失败'); });
} this.refreshCurrentListView();
} catch (err) { }
console.error('加载订单失败', err); },
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
this.setData({ // 刷新当前视图
[`sjDingdanShuju.${key}.isLoading`]: false, refreshCurrentListView() {
isLoading: false, const key = this.data.currentStatusKey;
isLoadingMore: false, const tabData = this.data.sjDingdanShuju[key];
scrollViewRefreshing: false this.setData({
}); currentList: tabData.list,
this.refreshCurrentListView(); hasMore: tabData.hasMore,
} isLoading: tabData.isLoading
}, });
},
// 刷新当前视图
refreshCurrentListView() { // 重置当前状态数据
const key = this.data.currentStatusKey; resetCurrentStatusData() {
const tabData = this.data.sjDingdanShuju[key]; const key = this.data.currentStatusKey;
this.setData({ this.setData({
currentList: tabData.list, [`sjDingdanShuju.${key}.list`]: [],
hasMore: tabData.hasMore, [`sjDingdanShuju.${key}.page`]: 1,
isLoading: tabData.isLoading [`sjDingdanShuju.${key}.hasMore`]: true,
}); [`sjDingdanShuju.${key}.isLoading`]: false
}, });
this.refreshCurrentListView();
// 重置当前状态数据 },
resetCurrentStatusData() {
const key = this.data.currentStatusKey; // 🆕 加载待结算数量
this.setData({ async loadPendingCount() {
[`sjDingdanShuju.${key}.list`]: [], try {
[`sjDingdanShuju.${key}.page`]: 1, const res = await request({
[`sjDingdanShuju.${key}.hasMore`]: true, url: '/dingdan/sjdingdanhq',
[`sjDingdanShuju.${key}.isLoading`]: false method: 'POST',
}); data: {
this.refreshCurrentListView(); zhuangtai_list: [8],
}, page: 1,
page_size: 1
// 🆕 加载待结算数量 }
async loadPendingCount() { });
try { if (res && res.data.code === 0) {
const res = await request({ this.setData({ pendingCount: res.data.data.pending_count || 0 });
url: '/dingdan/sjdingdanhq', }
method: 'POST', } catch (error) {
data: { console.error('加载待结算数量失败:', error);
zhuangtai_list: [8], }
page: 1, },
page_size: 1
} // 下拉刷新(由 scroll-view 触发)
}); onPullDownRefresh() {
if (res && res.data.code === 0) { if (this.data.isLoading) {
this.setData({ pendingCount: res.data.data.pending_count || 0 }); this.setData({ scrollViewRefreshing: false });
} return;
} catch (error) { }
console.error('加载待结算数量失败:', error); this.setData({ scrollViewRefreshing: true });
} this.resetCurrentStatusData();
}, this.loadCurrentStatusOrders(true);
},
// 下拉刷新(由 scroll-view 触发)
onPullDownRefresh() { // 上拉加载更多(由 scroll-view 触发)
if (this.data.isLoading) { onReachBottom() {
this.setData({ scrollViewRefreshing: false }); const key = this.data.currentStatusKey;
return; const tabData = this.data.sjDingdanShuju[key];
} if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ scrollViewRefreshing: true }); this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 });
this.resetCurrentStatusData(); this.loadCurrentStatusOrders(false);
this.loadCurrentStatusOrders(true); },
},
// 🆕 手动点击"加载更多"按钮
// 上拉加载更多(由 scroll-view 触发) onLoadMoreTap() {
onReachBottom() { const key = this.data.currentStatusKey;
const key = this.data.currentStatusKey; const tabData = this.data.sjDingdanShuju[key];
const tabData = this.data.sjDingdanShuju[key]; if (tabData.isLoading || !tabData.hasMore) return;
if (tabData.isLoading || !tabData.hasMore) return; this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 });
this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 }); this.loadCurrentStatusOrders(false);
this.loadCurrentStatusOrders(false); },
},
// 跳转订单详情
// 🆕 手动点击"加载更多"按钮 goToSjDingdanXiangqing(e) {
onLoadMoreTap() { const item = e.currentTarget.dataset.item;
const key = this.data.currentStatusKey; if (!item) return;
const tabData = this.data.sjDingdanShuju[key]; const dataStr = encodeURIComponent(JSON.stringify(item));
if (tabData.isLoading || !tabData.hasMore) return; wx.navigateTo({
this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 }); url: `/pages/merchant-order-detail/merchant-order-detail?dingdanData=${dataStr}`
this.loadCurrentStatusOrders(false); });
}, },
// 状态码转中文 // 图片加载失败
getZhuangtaiZh(zhuangtai) { onImageError() {}
const map = { }))
1: '已下单', 2: '进行中', 3: '已完成',
4: '退款中', 5: '已退款', 6: '退款失败',
7: '指定中', 8: '结算中'
};
return map[zhuangtai] || '未知状态';
},
// 跳转订单详情
goToSjDingdanXiangqing(e) {
const item = e.currentTarget.dataset.item;
if (!item) return;
const dataStr = encodeURIComponent(JSON.stringify(item));
wx.navigateTo({
url: `/pages/merchant-order-detail/merchant-order-detail?dingdanData=${dataStr}`
});
},
// 图片加载失败
onImageError() {}
});
+166 -200
View File
@@ -1,235 +1,205 @@
<!-- pages/shangjia-paihang/shangjia-paihang.wxml --> <!-- pages/merchant-rank/merchant-rank.wxml -->
<view class="shangjia-paihang-page"> <view class="rank-page">
<!-- 赛博朋克背景元素 --> <!-- 赛博朋克背景元素 -->
<view class="cyber-bg"> <view class="rank-cyber-bg">
<!-- 网格线 --> <!-- 网格线 -->
<view class="cyber-grid"></view> <view class="rank-cyber-grid"></view>
<!-- 扫描线 --> <!-- 扫描线 -->
<view class="cyber-scanline"></view> <view class="rank-cyber-scanline"></view>
<!-- 流动数据流 --> <!-- 流动数据流 -->
<view class="data-streams"> <view class="rank-data-streams">
<view class="stream stream-1"></view> <view class="rank-stream rank-stream--1"></view>
<view class="stream stream-2"></view> <view class="rank-stream rank-stream--2"></view>
<view class="stream stream-3"></view> <view class="rank-stream rank-stream--3"></view>
</view> </view>
<!-- 浮动粒子 --> <!-- 浮动粒子 -->
<view class="floating-particles"> <view class="rank-particle" wx:for="{{12}}" wx:key="index"></view>
<view class="particle" wx:for="{{12}}" wx:key="index"></view>
</view>
</view> </view>
<!-- 页面头部 --> <!-- 页面头部 -->
<view class="page-header"> <view class="rank-page-header">
<!-- 标题 --> <!-- 标题 -->
<view class="title-container"> <view class="rank-title-container">
<text class="main-title">商家排行榜</text> <text class="rank-main-title">商家排行榜</text>
<view class="title-line"></view> <view class="rank-title-line"></view>
<text class="sub-title">SHANGJIA RANKING</text> <text class="rank-sub-title">SHANGJIA RANKING</text>
</view> </view>
<!-- 切换按钮 --> <!-- 切换按钮 -->
<view class="toggle-container"> <view class="rank-toggle-container">
<view class="toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view> <view class="rank-toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view <view
class="toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}" class="rank-toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="1" data-leixing="1"
bindtap="qiehuangPaihangLeixing" bindtap="qiehuangPaihangLeixing"
> >
<text class="toggle-text">今日流水</text> <text class="rank-toggle-text">今日流水</text>
<view class="toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view> <view class="rank-toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
</view> </view>
<view <view
class="toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}" class="rank-toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="2" data-leixing="2"
bindtap="qiehuangPaihangLeixing" bindtap="qiehuangPaihangLeixing"
> >
<text class="toggle-text">今月流水</text> <text class="rank-toggle-text">今月流水</text>
<view class="toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view> <view class="rank-toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
</view> </view>
</view> </view>
</view> </view>
<!-- 加载状态 --> <!-- 加载状态 -->
<view wx:if="{{jiazaiZhuangtai}}" class="loading-state"> <view wx:if="{{jiazaiZhuangtai}}" class="rank-loading-state">
<view class="loading-content"> <view class="rank-loading-spinner">
<view class="loading-spinner"> <view class="rank-spinner-outer"></view>
<view class="spinner-outer"></view> <view class="rank-spinner-inner"></view>
<view class="spinner-inner"></view> <view class="rank-spinner-core"></view>
<view class="spinner-core"></view>
</view>
<text class="loading-text">数据加载中</text>
<text class="loading-sub">Connecting to server...</text>
</view> </view>
<text class="rank-loading-text">数据加载中</text>
<text class="rank-loading-sub">Connecting to server...</text>
</view> </view>
<!-- 排行榜内容 --> <!-- 排行榜内容 -->
<view wx:else class="ranking-content"> <view wx:else class="rank-content">
<!-- 虚拟数据提示 --> <!-- 虚拟数据提示 -->
<view wx:if="{{!xianshiZhenshiShuju}}" class="demo-notice"> <view wx:if="{{!xianshiZhenshiShuju}}" class="rank-demo-notice">
<view class="notice-icon">📡</view> <view class="rank-notice-icon">📡</view>
<view class="notice-content"> <view class="rank-notice-content">
<text class="notice-title">排行榜数据准备中</text> <text class="rank-notice-title">排行榜数据准备中</text>
<text class="notice-desc">当前显示为模拟数据,实时排名即将开放</text> <text class="rank-notice-desc">当前显示为模拟数据,实时排名即将开放</text>
</view> </view>
<view class="notice-glow"></view>
</view> </view>
<!-- ========== 前三名区域 ========== --> <!-- ========== 前三名区域 ========== -->
<view class="top-three-section" wx:if="{{qiansanMing.length > 0}}"> <view class="rank-top-three" wx:if="{{qiansanMing.length > 0}}">
<!-- 第二名 --> <!-- 第二名 -->
<view class="rank-card rank-2 {{flashCardIndex == 1 && flashCardType == 'top3' ? 'flash' : ''}}" <view class="rank-card rank-card--2 {{flashCardIndex == 1 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 1 && flashCardType == 'top3' ? flashAnimation : ''}}" animation="{{flashCardIndex == 1 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="1" data-type="top3" bindtap="handleCardTap"> data-index="1" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-silver"> <view class="rank-podium-base rank-podium-silver">
<view class="podium-glow"></view> <text class="rank-podium-number">2</text>
<text class="podium-number">2</text>
</view> </view>
<view class="rank-content"> <view class="rank-content">
<!-- 头像容器 --> <!-- 头像容器 -->
<view class="avatar-frame frame-silver"> <view class="rank-avatar-frame rank-frame-silver">
<view class="avatar-container"> <view class="rank-avatar-container">
<image class="avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image> <image class="rank-avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-silver"></view> <view class="rank-avatar-halo rank-halo-silver"></view>
<view class="avatar-sparkles"> <view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="sparkle s1"></view> <view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="sparkle s2"></view> <view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="sparkle s3"></view>
</view>
</view> </view>
<view class="rank-badge badge-silver">2</view> <view class="rank-badge rank-badge-silver">2</view>
</view> </view>
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="user-info-card"> <view class="rank-user-info-card">
<view class="info-header"> <text class="rank-user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
<text class="user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text> <view class="rank-info-body">
</view> <view class="rank-uid-row">
<view class="info-body"> <text class="rank-uid-label">ID:</text>
<view class="uid-row"> <text class="rank-uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
</view> </view>
<view class="income-row"> <view class="rank-income-row">
<text class="income-label">流水:</text> <text class="rank-income-label">流水:</text>
<view class="income-value"> <view class="rank-income-value">
<text class="currency">¥</text> <text class="rank-currency">¥</text>
<text class="amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0.00'}}</text> <text class="rank-amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0.00'}}</text>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<!-- 第一名 --> <!-- 第一名 -->
<view class="rank-card rank-1 {{flashCardIndex == 0 && flashCardType == 'top3' ? 'flash' : ''}}" <view class="rank-card rank-card--1 {{flashCardIndex == 0 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 0 && flashCardType == 'top3' ? flashAnimation : ''}}" animation="{{flashCardIndex == 0 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="0" data-type="top3" bindtap="handleCardTap"> data-index="0" data-type="top3" bindtap="handleCardTap">
<!-- 皇冠 --> <!-- 皇冠 -->
<view class="crown-container" animation="{{crownAnimation}}"> <view class="rank-crown-container" animation="{{crownAnimation}}">
<view class="crown"> <text class="rank-crown-icon">👑</text>
<text class="crown-icon">👑</text> <view class="rank-crown-glow"></view>
<view class="crown-rays">
<view class="ray ray-1"></view>
<view class="ray ray-2"></view>
<view class="ray ray-3"></view>
<view class="ray ray-4"></view>
<view class="ray ray-5"></view>
<view class="ray ray-6"></view>
</view>
<view class="crown-glow"></view>
</view>
</view> </view>
<view class="podium-base podium-gold"> <view class="rank-podium-base rank-podium-gold">
<view class="podium-glow"></view> <text class="rank-podium-number">1</text>
<text class="podium-number">1</text>
</view> </view>
<view class="rank-content"> <view class="rank-content">
<!-- 头像容器 --> <!-- 头像容器 -->
<view class="avatar-frame frame-gold"> <view class="rank-avatar-frame rank-frame-gold">
<view class="avatar-container"> <view class="rank-avatar-container">
<image class="avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image> <image class="rank-avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-gold"></view> <view class="rank-avatar-halo rank-halo-gold"></view>
<view class="avatar-sparkles"> <view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="sparkle s1"></view> <view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="sparkle s2"></view> <view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="sparkle s3"></view> <view class="rank-sparkle" style="bottom:15%;right:15%;animation-delay:1.5s"></view>
<view class="sparkle s4"></view>
</view>
</view> </view>
<view class="rank-badge badge-gold">1</view> <view class="rank-badge rank-badge-gold">1</view>
</view> </view>
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="user-info-card"> <view class="rank-user-info-card">
<view class="info-header"> <text class="rank-user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
<text class="user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text> <view class="rank-info-body">
</view> <view class="rank-uid-row">
<view class="info-body"> <text class="rank-uid-label">ID:</text>
<view class="uid-row"> <text class="rank-uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
</view> </view>
<view class="income-row"> <view class="rank-income-row">
<text class="income-label">流水:</text> <text class="rank-income-label">流水:</text>
<view class="income-value"> <view class="rank-income-value">
<text class="currency">¥</text> <text class="rank-currency">¥</text>
<text class="amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0.00'}}</text> <text class="rank-amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0.00'}}</text>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<!-- 第三名 --> <!-- 第三名 -->
<view class="rank-card rank-3 {{flashCardIndex == 2 && flashCardType == 'top3' ? 'flash' : ''}}" <view class="rank-card rank-card--3 {{flashCardIndex == 2 && flashCardType == 'top3' ? 'flash' : ''}}"
animation="{{flashCardIndex == 2 && flashCardType == 'top3' ? flashAnimation : ''}}" animation="{{flashCardIndex == 2 && flashCardType == 'top3' ? flashAnimation : ''}}"
data-index="2" data-type="top3" bindtap="handleCardTap"> data-index="2" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-bronze"> <view class="rank-podium-base rank-podium-bronze">
<view class="podium-glow"></view> <text class="rank-podium-number">3</text>
<text class="podium-number">3</text>
</view> </view>
<view class="rank-content"> <view class="rank-content">
<!-- 头像容器 --> <!-- 头像容器 -->
<view class="avatar-frame frame-bronze"> <view class="rank-avatar-frame rank-frame-bronze">
<view class="avatar-container"> <view class="rank-avatar-container">
<image class="avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image> <image class="rank-avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-bronze"></view> <view class="rank-avatar-halo rank-halo-bronze"></view>
<view class="avatar-sparkles"> <view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="sparkle s1"></view> <view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="sparkle s2"></view> <view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="sparkle s3"></view>
</view>
</view> </view>
<view class="rank-badge badge-bronze">3</view> <view class="rank-badge rank-badge-bronze">3</view>
</view> </view>
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="user-info-card"> <view class="rank-user-info-card">
<view class="info-header"> <text class="rank-user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
<text class="user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text> <view class="rank-info-body">
</view> <view class="rank-uid-row">
<view class="info-body"> <text class="rank-uid-label">ID:</text>
<view class="uid-row"> <text class="rank-uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
</view> </view>
<view class="income-row"> <view class="rank-income-row">
<text class="income-label">流水:</text> <text class="rank-income-label">流水:</text>
<view class="income-value"> <view class="rank-income-value">
<text class="currency">¥</text> <text class="rank-currency">¥</text>
<text class="amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0.00'}}</text> <text class="rank-amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0.00'}}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -237,54 +207,50 @@
</view> </view>
</view> </view>
</view> </view>
<!-- ========== 其他排名列表 ========== --> <!-- ========== 其他排名列表 ========== -->
<view class="other-ranks-section" wx:if="{{paihangShuju.length > 0}}"> <view wx:if="{{paihangShuju.length > 0}}">
<view class="section-title"> <view class="rank-section-title">
<view class="title-line"></view> <view class="rank-section-line"></view>
<text class="title-text">其他排名</text> <text class="rank-section-text">其他排名</text>
<view class="title-line"></view> <view class="rank-section-line"></view>
</view> </view>
<view class="rank-list"> <view class="rank-list-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
<view class="rank-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}" wx:for="{{paihangShuju}}" wx:key="index"
wx:for="{{paihangShuju}}" wx:key="index" animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}"
animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}" data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
<view class="rank-number">
<view class="rank-number"> <text class="rank-number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<text class="number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text> <view class="rank-number-glow"></view>
<view class="number-glow"></view> </view>
</view>
<view class="rank-list-avatar">
<view class="rank-avatar"> <image class="rank-list-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<image class="rank-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image> <view class="rank-list-avatar-halo"></view>
<view class="rank-avatar-halo"></view> </view>
</view>
<view class="rank-list-info">
<view class="rank-info"> <text class="rank-list-name">{{item.nicheng}}</text>
<text class="rank-name">{{item.nicheng}}</text> <text class="rank-list-uid">UID: {{item.uid}}</text>
<text class="rank-uid">UID: {{item.uid}}</text> </view>
</view>
<view class="rank-list-income">
<view class="rank-income"> <text class="rank-income-currency">¥</text>
<text class="income-currency">¥</text> <text class="rank-income-amount {{item.jineClass}}">{{item.jine}}</text>
<text class="income-amount {{item.jineClass}}">{{item.jine}}</text> <view class="rank-income-glow"></view>
<view class="income-glow"></view>
</view>
<view class="rank-hover"></view>
</view> </view>
</view> </view>
</view> </view>
<!-- 空状态 --> <!-- 空状态 -->
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="empty-state"> <view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="rank-empty-state">
<view class="empty-icon">🏆</view> <view class="rank-empty-icon">🏆</view>
<text class="empty-title">暂无排名数据</text> <text class="rank-empty-title">暂无排名数据</text>
<text class="empty-desc">成为第一个登榜的商家吧</text> <text class="rank-empty-desc">成为第一个登榜的商家吧</text>
</view> </view>
</view> </view>
<global-notification id="global-notification" /> <global-notification id="global-notification" />
</view> </view>
File diff suppressed because it is too large. Load diff
+77 -203
View File
@@ -1,21 +1,11 @@
// pages/shangjiazhongxin/shangjiazhongxin.js // pages/shangjiazhongxin/shangjiiazhongxin.js
import request from '../../utils/request.js'; import { createPage, ensureRoleOnCenterPage, lockPrimaryRole, parseSceneOptions } from '../../utils/base-page.js';
import popupService from '../../services/popupService.js';
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
ensureRoleOnCenterPage,
clearCacheAndEnterNormal,
} from '../../utils/role-tab-bar.js';
import { lockPrimaryRole } from '../../utils/primary-role.js';
import { ensureLogin } from '../../utils/login.js'; import { ensureLogin } from '../../utils/login.js';
const app = getApp(); const app = getApp();
Page({ Page(createPage({
data: { data: {
// 图片资源 // 图片资源
imgUrls: { imgUrls: {
@@ -35,14 +25,13 @@ Page({
iconRecharge: '', iconRecharge: '',
iconWithdraw: '', iconWithdraw: '',
iconRefresh: '', iconRefresh: '',
iconCopy: '', iconCopy: '',
avatarFrame: '', avatarFrame: '',
iconClear: '', iconClear: '',
}, },
// 商家状态 // 商家状态
isShangjia: false, isShangjia: false,
isLoading: false,
isAutoRegistering: false, isAutoRegistering: false,
// 用户基础信息 // 用户基础信息
@@ -62,52 +51,57 @@ Page({
// 交互控制 // 交互控制
inviteCode: '', inviteCode: '',
lastRefreshTime: 0,
canRefresh: true,
}, },
onLoad(options) { onLoad(options) {
this.initImageUrls(); const parsed = parseSceneOptions(options);
this.checkShangjiaStatus(); this.setupImageUrls();
this.checkRoleStatus();
const { inviteCode } = options || {}; const inviteCode = parsed.inviteCode;
if (inviteCode) { if (inviteCode) {
this.setData({ inviteCode }); this.setData({ inviteCode });
setTimeout(() => this.handleInviteCodeWithLoginCheck(inviteCode), 500); setTimeout(() => this.handleInviteCodeWithLoginCheck({
inviteCode,
role: 'shangjia',
statusKey: 'shangjiastatus',
alreadyMsg: '您已是商家',
onNeedLogin: (code) => {
this.setData({ inviteCode: code });
ensureLogin({ silent: false, page: this })
.then(() => {
if (!this.data.isShangjia) {
wx.showLoading({ title: '商家注册中...', mask: true });
this.setData({ isAutoRegistering: true });
this.onRegister();
}
})
.catch(() => {});
},
onAlreadyRegistered: () => {
wx.showToast({ title: '您已是商家', icon: 'success' });
ensureRoleOnCenterPage(this, 'shangjia');
},
}), 500);
} }
if (!app.globalData.hasShownPopupOnColdStart) { this.checkColdStartPopup('shangjiaduan');
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => popupService.checkAndShow(this, 'shangjiaduan'), 300);
}
},
onReady() {
if (isCenterPageActive(this, 'isShangjia', 'shangjiastatus')) {
ensureRoleOnCenterPage(this, 'shangjia');
}
}, },
onShow() { onShow() {
this.registerNotificationComponent(); this.registerNotificationComponent();
if (isCenterPageActive(this, 'isShangjia', 'shangjiastatus')) { if (this.data.isShangjia) {
if (!this.data.isShangjia) this.setData({ isShangjia: true });
ensureRoleOnCenterPage(this, 'shangjia'); ensureRoleOnCenterPage(this, 'shangjia');
this.refreshShangjiaInfo(); this.refreshShangjiaInfo();
} }
}, },
onHide() {
const popupComp = this.selectComponent('#popupNotice');
popupComp?.cleanup?.();
},
// 图片路径初始化 // 图片路径初始化
initImageUrls() { setupImageUrls() {
const ossBase = app.globalData.ossImageUrl || ''; const ossBase = app.globalData.ossImageUrl || '';
const imgDir = `${ossBase}beijing/shangjiaduan/`; const imgDir = `${ossBase}beijing/shangjiaduan/`;
this.setData({ this.setData({
imgUrls: { imgUrls: this.initImageUrls({
pageBg: `${imgDir}page_bg.png`, pageBg: `${imgDir}page_bg.png`,
avatarCardBg: `${imgDir}avatar_card_bg.png`, avatarCardBg: `${imgDir}avatar_card_bg.png`,
assetCardBg: `${imgDir}asset_card_bg.png`, assetCardBg: `${imgDir}asset_card_bg.png`,
@@ -128,16 +122,16 @@ Page({
avatarFrame: `${imgDir}avatar_frame.png`, avatarFrame: `${imgDir}avatar_frame.png`,
iconClear: `${ossBase}beijing/tubiao/grzx_qingchu.jpg`, iconClear: `${ossBase}beijing/tubiao/grzx_qingchu.jpg`,
iconSwitch: '/images/_exit.png', iconSwitch: '/images/_exit.png',
} })
}); });
}, },
// 商家状态检查 // 商家状态检查(覆盖基类 checkRoleStatus,增加 getShangjiaInfo 调用)
checkShangjiaStatus() { checkRoleStatus() {
try { try {
const shangjiastatus = wx.getStorageSync('shangjiastatus'); const shangjiastatus = wx.getStorageSync('shangjiastatus');
const uid = wx.getStorageSync('uid'); const uid = wx.getStorageSync('uid');
if (isRoleStatusActive(shangjiastatus)) { if (shangjiastatus) {
this.setData({ isShangjia: true, uid: uid || '' }); this.setData({ isShangjia: true, uid: uid || '' });
this.loadAvatar(); this.loadAvatar();
this.getShangjiaInfo(); this.getShangjiaInfo();
@@ -151,32 +145,20 @@ Page({
} }
}, },
loadAvatar() {
const { ossImageUrl = '', morentouxiang = 'avatar/default.jpg' } = app.globalData || {};
const touxiang = wx.getStorageSync('touxiang') || '';
let avatarUrl = '';
if (touxiang) {
avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
} else {
avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang);
}
this.setData({ avatarUrl });
},
// 获取商家信息 // 获取商家信息
async getShangjiaInfo() { async getShangjiaInfo() {
this.setData({ isLoading: true }); await this.loadData({
try { url: '/yonghu/shangjiaxinxi',
const res = await request({ url: '/yonghu/shangjiaxinxi', method: 'POST' }); method: 'POST',
if (res?.data?.code === 200) { successCode: 200,
const data = res.data.data; errorMsg: '获取商家信息失败',
onSuccess: (data) => {
// 更新全局缓存 // 更新全局缓存
app.globalData.shangjia = data; app.globalData.shangjia = data;
wx.setStorageSync('shangjiastatus', 1); wx.setStorageSync('shangjiastatus', 1);
const chenghaoList = data.chenghao_list || []; const chenghaoList = data.chenghao_list || [];
// 字段映射
this.setData({ this.setData({
nicheng: data.nicheng || '', nicheng: data.nicheng || '',
sjyue: data.sjyue || '0.00', sjyue: data.sjyue || '0.00',
@@ -189,133 +171,46 @@ Page({
chenghaoList: chenghaoList, chenghaoList: chenghaoList,
isLoading: false, isLoading: false,
}); });
if (this.data.isAutoRegistering) { if (this.data.isAutoRegistering) {
this.setData({ isAutoRegistering: false }); this.setData({ isAutoRegistering: false });
wx.showToast({ title: '商家注册成功', icon: 'success' }); wx.showToast({ title: '商家注册成功', icon: 'success' });
} }
} else { },
throw new Error(res?.data?.msg || '获取商家信息失败'); });
}
} catch (error) {
console.error('获取商家信息失败', error);
this.setData({ isLoading: false });
wx.showToast({ title: error.message || '加载失败', icon: 'none' });
}
}, },
refreshShangjiaInfo() { refreshShangjiaInfo() {
const now = Date.now(); this.throttledRefresh(() => this.getShangjiaInfo());
if (!this.data.canRefresh || now - this.data.lastRefreshTime < 3000) {
wx.showToast({ title: '操作过快', icon: 'none' });
return;
}
this.setData({ lastRefreshTime: now, canRefresh: false });
this.getShangjiaInfo();
setTimeout(() => this.setData({ canRefresh: true }), 3000);
},
// 邀请码注册
handleInviteCodeWithLoginCheck(inviteCode) {
const token = wx.getStorageSync('token');
const uid = wx.getStorageSync('uid');
if (!token || !uid) {
this.setData({ inviteCode });
ensureLogin({ silent: false, page: this })
.then(() => {
if (!this.data.isShangjia) {
wx.showLoading({ title: '商家注册中...', mask: true });
this.setData({ isAutoRegistering: true });
this.onRegister();
}
})
.catch(() => {});
} else {
if (!this.data.isShangjia) {
wx.showLoading({ title: '商家注册中...', mask: true });
this.setData({ isAutoRegistering: true });
this.onRegister();
} else {
wx.showToast({ title: '您已是商家', icon: 'success' });
ensureRoleOnCenterPage(this, 'shangjia');
}
}
}, },
async onRegister() { async onRegister() {
const { inviteCode } = this.data; const { inviteCode } = this.data;
if (!inviteCode) { await this.registerWithInviteCode({
wx.showToast({ title: '请输入邀请码', icon: 'none' }); inviteCode,
return; apiUrl: '/yonghu/shangjiahuce',
} role: 'shangjia',
if (inviteCode.length > 100) { statusKey: 'shangjiastatus',
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' }); successMsg: '商家注册成功',
return; onSuccess: (userData) => {
} app.globalData.shangjia = userData;
this.setData({ isLoading: true }); const chenghaoList = userData.chenghao_list || [];
try {
const res = await request({
url: '/yonghu/shangjiahuce',
method: 'POST',
data: { inviteCode }
});
if (res?.data?.code === 200) {
const data = res.data.data || {};
syncRoleStatuses(data);
syncProfileFields(data);
if (!Object.prototype.hasOwnProperty.call(data, 'shangjiastatus')) {
wx.setStorageSync('shangjiastatus', 1);
app.globalData.shangjiastatus = 1;
}
app.globalData.shangjia = data;
const chenghaoList = data.chenghao_list || [];
this.setData({ this.setData({
isShangjia: true, isShangjia: true,
nicheng: data.nicheng || '', nicheng: userData.nicheng || '',
sjyue: data.sjyue || '0.00', sjyue: userData.sjyue || '0.00',
fabu: data.fabu || 0, fabu: userData.fabu || 0,
tuikuan: data.tuikuan || 0, tuikuan: userData.tuikuan || 0,
jinriliushui: data.jinriliushui || '0.00', jinriliushui: userData.jinriliushui || '0.00',
jinyueliushui: data.jinyueliushui || '0.00', jinyueliushui: userData.jinyueliushui || '0.00',
jinridingdan: data.jinridingdan || 0, jinridingdan: userData.jinridingdan || 0,
chenghaoList: chenghaoList, chenghaoList: chenghaoList,
isLoading: false,
isAutoRegistering: false, isAutoRegistering: false,
}); });
this.loadAvatar(); this.loadAvatar();
ensureRoleOnCenterPage(this, 'shangjia'); ensureRoleOnCenterPage(this, 'shangjia');
lockPrimaryRole('shangjia'); lockPrimaryRole('shangjia');
wx.showToast({ title: '商家注册成功', icon: 'success' }); },
} else {
throw new Error(res?.data?.msg || '注册失败');
}
} catch (error) {
console.error('商家注册失败', error);
this.setData({ isLoading: false, isAutoRegistering: false });
wx.showToast({ title: error.message, icon: 'error' });
}
},
// 辅助功能
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp?.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
copyUid() {
if (!this.data.uid) {
wx.showToast({ title: 'UID不存在', icon: 'none' });
return;
}
wx.setClipboardData({
data: this.data.uid,
success: () => wx.showToast({ title: 'UID已复制', icon: 'success' }),
fail: () => wx.showToast({ title: '复制失败', icon: 'error' })
}); });
}, },
@@ -327,7 +222,7 @@ Page({
// 页面跳转 // 页面跳转
goToPaiDan() { wx.navigateTo({ url: '/pages/merchant-dispatch/merchant-dispatch' }); }, goToPaiDan() { wx.navigateTo({ url: '/pages/merchant-dispatch/merchant-dispatch' }); },
goToJiSuPaiDan() { goToJiSuPaiDan() {
wx.vibrateShort({ type: 'medium' }); wx.vibrateShort({ type: 'medium' });
wx.navigateTo({ url: '/pages/express-order/express-order' }); wx.navigateTo({ url: '/pages/express-order/express-order' });
}, },
@@ -336,33 +231,12 @@ Page({
goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }); }, goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }); },
goToRanking() { wx.navigateTo({ url: '/pages/fighter-rank/fighter-rank?type=shangjia' }); }, goToRanking() { wx.navigateTo({ url: '/pages/fighter-rank/fighter-rank?type=shangjia' }); },
goToMessages() { wx.navigateTo({ url: '/pages/merchant-msg/merchant-msg' }); }, goToMessages() { wx.navigateTo({ url: '/pages/merchant-msg/merchant-msg' }); },
goToKefu() { }, {
const { kefuConfig } = app.globalData; roleConfig: {
if (kefuConfig?.link && kefuConfig?.enterpriseId) { role: 'shangjia',
wx.openCustomerServiceChat({ statusKey: 'shangjiastatus',
extInfo: { url: kefuConfig.link }, dataFlag: 'isShangjia',
corpId: kefuConfig.enterpriseId, pageId: 'shangjiaduan',
fail: () => wx.showToast({ title: '客服暂不可用', icon: 'none' }) apiUrl: '/yonghu/shangjiaxinxi',
});
} else {
wx.showToast({ title: '客服配置未加载', icon: 'none' });
}
}, },
}));
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: (r) => {
if (r.confirm) clearCacheAndEnterNormal(this);
},
});
},
switchToNormal() {
lockPrimaryRole('normal');
wx.reLaunch({ url: '/pages/mine/mine' });
},
});
+394 -403
View File
@@ -1,403 +1,394 @@
// pages/messages/messages.js // pages/messages/messages.js
const app = getApp(); const app = getApp();
import { formatDate } from '../../static/lib/utils'; import { formatDate } from '../../static/lib/utils';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js'; import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js';
import { getLocalImUserId } from '../../utils/im-user.js'; import { getLocalImUserId } from '../../utils/im-user.js';
import { normalizeConversationsList } from '../../utils/conversation-display.js'; import { normalizeConversationsList } from '../../utils/conversation-display.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js'; import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getPrimaryRole } from '../../utils/primary-role.js'; import { getPrimaryRole } from '../../utils/primary-role.js';
const { jianquanxian } = require('../../utils/imAuth/jianquanxian'); import { createPage } from '../../utils/base-page.js';
const { jianquanxian } = require('../../utils/imAuth/jianquanxian');
Page({
data: { Page(createPage({
allConversations: [], data: {
filteredConversations: [], allConversations: [],
activeTab: 0, filteredConversations: [],
tabUnread: [0, 0, 0], activeTab: 0,
displayCount: 10, tabUnread: [0, 0, 0],
searchKeyword: '', displayCount: 10,
actionPopup: { visible: false, conversation: null }, searchKeyword: '',
currentUser: null, actionPopup: { visible: false, conversation: null },
notificationMuted: false, currentUser: null,
scrollHeight: 0 notificationMuted: false,
}, scrollHeight: 0
},
_permissionSeq: 0,
_peerProfileCache: {}, _permissionSeq: 0,
_peerProfileCache: {},
onLoad() {
this.calculateScrollHeight(); onLoad() {
const muted = wx.getStorageSync('notificationMuted') || false; this.calculateScrollHeight();
this.setData({ notificationMuted: muted }); const muted = wx.getStorageSync('notificationMuted') || false;
}, this.setData({ notificationMuted: muted });
},
onShow() {
if (!this.checkLoginStatus()) return; onShow() {
this.registerNotificationComponent(); if (!this.checkLoginStatus()) return;
this.checkPermissionAndAutoConnect(); this.registerNotificationComponent();
}, this.checkPermissionAndAutoConnect();
},
onHide() {},
onHide() {},
onUnload() {},
onUnload() {},
// ========== ==== ================ ==== ================ 鉴权检查 ==========
async checkPermissionAndAutoConnect() { // ========== ==== ================ ==== ================ 鉴权检查 ==========
const seq = ++this._permissionSeq; async checkPermissionAndAutoConnect() {
let quanxian; const seq = ++this._permissionSeq;
try { let quanxian;
quanxian = await jianquanxian(app); try {
} catch (e) { quanxian = await jianquanxian(app);
return; } catch (e) {
} return;
if (seq !== this._permissionSeq) return; }
if (seq !== this._permissionSeq) return;
if (!quanxian.allowed) {
const currentRole = app.globalData.currentRole || 'normal'; if (!quanxian.allowed) {
let content = '您没有权限使用消息功能'; const currentRole = app.globalData.currentRole || 'normal';
if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能'; let content = '您没有权限使用消息功能';
wx.showModal({ if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能';
title: '权限不足', wx.showModal({
content, title: '权限不足',
showCancel: false, content,
confirmText: '我知道了' showCancel: false,
}); confirmText: '我知道了'
return; });
} return;
this.autoConnect(); }
}, this.autoConnect();
},
// 登录状态检查
checkLoginStatus() { // 登录状态检查
const uid = wx.getStorageSync('uid'); checkLoginStatus() {
if (!uid) { const uid = wx.getStorageSync('uid');
wx.showToast({ title: '请先登录', icon: 'none' }); if (!uid) {
setTimeout(() => { wx.redirectTo({ url: '/pages/mine/mine' }); }, 1500); wx.showToast({ title: '请先登录', icon: 'none' });
return false; setTimeout(() => { wx.redirectTo({ url: '/pages/mine/mine' }); }, 1500);
} return false;
let currentUser = app.globalData.currentUser; }
if (!currentUser) { let currentUser = app.globalData.currentUser;
currentUser = { if (!currentUser) {
id: getLocalImUserId(app) || uid, currentUser = {
name: wx.getStorageSync('nicheng') || app.globalData.dashouNicheng || app.globalData.nicheng || ('用户' + uid.substring(0, 6)), id: getLocalImUserId(app) || uid,
avatar: resolveAvatarFromStorage(app), name: wx.getStorageSync('nicheng') || app.globalData.dashouNicheng || app.globalData.nicheng || ('用户' + uid.substring(0, 6)),
}; avatar: resolveAvatarFromStorage(app),
app.globalData.currentUser = currentUser; };
} app.globalData.currentUser = currentUser;
this.setData({ currentUser }); }
return true; this.setData({ currentUser });
}, return true;
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification'); autoConnect() {
if (notificationComp && notificationComp.showNotification) { const targetUserId = getLocalImUserId(app);
app.globalData.globalNotification = { if (!targetUserId) return;
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification(), if (app.ensureConnection) app.ensureConnection();
}; reconnectForRole(getPrimaryRole(app));
}
}, const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
autoConnect() { const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
const targetUserId = getLocalImUserId(app); if (currentUserId === targetUserId) {
if (!targetUserId) return; this.setupConversationListener();
this.loadConversations();
if (app.ensureConnection) app.ensureConnection(); return;
reconnectForRole(getPrimaryRole(app)); }
try {
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected'; wx.goEasy.disconnect();
if (status === 'connected' || status === 'reconnected') { } catch (e) {}
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null; }
if (currentUserId === targetUserId) {
this.setupConversationListener(); this.connectGoEasy(targetUserId);
this.loadConversations(); },
return;
} connectGoEasy(userId) {
try { const { currentUser } = this.data;
wx.goEasy.disconnect(); if (!currentUser) return;
} catch (e) {} wx.goEasy.connect({
} id: userId,
data: {
this.connectGoEasy(targetUserId); name: currentUser.name,
}, avatar: resolveAvatarUrl(currentUser.avatar, app),
},
connectGoEasy(userId) { onSuccess: () => {
const { currentUser } = this.data; try {
if (!currentUser) return; wx.setStorageSync('savedGoEasyConnection', JSON.stringify({
wx.goEasy.connect({ userId,
id: userId, identityType: app.globalData.currentRole,
data: { lastConnectTime: Date.now(),
name: currentUser.name, status: 'connected',
avatar: resolveAvatarUrl(currentUser.avatar, app), }));
}, } catch (e) {}
onSuccess: () => { this.setupConversationListener();
try { this.loadConversations();
wx.setStorageSync('savedGoEasyConnection', JSON.stringify({ if (app.registerNotification) {
userId, }
identityType: app.globalData.currentRole, },
lastConnectTime: Date.now(), onFailed: (error) => {
status: 'connected', if (error.code === 408) {
})); this.setupConversationListener();
} catch (e) {} this.loadConversations();
this.setupConversationListener(); } else {
this.loadConversations(); wx.showToast({ title: '连接失败,请重试', icon: 'none' });
if (app.registerNotification) { }
} },
}, });
onFailed: (error) => { },
if (error.code === 408) {
this.setupConversationListener(); setupConversationListener() {
this.loadConversations(); if (!wx.goEasy || !wx.goEasy.im) return;
} else { if (this.conversationsUpdatedListener) {
wx.showToast({ title: '连接失败,请重试', icon: 'none' }); wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
} }
}, this.conversationsUpdatedListener = (result) => {
}); const content = result?.content || result;
}, this.renderConversations(content);
};
setupConversationListener() { wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
if (!wx.goEasy || !wx.goEasy.im) return; },
if (this.conversationsUpdatedListener) {
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener); loadConversations() {
} if (!wx.goEasy || !wx.goEasy.im) return;
this.conversationsUpdatedListener = (result) => { wx.goEasy.im.latestConversations({
const content = result?.content || result; onSuccess: (result) => {
this.renderConversations(content); const content = result?.content || result;
}; this.renderConversations(content);
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener); },
}, onFailed: () => {
wx.showToast({ title: '获取会话失败', icon: 'none' });
loadConversations() { },
if (!wx.goEasy || !wx.goEasy.im) return; });
wx.goEasy.im.latestConversations({ },
onSuccess: (result) => {
const content = result?.content || result; // 会话渲染与排序
this.renderConversations(content); renderConversations(content) {
}, const conversations = (content && content.conversations) ? content.conversations : [];
onFailed: () => { if (!conversations.length) {
wx.showToast({ title: '获取会话失败', icon: 'none' }); this.setData({ allConversations: [] }, () => this.applyFilter());
}, return;
}); }
}, let list = normalizeConversationsList(conversations, app, this._peerProfileCache);
list.forEach(item => {
// 会话渲染与排序 if (!item.lastMessage) {
renderConversations(content) { item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 };
const conversations = (content && content.conversations) ? content.conversations : []; }
if (!conversations.length) { if (item.lastMessage.timestamp) {
this.setData({ allConversations: [] }, () => this.applyFilter()); item.lastMessage.date = formatDate(item.lastMessage.timestamp);
return; }
} });
let list = normalizeConversationsList(conversations, app, this._peerProfileCache);
list.forEach(item => { list.sort((a, b) => {
if (!item.lastMessage) { if (a.top && !b.top) return -1;
item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 }; if (!a.top && b.top) return 1;
} if (a.unread > 0 && b.unread <= 0) return -1;
if (item.lastMessage.timestamp) { if (a.unread <= 0 && b.unread > 0) return 1;
item.lastMessage.date = formatDate(item.lastMessage.timestamp); const ta = (a.lastMessage && a.lastMessage.timestamp) || 0;
} const tb = (b.lastMessage && b.lastMessage.timestamp) || 0;
}); return tb - ta;
});
list.sort((a, b) => {
if (a.top && !b.top) return -1; this.setData({ allConversations: list, displayCount: 10 }, () => {
if (!a.top && b.top) return 1; this.applyFilter();
if (a.unread > 0 && b.unread <= 0) return -1; });
if (a.unread <= 0 && b.unread > 0) return 1;
const ta = (a.lastMessage && a.lastMessage.timestamp) || 0; const unreadTotal = content.unreadTotal !== undefined
const tb = (b.lastMessage && b.lastMessage.timestamp) || 0; ? content.unreadTotal
return tb - ta; : list.reduce((sum, c) => sum + (c.unread || 0), 0);
}); if (app.updateUnreadCount) {
app.updateUnreadCount(unreadTotal);
this.setData({ allConversations: list, displayCount: 10 }, () => { } else if (app.emitEvent) {
this.applyFilter(); app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' });
}); }
const unreadTotal = content.unreadTotal !== undefined // 重新订阅群组
? content.unreadTotal this.subscribeGroupsIfNeeded(list);
: list.reduce((sum, c) => sum + (c.unread || 0), 0); },
if (app.updateUnreadCount) {
app.updateUnreadCount(unreadTotal); /**
} else if (app.emitEvent) { * 群组订阅,每次获取群组列表时直接订阅保证消息不丢失
app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' }); */
} subscribeGroupsIfNeeded(conversations) {
if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return;
// 重新订阅群组
this.subscribeGroupsIfNeeded(list); const groupIds = conversations
}, .filter(c => c.type === 'group' && c.groupId)
.map(c => c.groupId);
/**
* 群组订阅,每次获取群组列表时直接订阅保证消息不丢失 if (groupIds.length === 0) return;
*/
subscribeGroupsIfNeeded(conversations) { // 不再判断缓存,直接订阅当前所有群组
if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return; wx.goEasy.im.subscribeGroup({
groupIds: groupIds,
const groupIds = conversations onSuccess: () => {
.filter(c => c.type === 'group' && c.groupId) // 可选的日志输出,不影响功能
.map(c => c.groupId); // console.log('群组订阅成功', groupIds);
},
if (groupIds.length === 0) return; onFailed: (error) => {
console.error('群组订阅失败:', error);
// 不再判断缓存,直接订阅当前所有群组 }
wx.goEasy.im.subscribeGroup({ });
groupIds: groupIds, },
onSuccess: () => {
// 可选的日志输出,不影响功能 applyFilter() {
// console.log('群组订阅成功', groupIds); const { allConversations, activeTab, searchKeyword } = this.data;
}, let filtered = [];
onFailed: (error) => { if (activeTab === 0) {
console.error('群组订阅失败:', error); filtered = allConversations.filter(c => c.type === 'group' && c.data && c.data.orderId);
} } else if (activeTab === 1) {
}); filtered = allConversations.filter(c => c.type === 'private');
}, } else if (activeTab === 2) {
filtered = allConversations.filter(c => c.type === 'cs');
applyFilter() { }
const { allConversations, activeTab, searchKeyword } = this.data;
let filtered = []; if (searchKeyword.trim()) {
if (activeTab === 0) { const kw = searchKeyword.trim().toLowerCase();
filtered = allConversations.filter(c => c.type === 'group' && c.data && c.data.orderId); filtered = filtered.filter(c => {
} else if (activeTab === 1) { const name = (c.data && c.data.name || '').toLowerCase();
filtered = allConversations.filter(c => c.type === 'private'); const orderId = (c.data && c.data.orderId || '').toLowerCase();
} else if (activeTab === 2) { const id = (c.userId || c.groupId || '').toLowerCase();
filtered = allConversations.filter(c => c.type === 'cs'); return name.includes(kw) || id.includes(kw) || orderId.includes(kw);
} });
}
if (searchKeyword.trim()) {
const kw = searchKeyword.trim().toLowerCase(); const tabUnread = [0, 0, 0];
filtered = filtered.filter(c => { allConversations.forEach(c => {
const name = (c.data && c.data.name || '').toLowerCase(); if (c.type === 'group' && c.data && c.data.orderId) tabUnread[0] += c.unread || 0;
const orderId = (c.data && c.data.orderId || '').toLowerCase(); else if (c.type === 'private') tabUnread[1] += c.unread || 0;
const id = (c.userId || c.groupId || '').toLowerCase(); else if (c.type === 'cs') tabUnread[2] += c.unread || 0;
return name.includes(kw) || id.includes(kw) || orderId.includes(kw); });
});
} this.setData({ filteredConversations: filtered, tabUnread });
},
const tabUnread = [0, 0, 0];
allConversations.forEach(c => { switchTab(e) {
if (c.type === 'group' && c.data && c.data.orderId) tabUnread[0] += c.unread || 0; const index = parseInt(e.currentTarget.dataset.index);
else if (c.type === 'private') tabUnread[1] += c.unread || 0; this.setData({ activeTab: index, displayCount: 10, searchKeyword: '' }, () => this.applyFilter());
else if (c.type === 'cs') tabUnread[2] += c.unread || 0; },
});
onSearchInput(e) {
this.setData({ filteredConversations: filtered, tabUnread }); this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter());
}, },
switchTab(e) { onScrollToLower() {
const index = parseInt(e.currentTarget.dataset.index); const { displayCount, filteredConversations } = this.data;
this.setData({ activeTab: index, displayCount: 10, searchKeyword: '' }, () => this.applyFilter()); if (displayCount < filteredConversations.length) {
}, this.setData({ displayCount: Math.min(displayCount + 10, filteredConversations.length) });
}
onSearchInput(e) { },
this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter());
}, onRefresh() {
this.loadConversations();
onScrollToLower() { },
const { displayCount, filteredConversations } = this.data;
if (displayCount < filteredConversations.length) { chat(e) {
this.setData({ displayCount: Math.min(displayCount + 10, filteredConversations.length) }); const conversation = e.currentTarget.dataset.conversation;
} if (!conversation) return;
},
if (conversation.type === 'private') {
onRefresh() { const param = {
this.loadConversations(); toUserId: conversation.userId,
}, toName: conversation.data.name || '用户',
toAvatar: resolveAvatarUrl(conversation.data.avatar, app),
chat(e) { };
const conversation = e.currentTarget.dataset.conversation; wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) });
if (!conversation) return; } else if (conversation.type === 'group') {
const param = {
if (conversation.type === 'private') { groupId: conversation.groupId,
const param = { orderId: conversation.data.orderId || '',
toUserId: conversation.userId, groupName: conversation.data.name,
toName: conversation.data.name || '用户', groupAvatar: conversation.data.avatar,
toAvatar: resolveAvatarUrl(conversation.data.avatar, app), isCross: conversation.data.isCross || 0
}; };
wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) }); wx.navigateTo({ url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'group') { } else if (conversation.type === 'cs') {
const param = { const param = { teamId: conversation.teamId || conversation.id };
groupId: conversation.groupId, wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
orderId: conversation.data.orderId || '', }
groupName: conversation.data.name, },
groupAvatar: conversation.data.avatar,
isCross: conversation.data.isCross || 0 chatKefu() {
}; const param = { teamId: 'support_team' };
wx.navigateTo({ url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)) }); wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'cs') { },
const param = { teamId: conversation.teamId || conversation.id };
wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) }); showAction(e) {
} const conversation = e.currentTarget.dataset.conversation;
}, this.setData({ ['actionPopup.conversation']: conversation, ['actionPopup.visible']: true });
},
chatKefu() { closeMask() {
const param = { teamId: 'support_team' }; this.setData({ ['actionPopup.visible']: false });
wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) }); },
}, topConversation() {
let conversation = this.data.actionPopup.conversation;
showAction(e) { if (!conversation) return;
const conversation = e.currentTarget.dataset.conversation; const top = !conversation.top;
this.setData({ ['actionPopup.conversation']: conversation, ['actionPopup.visible']: true }); wx.showLoading({ title: '处理中...' });
}, if (conversation.type === 'private') {
closeMask() { wx.goEasy.im.topPrivateConversation({
this.setData({ ['actionPopup.visible']: false }); userId: conversation.userId, top,
}, onSuccess: () => {
topConversation() { wx.hideLoading(); this.loadConversations();
let conversation = this.data.actionPopup.conversation; wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
if (!conversation) return; },
const top = !conversation.top; onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
wx.showLoading({ title: '处理中...' }); });
if (conversation.type === 'private') { } else {
wx.goEasy.im.topPrivateConversation({ wx.goEasy.im.topGroupConversation({
userId: conversation.userId, top, groupId: conversation.groupId, top,
onSuccess: () => { onSuccess: () => {
wx.hideLoading(); this.loadConversations(); wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' }); wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
}, },
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); } onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
}); });
} else { }
wx.goEasy.im.topGroupConversation({ this.closeMask();
groupId: conversation.groupId, top, },
onSuccess: () => { removeConversation() {
wx.hideLoading(); this.loadConversations(); let conversation = this.data.actionPopup.conversation;
wx.showToast({ title: top ? '已置顶' : '已取消置顶' }); if (!conversation) return;
}, wx.showLoading({ title: '删除中...' });
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); } wx.goEasy.im.removeConversation({
}); conversation: conversation,
} onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: '已删除' }); },
this.closeMask(); onFailed: () => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); }
}, });
removeConversation() { this.closeMask();
let conversation = this.data.actionPopup.conversation; },
if (!conversation) return;
wx.showLoading({ title: '删除中...' }); toggleNotification() {
wx.goEasy.im.removeConversation({ const newMuted = !this.data.notificationMuted;
conversation: conversation, this.setData({ notificationMuted: newMuted });
onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: '已删除' }); }, wx.setStorageSync('notificationMuted', newMuted);
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); } wx.showToast({ title: newMuted ? '已静音' : '已开启通知', icon: 'none' });
}); },
this.closeMask();
}, calculateScrollHeight() {
const systemInfo = wx.getSystemInfoSync();
toggleNotification() { const windowHeight = systemInfo.windowHeight;
const newMuted = !this.data.notificationMuted; const windowWidth = systemInfo.windowWidth;
this.setData({ notificationMuted: newMuted }); const rpxRatio = 750 / windowWidth;
wx.setStorageSync('notificationMuted', newMuted); const headerHeight = 150 / rpxRatio;
wx.showToast({ title: newMuted ? '已静音' : '已开启通知', icon: 'none' }); const tabBarHeight = 130 / rpxRatio;
}, const safeAreaBottom = systemInfo.safeArea ? (windowHeight - systemInfo.safeArea.bottom) : 0;
this.setData({ scrollHeight: windowHeight - headerHeight - tabBarHeight - safeAreaBottom });
calculateScrollHeight() { },
const systemInfo = wx.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight; onAvatarError(e) {
const windowWidth = systemInfo.windowWidth; const index = e.currentTarget.dataset.index;
const rpxRatio = 750 / windowWidth; const def = getDefaultAvatarUrl(app);
const headerHeight = 150 / rpxRatio; const key = `filteredConversations[${index}].data.avatar`;
const tabBarHeight = 130 / rpxRatio; this.setData({ [key]: def });
const safeAreaBottom = systemInfo.safeArea ? (windowHeight - systemInfo.safeArea.bottom) : 0; }
this.setData({ scrollHeight: windowHeight - headerHeight - tabBarHeight - safeAreaBottom }); }))
},
onAvatarError(e) {
const index = e.currentTarget.dataset.index;
const def = getDefaultAvatarUrl(app);
const key = `filteredConversations[${index}].data.avatar`;
this.setData({ [key]: def });
}
});
+247 -31
View File
@@ -1,38 +1,254 @@
.msg-page { background: #f5f5f5; } /* pages/messages/messages.wxss */
.header { display: flex; align-items: center; padding: 20rpx 24rpx; background: #fff; border-bottom: 1rpx solid #e8e8e8; } .msg-page {
.search-bar { flex: 1; } background: #f5f5f5;
.search-input { height: 68rpx; } min-height: 100vh;
.search-icon { font-size: 32rpx; margin-left: 10rpx; color: #999; } }
.notify-switch { margin-left: 20rpx; }
.notify-text { font-size: 26rpx; color: #666; padding: 10rpx 16rpx; background: #f0f0f0; border-radius: 30rpx; }
.tab-row { border-bottom: 1rpx solid #e8e8e8; } /* 顶部搜索 + 通知 */
/* .tab-item 已由全局 .tab-item 覆盖 */ .header {
.tab-item.active { color: #07c160; font-weight: 600; } display: flex;
.tab-item.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 60rpx; height: 6rpx; background: #07c160; border-radius: 3rpx; } align-items: center;
.tab-badge { border-radius: 20rpx; padding: 2rpx 12rpx; margin-left: 8rpx; } padding: 20rpx 24rpx;
background: #fff;
}
.conversation-list { background: #fff; } .search-bar {
.conversation-item { display: flex; align-items: center; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; } flex: 1;
.has-unread { background: #fafafa; } display: flex;
.avatar { width: 96rpx; height: 96rpx; border-radius: 12rpx; margin-right: 20rpx; background: #eee; } align-items: center;
.info { flex: 1; } background: #f5f5f5;
.top-line { display: flex; justify-content: space-between; margin-bottom: 8rpx; } border-radius: 36rpx;
.name { font-size: 30rpx; color: #1a1a1a; font-weight: 500; } padding: 0 24rpx;
.time { font-size: 22rpx; color: #999; } height: 68rpx;
.bottom-line { display: flex; justify-content: space-between; align-items: center; } }
.last-msg { font-size: 26rpx; color: #888; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.unread-dot { background: #fa5151; color: #fff; font-size: 20rpx; border-radius: 50%; min-width: 36rpx; height: 36rpx; line-height: 36rpx; text-align: center; margin-left: 12rpx; } .search-input {
.load-tip { text-align: center; padding: 24rpx; color: #999; font-size: 24rpx; } flex: 1;
.empty { text-align: center; padding: 200rpx 0; color: #bbb; font-size: 28rpx; } height: 68rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
font-size: 28rpx;
margin-left: 10rpx;
color: #999;
}
.notify-switch {
margin-left: 20rpx;
flex-shrink: 0;
}
.notify-text {
font-size: 24rpx;
color: #666;
padding: 10rpx 20rpx;
background: #f0f0f0;
border-radius: 30rpx;
}
/* TAB 栏 */
.tab-row {
display: flex;
background: #fff;
border-bottom: 1rpx solid #e8e8e8;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
position: relative;
font-size: 28rpx;
color: #666;
}
.tab-item.active {
color: #07c160;
font-weight: 600;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 6rpx;
background: #07c160;
border-radius: 3rpx;
}
.tab-label {
line-height: 1;
}
.tab-badge {
background: #fa5151;
color: #fff;
font-size: 20rpx;
border-radius: 20rpx;
padding: 2rpx 10rpx;
margin-left: 8rpx;
line-height: 1.2;
}
/* 会话列表 */
.conversation-list {
background: #fff;
}
.conversation-item {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.15s;
}
.conversation-item:active {
background: #f5f5f5;
}
.has-unread {
background: #fafafa;
}
.avatar {
width: 96rpx;
height: 96rpx;
border-radius: 12rpx;
margin-right: 20rpx;
background: #eee;
flex-shrink: 0;
}
.info {
flex: 1;
min-width: 0;
}
.top-line {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.name {
font-size: 30rpx;
color: #1a1a1a;
font-weight: 500;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
font-size: 22rpx;
color: #999;
flex-shrink: 0;
margin-left: 16rpx;
}
.bottom-line {
display: flex;
justify-content: space-between;
align-items: center;
}
.last-msg {
font-size: 26rpx;
color: #888;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.unread-dot {
background: #fa5151;
color: #fff;
font-size: 20rpx;
border-radius: 50%;
min-width: 36rpx;
height: 36rpx;
line-height: 36rpx;
text-align: center;
margin-left: 12rpx;
flex-shrink: 0;
}
.load-tip {
text-align: center;
padding: 24rpx;
color: #999;
font-size: 24rpx;
}
.empty {
text-align: center;
padding: 200rpx 0;
color: #bbb;
font-size: 28rpx;
}
/* 客服入口 */ /* 客服入口 */
.kefu-entry { text-align: center; padding: 100rpx 0; } .kefu-entry {
.kefu-btn { width: 300rpx; background: #07c160; color: #fff; border-radius: 40rpx; font-size: 28rpx; } text-align: center;
padding: 100rpx 0;
}
.kefu-btn {
width: 300rpx;
background: #07c160;
color: #fff;
border-radius: 40rpx;
font-size: 28rpx;
}
/* 操作弹窗 */ /* 操作弹窗 */
.action-mask { background: rgba(0,0,0,0.4); z-index: 10000; } .action-mask {
.action-sheet { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 24rpx 24rpx 0 0; padding: 20rpx 20rpx 150rpx 20rpx; } position: fixed;
.action-btn { text-align: center; padding: 28rpx 0; font-size: 32rpx; border-bottom: 1rpx solid #eee; } top: 0;
.cancel { color: #999; margin-top: 12rpx; border-bottom: none; } left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 10000;
display: flex;
align-items: flex-end;
animation: fadeIn 0.2s ease-out;
}
.action-sheet {
width: 100%;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 20rpx 20rpx 60rpx;
animation: slideInUp 0.25s ease-out;
}
.action-btn {
text-align: center;
padding: 28rpx 0;
font-size: 32rpx;
color: #333;
border-bottom: 1rpx solid #eee;
}
.action-btn:active {
background: #f5f5f5;
}
.cancel {
color: #999;
margin-top: 12rpx;
border-bottom: none;
}
+140 -169
View File
@@ -1,169 +1,140 @@
// pages/mine/mine.js // pages/mine/mine.js
const app = getApp(); const app = getApp();
import { ensureLogin } from '../../utils/login'; import { ensureLogin } from '../../utils/login';
import { import {
backfillUserProfileCache, backfillUserProfileCache,
getUserProfileForDisplay, getUserProfileForDisplay,
clearCacheAndEnterNormal, isRoleStatusActive,
isRoleStatusActive, ensureRoleOnCenterPage,
ensureRoleOnCenterPage, } from '../../utils/role-tab-bar';
} from '../../utils/role-tab-bar'; import { enterLockedRole } from '../../utils/primary-role.js';
import { enterLockedRole } from '../../utils/primary-role.js'; import { createPage } from '../../utils/base-page.js';
Page({ Page(createPage({
data: { data: {
avatarUrl: '', avatarUrl: '',
userNicheng: '', userNicheng: '',
userUid: '', userUid: '',
showLoginBtn: false, showLoginBtn: false,
orderCounts: { daifuwu:0, fuwuzhong:0, yiwancheng:0, yituikuan:0 }, orderCounts: { daifuwu:0, fuwuzhong:0, yiwancheng:0, yituikuan:0 },
// 图标 // 图标
settingIcon: '', daifuwuIcon: '', fuwuzhongIcon: '', yiwanchengIcon: '', yituikuanIcon: '', settingIcon: '', daifuwuIcon: '', fuwuzhongIcon: '', yiwanchengIcon: '', yituikuanIcon: '',
dashouIcon: '', shangjiaIcon: '', guanshiIcon: '', kefuIcon: '', dashouIcon: '', shangjiaIcon: '', guanshiIcon: '', kefuIcon: '',
zuzhangIcon: '', guanzhualongIcon: '', arrowRightIcon: '', qingchuIcon: '', zuzhangIcon: '', guanzhualongIcon: '', arrowRightIcon: '', qingchuIcon: '',
dashouCertified: false, dashouCertified: false,
shangjiaCertified: false, shangjiaCertified: false,
bottomSafePadding: 0 bottomSafePadding: 0
}, },
onLoad() { onLoad() {
this.initIconUrls() this.initIconUrls()
this.loadUserData() this.loadUserData()
this.setBottomSafe() this.setBottomSafe()
}, },
onShow() { onShow() {
this.initIconUrls(); this.initIconUrls();
this.registerNotification(); this.registerNotificationComponent();
this.checkAvatarPrompt(); this.checkAvatarPrompt();
backfillUserProfileCache(app); backfillUserProfileCache(app);
this.loadUserData(); this.loadUserData();
this.updateOrderCounts(); this.updateOrderCounts();
}, },
setBottomSafe() { setBottomSafe() {
const sys = wx.getSystemInfoSync() const sys = wx.getSystemInfoSync()
const safeBottom = sys.screenHeight - sys.safeArea.bottom // 底部安全区高度(px) const safeBottom = sys.screenHeight - sys.safeArea.bottom // 底部安全区高度(px)
const tabBarHeight = (sys.windowWidth / 750) * 100 // 100rpx 转为 px const tabBarHeight = (sys.windowWidth / 750) * 100 // 100rpx 转为 px
this.setData({ this.setData({
bottomSafePadding: safeBottom + tabBarHeight + 4 // 4px 微调,确保不留缝隙 bottomSafePadding: safeBottom + tabBarHeight + 4 // 4px 微调,确保不留缝隙
}) })
}, },
registerNotification() { checkAvatarPrompt() {
const comp = this.selectComponent('#global-notification') const ds = wx.getStorageSync('dashoustatus')
if (comp?.showNotification) { const tx = wx.getStorageSync('touxiang') || ''
app.globalData.globalNotification = { if (ds === 1 && (!tx || tx === 'a_long/morentouxiang.jpg')) {
show: d => comp.showNotification(d), wx.showModal({
hide: () => comp.hideNotification() title: '提示', content: '请先设置头像',
} confirmText: '去设置', confirmColor: '#C9A962',
} success: r => r.confirm && wx.navigateTo({ url: '/pages/edit/edit' })
}, })
}
checkAvatarPrompt() { },
const ds = wx.getStorageSync('dashoustatus')
const tx = wx.getStorageSync('touxiang') || '' initIconUrls() {
if (ds === 1 && (!tx || tx === 'a_long/morentouxiang.jpg')) { const base = (app.globalData.ossImageUrl || '') + '/beijing/tubiao/'
wx.showModal({ this.setData({
title: '提示', content: '请先设置头像', settingIcon: base + 'grzx_shezhi.jpg',
confirmText: '去设置', confirmColor: '#C9A962', daifuwuIcon: base + 'grzx_daifuwu.jpg',
success: r => r.confirm && wx.navigateTo({ url: '/pages/edit/edit' }) fuwuzhongIcon: base + 'grzx_fuwuzhong.jpg',
}) yiwanchengIcon: base + 'grzx_yiwancheng.jpg',
} yituikuanIcon: base + 'grzx_yituikuan.jpg',
}, dashouIcon: base + 'grzx_dashou.jpg',
shangjiaIcon: base + 'grzx_shangjia.jpg',
initIconUrls() { guanshiIcon: base + 'grzx_guanshi.jpg',
const base = (app.globalData.ossImageUrl || '') + '/beijing/tubiao/' kefuIcon: base + 'grzx_kefu.jpg',
this.setData({ zuzhangIcon: base + 'grzx_zuzhang.jpg',
settingIcon: base + 'grzx_shezhi.jpg', guanzhualongIcon: base + 'grzx_guanzhualong.jpg',
daifuwuIcon: base + 'grzx_daifuwu.jpg', arrowRightIcon: base + 'grzx_arrow_right.jpg',
fuwuzhongIcon: base + 'grzx_fuwuzhong.jpg', qingchuIcon: base + 'grzx_qingchu.jpg',
yiwanchengIcon: base + 'grzx_yiwancheng.jpg', kaoheguanIcon: base + 'kaoheguan.png',
yituikuanIcon: base + 'grzx_yituikuan.jpg', })
dashouIcon: base + 'grzx_dashou.jpg', },
shangjiaIcon: base + 'grzx_shangjia.jpg',
guanshiIcon: base + 'grzx_guanshi.jpg', loadUserData() {
kefuIcon: base + 'grzx_kefu.jpg', const profile = getUserProfileForDisplay(app);
zuzhangIcon: base + 'grzx_zuzhang.jpg', this.setData({
guanzhualongIcon: base + 'grzx_guanzhualong.jpg', avatarUrl: profile.avatarUrl,
arrowRightIcon: base + 'grzx_arrow_right.jpg', userNicheng: profile.nick,
qingchuIcon: base + 'grzx_qingchu.jpg', userUid: profile.uid,
kaoheguanIcon: base + 'kaoheguan.png', showLoginBtn: !profile.isLoggedIn,
}) dashouCertified: isRoleStatusActive(wx.getStorageSync('dashoustatus')),
}, shangjiaCertified: isRoleStatusActive(wx.getStorageSync('shangjiastatus')),
});
loadUserData() { },
const profile = getUserProfileForDisplay(app);
this.setData({ updateOrderCounts() {
avatarUrl: profile.avatarUrl, const c = app.globalData.dingdanTiaoshu || {}
userNicheng: profile.nick, this.setData({ orderCounts: {
userUid: profile.uid, daifuwu: c.daifuwu||0, fuwuzhong: c.fuwuzhong||0,
showLoginBtn: !profile.isLoggedIn, yiwancheng: c.yiwancheng||0, yituikuan: c.yituikuan||0
dashouCertified: isRoleStatusActive(wx.getStorageSync('dashoustatus')), }})
shangjiaCertified: isRoleStatusActive(wx.getStorageSync('shangjiastatus')), },
});
}, goToSetting() { wx.navigateTo({ url: '/pages/edit/edit' }) },
updateOrderCounts() { handleWechatLogin() {
const c = app.globalData.dingdanTiaoshu || {} wx.showLoading({ title: '登录中...', mask: true });
this.setData({ orderCounts: { ensureLogin({ silent: false, page: this })
daifuwu: c.daifuwu||0, fuwuzhong: c.fuwuzhong||0, .then(() => {
yiwancheng: c.yiwancheng||0, yituikuan: c.yituikuan||0 this.loadUserData();
}}) this.updateOrderCounts();
}, wx.showToast({ title: '登录成功', icon: 'success' });
})
goToSetting() { wx.navigateTo({ url: '/pages/edit/edit' }) }, .catch(() => {})
.finally(() => wx.hideLoading());
handleWechatLogin() { },
wx.showLoading({ title: '登录中...', mask: true });
ensureLogin({ silent: false, page: this }) goToOrder(e) { wx.navigateTo({ url: `/pages/orders/orders?type=${e.currentTarget.dataset.type}` }) },
.then(() => {
this.loadUserData(); goToAuth(e) {
this.updateOrderCounts(); const type = e.currentTarget.dataset.type;
wx.showToast({ title: '登录成功', icon: 'success' }); if (!type || !['dashou', 'shangjia'].includes(type)) return;
}) ensureLogin({ silent: false, page: this })
.catch(() => {}) .then(() => {
.finally(() => wx.hideLoading()); this.loadUserData();
}, const statusKey = type === 'dashou' ? 'dashoustatus' : 'shangjiastatus';
if (isRoleStatusActive(wx.getStorageSync(statusKey))) {
goToOrder(e) { wx.navigateTo({ url: `/pages/orders/orders?type=${e.currentTarget.dataset.type}` }) }, ensureRoleOnCenterPage(this, type);
enterLockedRole(type, app);
goToAuth(e) { return;
const type = e.currentTarget.dataset.type; }
if (!type || !['dashou', 'shangjia'].includes(type)) return; wx.navigateTo({ url: `/pages/verify/verify?type=${type}` });
ensureLogin({ silent: false, page: this }) })
.then(() => { .catch(() => {});
this.loadUserData(); },
const statusKey = type === 'dashou' ? 'dashoustatus' : 'shangjiastatus';
if (isRoleStatusActive(wx.getStorageSync(statusKey))) { goToCustomService() { this.goToKefu() },
ensureRoleOnCenterPage(this, type); goToGuanzhuA() { wx.navigateTo({ url: '/pages/manager-assign/manager-assign' }) },
enterLockedRole(type, app); }))
return;
}
wx.navigateTo({ url: `/pages/verify/verify?type=${type}` });
})
.catch(() => {});
},
goToKefu() {
const { link, enterpriseId } = app.globalData.kefuConfig || {}
if (!link || !enterpriseId) { wx.showToast({ title: '客服配置未加载', icon: 'none' }); return }
wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId })
},
goToCustomService() { this.goToKefu() },
goToGuanzhuA() { wx.navigateTo({ url: '/pages/manager-assign/manager-assign' }) },
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: r => {
if (r.confirm) {
clearCacheAndEnterNormal(this);
}
},
});
},
})
+213 -190
View File
@@ -1,190 +1,213 @@
/* pages/orders/orders.wxss */ /* pages/orders/orders.wxss */
.dingdan-page { .dingdan-page {
background-color: #f8f9fa; background: #f2f2f2;
padding-bottom: 20rpx; min-height: 100vh;
} }
.dingdan-tabs-scroll { /* TAB栏 */
width: 100%; .dingdan-tabs-scroll {
height: 90rpx; width: 100%;
white-space: nowrap; background: #fff;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03); white-space: nowrap;
} border-bottom: 1rpx solid #f0f0f0;
}
.dingdan-tabs-container {
display: flex; .dingdan-tabs-container {
align-items: center; display: flex;
height: 100%; align-items: center;
padding: 0 20rpx; height: 88rpx;
} padding: 0 8rpx;
}
.dingdan-tab-item {
position: relative; .dingdan-tab-item {
} position: relative;
padding: 0 28rpx;
.dingdan-tab-item:last-child { height: 88rpx;
margin-right: 0; display: flex;
} flex-direction: column;
align-items: center;
/* .dingdan-tab-item.tab-active 已由全局 .tab-capsule--active 覆盖 */ justify-content: center;
flex-shrink: 0;
.tab-active-line { }
width: 40rpx;
height: 4rpx; .tab-text {
background-color: #4caf50; font-size: 28rpx;
border-radius: 2rpx; color: #666;
margin-top: 6rpx; font-weight: 400;
animation: fadeIn 0.3s ease; line-height: 1;
} }
@keyframes fadeIn { .dingdan-tab-item.tab-active .tab-text {
from { opacity: 0; transform: scale(0.8); } color: #e02e24;
to { opacity: 1; transform: scale(1); } font-weight: 700;
} }
.tab-text { .tab-active-line {
font-weight: 400; width: 40rpx;
} height: 6rpx;
background: #e02e24;
.dingdan-list-container { border-radius: 3rpx;
padding: 20rpx; margin-top: 10rpx;
} }
.loading-container { /* 列表区 */
display: flex; .dingdan-list-container {
flex-direction: column; padding: 20rpx 24rpx;
align-items: center; }
justify-content: center;
height: 400rpx; /* 加载 */
} .loading-container {
display: flex;
.loading-spinner { flex-direction: column;
border: 6rpx solid #e0e0e0; align-items: center;
border-top-color: #4caf50; justify-content: center;
border-radius: 50%; height: 400rpx;
animation: spin 1s linear infinite; }
margin-bottom: 20rpx;
} .loading-spinner {
width: 56rpx;
.loading-text { height: 56rpx;
font-size: 28rpx; border: 4rpx solid #f0f0f0;
color: #999999; border-top-color: #e02e24;
} border-radius: 50%;
animation: spin 0.7s linear infinite;
.empty-container { margin-bottom: 20rpx;
height: 500rpx; }
padding-top: 80rpx;
} .loading-text {
font-size: 26rpx;
.empty-image { color: #999;
opacity: 0.6; }
}
/* 空状态 */
.empty-text { .empty-container {
font-size: 32rpx; display: flex;
color: #999999; flex-direction: column;
} align-items: center;
justify-content: center;
.dingdan-card { height: 500rpx;
display: flex; }
align-items: center;
border-radius: 16rpx; .empty-image {
padding: 24rpx; width: 200rpx;
margin-bottom: 20rpx; height: 200rpx;
transition: transform 0.2s ease, box-shadow 0.2s ease; opacity: 0.5;
} margin-bottom: 24rpx;
}
.dingdan-card:active {
transform: translateY(-2rpx); .empty-text {
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08); font-size: 30rpx;
} color: #999;
margin-bottom: 8rpx;
.dingdan-card-left { }
flex-shrink: 0;
margin-right: 24rpx; .empty-subtext {
} font-size: 24rpx;
color: #bbb;
.dingdan-image { }
width: 160rpx;
height: 160rpx; /* 订单卡片 */
border-radius: 12rpx; .dingdan-card {
background-color: #f5f5f5; display: flex;
} align-items: center;
background: #fff;
.dingdan-card-center { border-radius: 12rpx;
flex: 1; padding: 24rpx;
height: 160rpx; margin-bottom: 16rpx;
display: flex; }
align-items: flex-start;
} .dingdan-card-left {
flex-shrink: 0;
.dingdan-jieshao { margin-right: 20rpx;
font-size: 28rpx; }
line-height: 40rpx;
color: #333333; .dingdan-image {
font-weight: 400; width: 160rpx;
display: -webkit-box; height: 160rpx;
-webkit-box-orient: vertical; border-radius: 8rpx;
-webkit-line-clamp: 3; background: #f5f5f5;
overflow: hidden; }
text-overflow: ellipsis;
} .dingdan-card-center {
flex: 1;
.dingdan-card-right { height: 160rpx;
display: flex; display: flex;
flex-direction: column; align-items: flex-start;
align-items: flex-end; }
justify-content: space-between;
height: 160rpx; .dingdan-jieshao {
margin-left: 20rpx; font-size: 28rpx;
} line-height: 40rpx;
color: #333;
/* 价格样式 */ display: -webkit-box;
.dingdan-jine { -webkit-box-orient: vertical;
display: flex; -webkit-line-clamp: 3;
align-items: center; /* 垂直居中 */ overflow: hidden;
} }
.jine-symbol { .dingdan-card-right {
font-size: 28rpx; display: flex;
color: #333333; flex-direction: column;
font-weight: 500; align-items: flex-end;
margin-right: 4rpx; /* 添加右边距,让符号和数字有间隔 */ justify-content: space-between;
} height: 160rpx;
margin-left: 16rpx;
.jine-number { flex-shrink: 0;
font-size: 32rpx; /* 整数和小数一样大 */ }
color: #333333;
font-weight: 600; .dingdan-zhuangtai {
} font-size: 24rpx;
color: #e02e24;
.load-more-line { font-weight: 600;
width: 80rpx; }
height: 1rpx;
background-color: #e0e0e0; .dingdan-jine {
margin: 0 20rpx; display: flex;
} align-items: baseline;
}
.load-more-text {
white-space: nowrap; .jine-symbol {
} font-size: 24rpx;
color: #333;
@media (max-width: 375px) { font-weight: 500;
.dingdan-image { }
width: 140rpx;
height: 140rpx; .jine-number {
} font-size: 32rpx;
color: #333;
.dingdan-card-center { font-weight: 700;
height: 140rpx; }
}
/* 加载更多 */
.dingdan-card-right { .load-more-container {
height: 140rpx; display: flex;
} align-items: center;
justify-content: center;
.jine-number { padding: 24rpx 0;
font-size: 30rpx; }
}
} .load-more-line {
width: 80rpx;
height: 1rpx;
background: #e0e0e0;
}
.load-more-text {
font-size: 24rpx;
color: #999;
white-space: nowrap;
margin: 0 20rpx;
}
.no-more-container {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
}
.no-more-text {
font-size: 24rpx;
color: #ccc;
}
@@ -62,9 +62,9 @@
</view> </view>
</scroll-view> </scroll-view>
<view class="modal-foot"> <view class="modal-foot">
<view wx:if="{{ detailItem.zhuangtai === 1 && !detailItem.bohuiliyou }}" class="btn primary" bindtap="openShensu">申诉</view> <view wx:if="{{ detailItem.zhuangtai === 1 && !detailItem.bohuiliyou }}" class="pbtn primary" bindtap="openShensu">申诉</view>
<view wx:if="{{ detailItem.zhuangtai === 1 || detailItem.zhuangtai === 3 }}" class="btn primary" bindtap="startPay">立即缴纳</view> <view wx:if="{{ detailItem.zhuangtai === 1 || detailItem.zhuangtai === 3 }}" class="pbtn primary" bindtap="startPay">立即缴纳</view>
<view class="btn default" bindtap="closeDetail">关闭</view> <view class="pbtn default" bindtap="closeDetail">关闭</view>
</view> </view>
</view> </view>
</view> </view>
@@ -100,8 +100,8 @@
</view> </view>
</scroll-view> </scroll-view>
<view class="modal-foot"> <view class="modal-foot">
<view class="btn default" bindtap="closeShensu">取消</view> <view class="pbtn default" bindtap="closeShensu">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view> <view class="pbtn primary" bindtap="submitShensu">提交申诉</view>
</view> </view>
</view> </view>
</view> </view>
@@ -50,10 +50,10 @@ page { background: #f5f5f5; }
.progress-text { font-size: 22rpx; color: #2e7d32; text-align: right; margin-top: 4rpx; } .progress-text { font-size: 22rpx; color: #2e7d32; text-align: right; margin-top: 4rpx; }
.modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; } .modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; }
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; } .pbtn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
.primary { background: #2e7d32; color: #fff; } .primary { background: #2e7d32; color: #fff; }
.default { background: #f0f0f0; color: #555; } .default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; } .pbtn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */ /* ===== 获取更多按钮 ===== */
@@ -48,8 +48,8 @@
<text class="confirm-tip">确认后将从您的佣金/分红中扣除</text> <text class="confirm-tip">确认后将从您的佣金/分红中扣除</text>
</view> </view>
<view class="pay-foot"> <view class="pay-foot">
<view class="btn default" bindtap="backToMethod">取消</view> <view class="pbtn default" bindtap="backToMethod">取消</view>
<view class="btn primary" bindtap="confirmBalance">确认</view> <view class="pbtn primary" bindtap="confirmBalance">确认</view>
</view> </view>
</view> </view>
@@ -53,7 +53,7 @@
@keyframes spin { to { transform: rotate(360deg); } } @keyframes spin { to { transform: rotate(360deg); } }
.loading-text { color: #fff; font-size: 26rpx; } .loading-text { color: #fff; font-size: 26rpx; }
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; } .pbtn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
.primary { background: #2e7d32; color: #fff; } .primary { background: #2e7d32; color: #fff; }
.default { background: #f0f0f0; color: #555; } .default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; } .pbtn:active { opacity: 0.8; }
@@ -60,8 +60,8 @@
</view> </view>
</scroll-view> </scroll-view>
<view class="modal-foot"> <view class="modal-foot">
<view wx:if="{{ xuanzhongChufa.sqzhuangtai === 0 }}" class="btn primary" bindtap="openShensuModal">申诉</view> <view wx:if="{{ xuanzhongChufa.sqzhuangtai === 0 }}" class="pbtn primary" bindtap="openShensuModal">申诉</view>
<view class="btn default" bindtap="guanbiXiangqing">关闭</view> <view class="pbtn default" bindtap="guanbiXiangqing">关闭</view>
</view> </view>
</view> </view>
</view> </view>
@@ -97,8 +97,8 @@
</view> </view>
</scroll-view> </scroll-view>
<view class="modal-foot"> <view class="modal-foot">
<view class="btn default" bindtap="closeShensuModal">取消</view> <view class="pbtn default" bindtap="closeShensuModal">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view> <view class="pbtn primary" bindtap="submitShensu">提交申诉</view>
</view> </view>
</view> </view>
</view> </view>
@@ -59,10 +59,10 @@ page { background: #f5f5f5; }
/* 🔥 底部按钮区左右 padding 严格均等 */ /* 🔥 底部按钮区左右 padding 严格均等 */
.modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; } .modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; }
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; } .pbtn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
.primary { background: #2e7d32; color: #fff; } .primary { background: #2e7d32; color: #fff; }
.default { background: #f0f0f0; color: #555; } .default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; } .pbtn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */ /* ===== 获取更多按钮 ===== */
+21 -21
View File
@@ -25,38 +25,38 @@
<!-- 按钮区域(第一行:保存/刷新/生成海报) --> <!-- 按钮区域(第一行:保存/刷新/生成海报) -->
<view class="button-section"> <view class="button-section">
<view class="btn-group"> <view class="pbtn-group">
<view class="btn save-btn" bindtap="saveToAlbum" wx:if="{{qrcodeUrl}}"> <view class="pbtn pbtn-save" bindtap="saveToAlbum" wx:if="{{qrcodeUrl}}">
<text class="btn-icon">📥</text> <text class="pbtn-icon">📥</text>
<text class="btn-text">保存二维码</text> <text class="pbtn-text">保存二维码</text>
</view> </view>
<view class="btn refresh-btn" bindtap="refreshQRCode" wx:if="{{!isLoading}}"> <view class="pbtn pbtn-refresh" bindtap="refreshQRCode" wx:if="{{!isLoading}}">
<text class="btn-icon">⟳</text> <text class="pbtn-icon">⟳</text>
<text class="btn-text">{{qrcodeUrl ? '重新获取' : '获取二维码'}}</text> <text class="pbtn-text">{{qrcodeUrl ? '重新获取' : '获取二维码'}}</text>
</view> </view>
<view class="btn loading-btn" wx:else> <view class="pbtn pbtn-loading" wx:else>
<view class="loading-spinner-small"></view> <view class="loading-spinner-small"></view>
<text class="btn-text">生成中...</text> <text class="pbtn-text">生成中...</text>
</view> </view>
<!-- 🔥【新增】生成我的专属海报按钮 --> <!-- 生成我的专属海报按钮 -->
<view class="btn poster-btn" bindtap="generateMyPoster" wx:if="{{qrcodeUrl && !showPosterCanvas}}"> <view class="pbtn pbtn-poster" bindtap="generateMyPoster" wx:if="{{qrcodeUrl && !showPosterCanvas}}">
<text class="btn-icon">🎨</text> <text class="pbtn-icon">🎨</text>
<text class="btn-text">生成我的专属海报</text> <text class="pbtn-text">生成我的专属海报</text>
</view> </view>
</view> </view>
</view> </view>
<!-- 🔥【新增】专属海报展示区(当 showPosterCanvas 为 true 时显示) --> <!-- 专属海报展示区(当 showPosterCanvas 为 true 时显示) -->
<view class="poster-section" wx:if="{{showPosterCanvas}}"> <view class="poster-section" wx:if="{{showPosterCanvas}}">
<canvas canvas-id="posterCanvas" class="poster-canvas"></canvas> <canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
<view class="poster-actions"> <view class="poster-actions">
<view class="btn save-poster-btn" bindtap="savePoster"> <view class="pbtn pbtn-save-poster" bindtap="savePoster">
<text class="btn-icon">📥</text> <text class="pbtn-icon">📥</text>
<text class="btn-text">保存海报</text> <text class="pbtn-text">保存海报</text>
</view> </view>
<view class="btn back-btn" bindtap="hidePoster"> <view class="pbtn pbtn-back" bindtap="hidePoster">
<text class="btn-icon">↩️</text> <text class="pbtn-icon">↩️</text>
<text class="btn-text">返回</text> <text class="pbtn-text">返回</text>
</view> </view>
</view> </view>
</view> </view>
@@ -68,4 +68,4 @@
</view> </view>
<global-notification id="global-notification" /> <global-notification id="global-notification" />
</view> </view>
+235 -267
View File
@@ -1,269 +1,237 @@
/* 机甲风格推广海报页面(含新增专属海报样式) */ /* 推广海报页面 */
page { page {
background: #0a0e17; background: #0a0e17;
min-height: 100vh; min-height: 100vh;
font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif; font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
color: #fff; color: #fff;
} }
/* 背景网格 */ /* 背景网格 */
.grid-bg { .grid-bg {
position: fixed; position: fixed;
top: 0; top: 0; left: 0;
left: 0; width: 100%; height: 100%;
width: 100%; background-image:
height: 100%; linear-gradient(rgba(0, 160, 255, 0.05) 1px, transparent 1px),
background-image: linear-gradient(90deg, rgba(0, 160, 255, 0.05) 1px, transparent 1px);
linear-gradient(rgba(0, 160, 255, 0.05) 1px, transparent 1px), background-size: 50rpx 50rpx;
linear-gradient(90deg, rgba(0, 160, 255, 0.05) 1px, transparent 1px); pointer-events: none;
background-size: 50rpx 50rpx; z-index: 0;
pointer-events: none; }
z-index: 0;
} .page-container {
position: relative;
.page-container { z-index: 2;
position: relative; min-height: 100vh;
z-index: 2; padding: 30rpx 25rpx;
min-height: 100vh; box-sizing: border-box;
padding: 30rpx 25rpx; }
box-sizing: border-box;
} /* 机械装饰线条 */
.mech-line {
/* 机械装饰线条 */ position: absolute;
.mech-line { pointer-events: none;
position: absolute; z-index: 1;
pointer-events: none; }
z-index: 1; .line-h {
} top: 200rpx; left: 0;
.line-h { width: 100%; height: 2rpx;
top: 200rpx; background: linear-gradient(90deg, transparent, #00a6ff, transparent);
left: 0; }
width: 100%; .line-v {
height: 2rpx; right: 50rpx; top: 100rpx;
background: linear-gradient(90deg, transparent, #00a6ff, transparent); width: 2rpx; height: 300rpx;
} background: linear-gradient(180deg, transparent, #00a6ff, transparent);
.line-v { }
right: 50rpx;
top: 100rpx; .content {
width: 2rpx; position: relative;
height: 300rpx; z-index: 3;
background: linear-gradient(180deg, transparent, #00a6ff, transparent); display: flex;
} flex-direction: column;
align-items: center;
.content { justify-content: center;
position: relative; min-height: 100vh;
z-index: 3; padding: 40rpx 0;
display: flex; }
flex-direction: column;
align-items: center; /* 标题区域 */
justify-content: center; .title-section {
min-height: 100vh; text-align: center;
padding: 40rpx 0; margin-bottom: 60rpx;
} }
.title {
/* 标题区域 */ font-size: 44rpx;
.title-section { font-weight: bold;
text-align: center; color: #fff;
margin-bottom: 60rpx; text-shadow: 0 0 30rpx #00a6ff;
} letter-spacing: 2rpx;
.title { display: block;
font-size: 44rpx; margin-bottom: 20rpx;
font-weight: bold; }
color: #fff; .subtitle {
text-shadow: 0 0 30rpx #00a6ff; font-size: 28rpx;
letter-spacing: 2rpx; color: #9cf;
display: block; opacity: 0.9;
margin-bottom: 20rpx; letter-spacing: 1rpx;
} }
.subtitle {
font-size: 28rpx; /* 二维码区域 */
color: #9cf; .qrcode-section {
opacity: 0.9; width: 100%;
letter-spacing: 1rpx; display: flex;
} justify-content: center;
align-items: center;
/* 二维码区域(方形,无斜切) */ margin-bottom: 60rpx;
.qrcode-section { }
width: 100%; .qrcode-wrapper {
display: flex; position: relative;
justify-content: center; width: 400rpx; height: 400rpx;
align-items: center; background: rgba(0, 20, 40, 0.8);
margin-bottom: 60rpx; border: 4rpx solid #00a6ff;
} display: flex;
.qrcode-wrapper { align-items: center;
position: relative; justify-content: center;
width: 400rpx; box-shadow: 0 0 80rpx rgba(0, 166, 255, 0.4);
height: 400rpx; animation: qrcodePulse 2s infinite alternate;
background: rgba(0, 20, 40, 0.8); }
border: 4rpx solid #00a6ff; .qrcode-img {
/* 移除 clip-path,保持方形 */ width: 90%; height: 90%;
display: flex; object-fit: contain;
align-items: center; }
justify-content: center; .qrcode-glow {
box-shadow: 0 0 80rpx rgba(0, 166, 255, 0.4); position: absolute;
animation: qrcodePulse 2s infinite alternate; top: 0; left: 0;
} width: 100%; height: 100%;
.qrcode-img { background: radial-gradient(circle at 30% 30%, rgba(0, 166, 255, 0.2), transparent 70%);
width: 90%; pointer-events: none;
height: 90%; }
object-fit: contain; .empty-qrcode {
} width: 400rpx; height: 400rpx;
.qrcode-glow { background: rgba(0, 20, 40, 0.8);
position: absolute; border: 4rpx dashed #00a6ff;
top: 0; display: flex;
left: 0; flex-direction: column;
width: 100%; align-items: center;
height: 100%; justify-content: center;
background: radial-gradient(circle at 30% 30%, rgba(0, 166, 255, 0.2), transparent 70%); }
pointer-events: none; .empty-icon {
} font-size: 80rpx;
.empty-qrcode { color: #00a6ff;
width: 400rpx; margin-bottom: 20rpx;
height: 400rpx; opacity: 0.5;
background: rgba(0, 20, 40, 0.8); }
border: 4rpx dashed #00a6ff; .empty-text {
/* 方形 */ font-size: 26rpx;
display: flex; color: #9cf;
flex-direction: column; text-align: center;
align-items: center; padding: 0 40rpx;
justify-content: center; }
}
.empty-icon { /* 按钮区域 */
font-size: 80rpx; .button-section {
color: #00a6ff; width: 100%;
margin-bottom: 20rpx; display: flex;
opacity: 0.5; justify-content: center;
} margin-bottom: 40rpx;
.empty-text { }
font-size: 26rpx; .pbtn-group {
color: #9cf; display: flex;
text-align: center; gap: 20rpx;
padding: 0 40rpx; flex-wrap: wrap;
} justify-content: center;
}
/* 按钮区域(机甲斜切风格) */ .pbtn {
.button-section { width: 220rpx;
width: 100%; height: 80rpx;
display: flex; background: rgba(0, 30, 60, 0.8);
justify-content: center; border: 2rpx solid #00a6ff;
margin-bottom: 40rpx; border-right: none;
} border-bottom: none;
.btn-group { clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
display: flex; display: flex;
gap: 20rpx; align-items: center;
flex-wrap: wrap; justify-content: center;
justify-content: center; position: relative;
} transition: 0.2s;
.btn { }
width: 220rpx; .pbtn:active {
height: 80rpx; transform: scale(0.96);
background: rgba(0, 30, 60, 0.8); box-shadow: 0 0 40rpx #00a6ff;
border: 2rpx solid #00a6ff; }
border-right: none; .pbtn-save { border-color: #00a6ff; }
border-bottom: none; .pbtn-refresh { border-color: #ffaa00; }
clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%); .pbtn-refresh .pbtn-icon { color: #ffaa00; }
display: flex; .pbtn-poster { border-color: #ffaa00; }
align-items: center; .pbtn-poster .pbtn-icon { color: #ffaa00; }
justify-content: center; .pbtn-loading {
position: relative; opacity: 0.7;
transition: 0.2s; pointer-events: none;
cursor: pointer; }
} .pbtn-icon {
.btn:active { font-size: 36rpx;
transform: scale(0.96); margin-right: 8rpx;
box-shadow: 0 0 40rpx #00a6ff; }
} .pbtn-text {
.save-btn { font-size: 26rpx;
border-color: #00a6ff; font-weight: 500;
} color: #fff;
.refresh-btn { }
border-color: #ffaa00; .loading-spinner-small {
} width: 30rpx; height: 30rpx;
.refresh-btn .btn-icon { border: 4rpx solid rgba(255,255,255,0.2);
color: #ffaa00; border-top: 4rpx solid #fff;
} border-radius: 50%;
/* 🔥【新增】生成海报按钮样式 */ animation: spin 1s linear infinite;
.poster-btn { margin-right: 10rpx;
border-color: #ffaa00; }
}
.poster-btn .btn-icon { /* 专属海报展示区 */
color: #ffaa00; .poster-section {
} margin-top: 40rpx;
.loading-btn { display: flex;
opacity: 0.7; flex-direction: column;
pointer-events: none; align-items: center;
} width: 100%;
.btn-icon { }
font-size: 36rpx; .poster-canvas {
margin-right: 8rpx; width: 600rpx; height: 900rpx;
} background: #1a1f2e;
.btn-text { border: 4rpx solid #00a6ff;
font-size: 26rpx; box-shadow: 0 0 60rpx #00a6ff;
font-weight: 500; }
color: #fff; .poster-actions {
} display: flex;
.loading-spinner-small { gap: 30rpx;
width: 30rpx; margin-top: 30rpx;
height: 30rpx; justify-content: center;
border: 4rpx solid rgba(255,255,255,0.2); }
border-top: 4rpx solid #fff; .pbtn-save-poster {
border-radius: 50%; background: linear-gradient(45deg, #0066cc, #00a6ff);
animation: spin 1s linear infinite; border-color: #00a6ff;
margin-right: 10rpx; }
} .pbtn-back {
background: rgba(0, 30, 60, 0.8);
/* 🔥【新增】专属海报展示区 */ border-color: #00a6ff;
.poster-section { }
margin-top: 40rpx;
display: flex; /* 使用说明 */
flex-direction: column; .instruction {
align-items: center; margin-top: 30rpx;
width: 100%; padding: 20rpx 40rpx;
} background: rgba(0, 30, 50, 0.5);
.poster-canvas { border-left: 4rpx solid #00a6ff;
width: 600rpx; clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%);
height: 900rpx; }
background: #1a1f2e; /* 占位色 */ .instruction-text {
border: 4rpx solid #00a6ff; font-size: 24rpx;
box-shadow: 0 0 60rpx #00a6ff; color: #9cf;
/* 保持方形,无斜切 */ line-height: 1.6;
} }
.poster-actions {
display: flex; /* 动画 */
gap: 30rpx; @keyframes qrcodePulse {
margin-top: 30rpx; 0% { box-shadow: 0 0 40rpx rgba(0, 166, 255, 0.3); }
justify-content: center; 100% { box-shadow: 0 0 100rpx rgba(0, 166, 255, 0.6); }
} }
.save-poster-btn {
background: linear-gradient(45deg, #0066cc, #00a6ff);
border-color: #00a6ff;
}
.back-btn {
background: rgba(0, 30, 60, 0.8);
border-color: #00a6ff;
}
/* 使用说明(保留) */
.instruction {
margin-top: 30rpx;
padding: 20rpx 40rpx;
background: rgba(0, 30, 50, 0.5);
border-left: 4rpx solid #00a6ff;
clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%);
}
.instruction-text {
font-size: 24rpx;
color: #9cf;
line-height: 1.6;
}
/* 动画 */
@keyframes qrcodePulse {
0% { box-shadow: 0 0 40rpx rgba(0, 166, 255, 0.3); }
100% { box-shadow: 0 0 100rpx rgba(0, 166, 255, 0.6); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
+5 -2
View File
@@ -140,8 +140,7 @@ Page({
that.handleLoadError(res.data?.msg || '加载失败') that.handleLoadError(res.data?.msg || '加载失败')
} }
}, },
fail(error) { fail() {
console.error('商品详情加载失败:', error)
that.handleLoadError('网络错误,请重试') that.handleLoadError('网络错误,请重试')
} }
}) })
@@ -234,6 +233,10 @@ Page({
this.loadShangpinDetail() this.loadShangpinDetail()
}, },
goToKefu() {
wx.navigateTo({ url: '/pages/cs-chat/cs-chat' })
},
onShareAppMessage() { onShareAppMessage() {
const { shangpinData, shangpinId } = this.data const { shangpinData, shangpinId } = this.data
+123 -112
View File
@@ -1,112 +1,123 @@
<!--pages/product-detail/product-detail.wxml--> <!--pages/product-detail/product-detail.wxml-->
<view class="detail-page page-container"> <view class="page">
<!-- 加载状态 --> <!-- 加载 -->
<view class="loading-container loading-mask loading-mask--light" wx:if="{{isLoading}}"> <view class="loading-box" wx:if="{{isLoading}}">
<view class="loading-content"> <view class="spinner"></view>
<view class="loading-spinner"> <text class="loading-text">加载中...</text>
<view class="spinner-ring"></view> </view>
<view class="spinner-dot"></view>
</view> <!-- 错误 -->
<text class="loading-text">加载中...</text> <view class="error-box" wx:if="{{!isLoading && loadError}}">
</view> <text class="error-text">加载失败</text>
</view> <view class="error-btn" bindtap="onRefresh">重新加载</view>
</view>
<!-- 错误状态 -->
<view class="error-container empty-state" wx:if="{{!isLoading && loadError}}"> <!-- 商品详情 -->
<image src="/images/error.png" class="error-icon" /> <block wx:if="{{!isLoading && !loadError && shangpinData}}">
<text class="error-text">加载失败</text> <!-- 商品大图 -->
<button class="error-btn btn btn-sm btn-sm--green" bindtap="onRefresh">重新加载</button> <view class="hero" bindtap="previewImage">
</view> <image src="{{shangpinData.tupian}}" mode="aspectFill" class="hero-img" />
<!-- 图片角标 -->
<!-- 商品详情内容 --> <view class="hero-badge">
<view class="detail-content" wx:if="{{!isLoading && !loadError && shangpinData}}"> <text class="hero-badge-text">{{shangpinData.kucun <= 0 ? '已售罄' : '现货'}}</text>
<!-- 商品图片区域(占满屏幕) --> </view>
<view class="image-section"> </view>
<view class="image-container" bindtap="previewImage">
<image <!-- 价格区 - 红底渐变 -->
src="{{shangpinData.tupian}}" <view class="price-block">
mode="aspectFill" <view class="price-left">
class="product-image" <text class="price-sym">¥</text>
/> <text class="price-int">{{shangpinData.priceInteger || '0'}}</text>
<!-- 库存标签 --> <text class="price-dec">.{{shangpinData.priceDecimal || '00'}}</text>
<view class="kucun-tag {{shangpinData.kucunClass || 'kucun-normal'}}"> </view>
<text class="kucun-text">{{shangpinData.kucunText || '库存充足'}}</text> <view class="price-right">
</view> <text class="sold-text">已拼{{shangpinData.xiaoliangText || '0'}}件</text>
</view> </view>
</view> </view>
<!-- 下方内容区域(顶部有圆角) --> <!-- 标题区 -->
<view class="content-area"> <view class="title-block">
<!-- 商品标题和价格区域 --> <view class="title-tags">
<view class="title-price-section"> <text class="tag-hot">热卖</text>
<view class="title-row"> </view>
<text class="product-title">{{shangpinData.biaoti || ''}}</text> <text class="title-text">{{shangpinData.biaoti || ''}}</text>
</view> </view>
<view class="price-row"> <!-- 服务保障条 -->
<!-- 价格 --> <view class="service-strip">
<view class="price-section"> <view class="service-item">
<text class="price-icon">¥</text> <text class="service-check">✓</text>
<text class="price-integer">{{shangpinData.priceInteger || '0'}}</text> <text class="service-label">极速接单</text>
<text class="price-decimal">.{{shangpinData.priceDecimal || '00'}}</text> </view>
</view> <view class="service-item">
<text class="service-check">✓</text>
<!-- 销量 --> <text class="service-label">资金担保</text>
<view class="sales-section"> </view>
<image src="/images/xiaoliang.png" class="sales-icon" /> <view class="service-item">
<text class="sales-text">{{shangpinData.xiaoliangText || '0'}}次购买</text> <text class="service-check">✓</text>
</view> <text class="service-label">售后保障</text>
</view> </view>
</view> <view class="service-item">
<text class="service-check">✓</text>
<!-- 商品介绍区域 --> <text class="service-label">隐私保护</text>
<view class="info-section" wx:if="{{shangpinData.xiangqing}}"> </view>
<view class="section-header"> </view>
<image src="/images/jieshao.png" class="section-icon" />
<text class="section-title">商品介绍:</text> <!-- 选择SKU入口 -->
</view> <view class="sku-trigger" bindtap="onOrderClick">
<view class="section-content"> <view class="sku-trigger-left">
<text class="content-text">{{shangpinData.xiangqing}}</text> <text class="sku-label">选择</text>
</view> <text class="sku-value">服务规格、购买数量</text>
</view> </view>
<text class="sku-arrow">></text>
<!-- 购买须知区域 --> </view>
<view class="info-section" wx:if="{{shangpinData.xiadanxuzhi}}">
<view class="section-header"> <!-- 商品介绍 -->
<image src="/images/jieshao.png" class="section-icon" /> <view class="detail-card" wx:if="{{shangpinData.xiangqing}}">
<text class="section-title">购买须知:</text> <view class="detail-card-head">
</view> <text class="detail-card-title">商品介绍</text>
<view class="section-content"> </view>
<text class="content-text">{{shangpinData.xiadanxuzhi}}</text> <text class="detail-card-body">{{shangpinData.xiangqing}}</text>
</view> </view>
</view>
<!-- 购买须知 -->
<!-- 规则图片区域(只有有图片时才显示) --> <view class="detail-card" wx:if="{{shangpinData.xiadanxuzhi}}">
<view class="rule-section" wx:if="{{shangpinData.guize}}"> <view class="detail-card-head">
<image <text class="detail-card-title">购买须知</text>
src="{{shangpinData.guize}}" </view>
mode="widthFix" <text class="detail-card-body">{{shangpinData.xiadanxuzhi}}</text>
class="rule-image" </view>
binderror="onImageError"
/> <!-- 规则图片 -->
</view> <view class="detail-card" wx:if="{{shangpinData.guize}}">
<view class="detail-card-head">
<!-- 占位区域(给底部按钮留空间) --> <text class="detail-card-title">服务规则</text>
<view class="placeholder"></view> </view>
</view> <image src="{{shangpinData.guize}}" mode="widthFix" class="rule-img" binderror="onImageError" />
</view> </view>
<!-- 立即下单按钮(固定在底部,无价格标签) --> <!-- 底部留白 -->
<view class="order-btn-container fixed-bottom" wx:if="{{!isLoading && !loadError && shangpinData}}"> <view class="bottom-space"></view>
<view class="order-btn btn btn-primary btn-primary--green" bindtap="onOrderClick"> </block>
<!-- 按钮发光特效 -->
<view class="btn-glow"></view> <!-- 底部操作栏 -->
<!-- 按钮流光特效 --> <view class="bottom-bar" wx:if="{{!isLoading && !loadError && shangpinData}}">
<view class="btn-streamer"></view> <view class="bottom-bar-inner">
<!-- 按钮文字 --> <!-- 客服 -->
<text class="btn-text">立即预约</text> <view class="bar-icon-btn" bindtap="goToKefu">
</view> <text class="bar-icon-text">客服</text>
</view> </view>
<global-notification id="global-notification" /> <!-- 单独购买 -->
<view
</view> class="bar-buy-btn {{shangpinData.kucun <= 0 ? 'bar-buy-btn--disabled' : ''}}"
bindtap="onOrderClick"
hover-class="bar-buy-btn--hover"
>
<text class="bar-buy-text">{{shangpinData.kucun <= 0 ? '已售罄' : '立即下单'}}</text>
<text class="bar-buy-price">¥{{shangpinData.priceInteger || '0'}}.{{shangpinData.priceDecimal || '00'}}</text>
</view>
</view>
</view>
<global-notification id="global-notification" />
</view>
+347 -349
View File
@@ -1,349 +1,347 @@
/* pages/product-detail/product-detail.wxss */ /* pages/product-detail/product-detail.wxss */
.detail-page {
width: 100%; page {
background: #f5f5f5; background: #f2f2f2;
} }
/* 加载状态 */ .page {
.loading-container { min-height: 100vh;
position: absolute; padding-bottom: 140rpx;
background: #f5f5f5; box-sizing: border-box;
z-index: 100; }
}
/* 加载 */
.loading-content { .loading-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} justify-content: center;
height: 60vh;
.loading-spinner { }
width: 80rpx;
height: 80rpx; .spinner {
position: relative; width: 56rpx;
margin-bottom: 30rpx; height: 56rpx;
} border: 4rpx solid #f0f0f0;
border-top-color: #e02e24;
.spinner-ring { border-radius: 50%;
position: absolute; animation: spin 0.7s linear infinite;
top: 0; margin-bottom: 20rpx;
left: 0; }
width: 100%;
height: 100%; .loading-text {
border: 4rpx solid rgba(82, 196, 26, 0.2); font-size: 26rpx;
border-radius: 50%; color: #999;
} }
.spinner-dot { /* 错误 */
position: absolute; .error-box {
top: 0; display: flex;
left: 50%; flex-direction: column;
width: 12rpx; align-items: center;
height: 12rpx; justify-content: center;
background: #52c41a; height: 60vh;
border-radius: 50%; }
transform: translateX(-50%);
animation: spin 1s linear infinite; .error-text {
} font-size: 30rpx;
color: #999;
.loading-text { margin-bottom: 32rpx;
font-size: 28rpx; }
color: #666;
font-weight: 500; .error-btn {
} padding: 16rpx 48rpx;
background: #e02e24;
/* 错误状态 */ color: #fff;
.error-container { font-size: 28rpx;
position: absolute; font-weight: 600;
top: 0; border-radius: 40rpx;
left: 0; }
right: 0;
bottom: 0; /* 商品大图 */
background: #f5f5f5; .hero {
z-index: 100; position: relative;
padding: 0; width: 100%;
} height: 750rpx;
background: #fff;
.error-icon { }
width: 180rpx;
height: 180rpx; .hero-img {
opacity: 0.3; width: 100%;
margin-bottom: 30rpx; height: 100%;
} }
.error-text { .hero-badge {
font-size: 30rpx; position: absolute;
color: #999; top: 24rpx;
margin-bottom: 40rpx; left: 24rpx;
} padding: 8rpx 16rpx;
border-radius: 6rpx;
.error-btn { background: rgba(224, 46, 36, 0.85);
font-weight: 500; display: flex;
padding: 20rpx 50rpx; align-items: center;
border-radius: 50rpx; justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(82, 196, 26, 0.3); line-height: 1;
} }
/* 商品详情内容 */ .hero-badge-text {
.detail-content { font-size: 20rpx;
padding-bottom: 140rpx; /* 给底部按钮留出空间 */ color: #fff;
} font-weight: 600;
line-height: 1;
/* 商品图片区域(占满屏幕) */ }
.image-section {
width: 100%; /* 价格区 - 拼多多红底 */
background: white; .price-block {
} display: flex;
align-items: baseline;
.image-container { justify-content: space-between;
width: 100%; padding: 24rpx 28rpx;
height: 750rpx; /* 正方形,占满屏幕宽度 */ background: linear-gradient(135deg, #e02e24, #f05545);
position: relative; }
overflow: hidden;
background: #f5f5f5; .price-left {
} display: flex;
align-items: baseline;
.product-image { }
width: 100%;
height: 100%; .price-sym {
} font-size: 28rpx;
color: #fff;
/* 库存标签 */ font-weight: 700;
.kucun-tag { }
position: absolute;
bottom: 30rpx; .price-int {
right: 30rpx; font-size: 60rpx;
padding: 12rpx 24rpx; color: #fff;
border-radius: 30rpx; font-weight: 800;
backdrop-filter: blur(10rpx); line-height: 1;
z-index: 10; margin-left: 2rpx;
} }
.kucun-normal { .price-dec {
background: rgba(0, 0, 0, 0.6); font-size: 28rpx;
color: white; color: rgba(255, 255, 255, 0.9);
} font-weight: 700;
}
.kucun-none {
background: rgba(255, 77, 79, 0.8); .price-right {
color: white; display: flex;
} align-items: center;
}
.kucun-text {
font-size: 24rpx; .sold-text {
font-weight: 500; font-size: 24rpx;
} color: rgba(255, 255, 255, 0.85);
background: rgba(0, 0, 0, 0.15);
/* 下方内容区域(顶部有圆角,与图片重叠一部分) */ padding: 6rpx 16rpx;
.content-area { border-radius: 20rpx;
position: relative; }
background: white;
border-radius: 40rpx 40rpx 0 0; /* 标题区 */
margin-top: -40rpx; /* 与图片重叠一部分,实现平滑过渡 */ .title-block {
padding: 40rpx 30rpx 0; background: #fff;
min-height: 400rpx; padding: 24rpx 28rpx 20rpx;
} }
/* 标题和价格区域 */ .title-tags {
.title-price-section { display: flex;
margin-bottom: 40rpx; align-items: center;
} margin-bottom: 12rpx;
}
.title-row {
margin-bottom: 30rpx; .tag-hot {
} font-size: 20rpx;
color: #fff;
.product-title { background: #e02e24;
font-size: 38rpx; padding: 4rpx 12rpx;
font-weight: 700; border-radius: 4rpx;
color: #222; font-weight: 600;
line-height: 1.5; margin-right: 12rpx;
display: -webkit-box; }
-webkit-box-orient: vertical;
-webkit-line-clamp: 2; .title-text {
overflow: hidden; font-size: 32rpx;
} font-weight: 600;
color: #222;
/* 价格和销量行 */ line-height: 1.6;
.price-row { display: -webkit-box;
display: flex; -webkit-box-orient: vertical;
justify-content: space-between; -webkit-line-clamp: 3;
align-items: center; overflow: hidden;
} }
/* 价格部分 */ /* 服务保障条 */
.price-section { .service-strip {
display: flex; display: flex;
align-items: baseline; flex-wrap: wrap;
} gap: 0;
padding: 16rpx 28rpx;
.price-icon { background: #fff;
font-size: 34rpx; margin-bottom: 16rpx;
color: #ff4d4f; }
font-weight: 600;
} .service-item {
display: flex;
.price-integer { align-items: center;
font-size: 58rpx; margin-right: 24rpx;
color: #ff4d4f; margin-bottom: 8rpx;
font-weight: 700; }
margin-left: 4rpx;
} .service-check {
font-size: 20rpx;
.price-decimal { color: #e02e24;
font-size: 34rpx; font-weight: 700;
color: #ff4d4f; margin-right: 4rpx;
font-weight: 600; }
}
.service-label {
/* 销量部分 */ font-size: 22rpx;
.sales-section { color: #666;
display: flex; }
align-items: center;
} /* 选择SKU入口 */
.sku-trigger {
.sales-icon { display: flex;
width: 30rpx; align-items: center;
height: 30rpx; justify-content: space-between;
margin-right: 12rpx; background: #fff;
opacity: 0.6; padding: 24rpx 28rpx;
} margin-bottom: 16rpx;
}
.sales-text {
font-size: 28rpx; .sku-trigger-left {
color: #999; display: flex;
font-weight: 400; align-items: center;
} }
/* 信息区域(商品介绍、购买须知) */ .sku-label {
.info-section { font-size: 26rpx;
margin-bottom: 40rpx; color: #999;
padding-top: 30rpx; margin-right: 16rpx;
border-top: 1rpx solid #f0f0f0; }
}
.sku-value {
.section-header { font-size: 26rpx;
display: flex; color: #333;
align-items: center; }
margin-bottom: 20rpx;
} .sku-arrow {
font-size: 28rpx;
.section-icon { color: #ccc;
width: 36rpx; margin-left: 12rpx;
height: 36rpx; }
margin-right: 16rpx;
} /* 详情卡片 */
.detail-card {
.section-title { background: #fff;
font-size: 32rpx; margin-bottom: 16rpx;
font-weight: 600; padding: 28rpx;
color: #52c41a; /* 绿色 */ }
}
.detail-card-head {
.section-content { margin-bottom: 20rpx;
line-height: 1.8; }
padding-left: 52rpx; /* 与图标对齐 */
} .detail-card-title {
font-size: 30rpx;
.content-text { font-weight: 700;
font-size: 28rpx; color: #222;
color: #333; position: relative;
font-weight: 400; padding-left: 16rpx;
white-space: pre-line; }
line-height: 1.8;
} .detail-card-title::before {
content: '';
/* 规则图片区域 */ position: absolute;
.rule-section { left: 0;
margin: 40rpx 0; top: 4rpx;
padding-top: 30rpx; width: 6rpx;
border-top: 1rpx solid #f0f0f0; height: 28rpx;
} border-radius: 3rpx;
background: #e02e24;
.rule-image { }
width: 100%;
border-radius: 16rpx; .detail-card-body {
display: block; font-size: 26rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08); color: #555;
} line-height: 1.8;
white-space: pre-line;
/* 占位区域 */ }
.placeholder {
height: 140rpx; .rule-img {
} width: 100%;
border-radius: 8rpx;
/* 立即下单按钮容器(固定底部) */ display: block;
.order-btn-container { }
background: linear-gradient(transparent, rgba(255, 255, 255, 0.9) 70%);
} /* 底部留白 */
.bottom-space {
/* 立即下单按钮 */ height: 40rpx;
.order-btn { }
border-radius: 50rpx;
height: 100rpx; /* 底部操作栏 */
box-shadow: .bottom-bar {
0 12rpx 36rpx rgba(82, 196, 26, 0.4), position: fixed;
0 4rpx 12rpx rgba(82, 196, 26, 0.3); bottom: 0;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); left: 0;
} right: 0;
z-index: 9999;
.order-btn:active { background: #fff;
box-shadow: padding-bottom: env(safe-area-inset-bottom);
0 6rpx 24rpx rgba(82, 196, 26, 0.5), border-top: 1rpx solid #f0f0f0;
0 2rpx 8rpx rgba(82, 196, 26, 0.4); }
}
.bottom-bar-inner {
/* 按钮发光特效 */ display: flex;
.btn-glow { align-items: stretch;
position: absolute; height: 100rpx;
top: 0; }
left: 0;
right: 0; .bar-icon-btn {
bottom: 0; display: flex;
background: rgba(255, 255, 255, 0.1); flex-direction: column;
animation: glowPulse 2s infinite; align-items: center;
} justify-content: center;
padding: 0 28rpx;
/* 按钮流光特效 */ height: 96rpx;
.btn-streamer { }
position: absolute;
top: 0; .bar-icon-text {
left: 0; font-size: 22rpx;
width: 100rpx; color: #666;
height: 100%; margin-top: 4rpx;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); }
transform: translateX(-100%);
animation: streamerFlow 3s infinite; .bar-buy-btn {
} width: 50%;
height: 100%;
/* 按钮文字 */ border-radius: 0;
.btn-text { display: flex;
font-size: 36rpx; align-items: center;
color: white; justify-content: center;
font-weight: 600; background: #e02e24;
letter-spacing: 2rpx; margin-left: auto;
position: relative; }
z-index: 2;
} .bar-buy-btn--disabled {
background: #ccc;
/* 动画定义 */ }
@keyframes glowPulse {
0%, 100% { .bar-buy-btn--hover {
opacity: 0.5; opacity: 0.9;
} transform: scale(0.98);
50% { }
opacity: 0.8;
} .bar-buy-text {
} font-size: 30rpx;
font-weight: 700;
@keyframes streamerFlow { color: #fff;
0% { margin-right: 8rpx;
transform: translateX(-100%); }
}
50% { .bar-buy-price {
transform: translateX(100%); font-size: 26rpx;
} font-weight: 700;
100% { color: rgba(255, 255, 255, 0.9);
transform: translateX(100%); }
}
}
+332 -547
View File
@@ -1,547 +1,332 @@
// pages/submit/submit.js // pages/submit/submit.js
const app = getApp() const app = getApp()
import request from '../../utils/request.js' import request from '../../utils/request.js'
// 引入弹窗服务(路径请根据项目实际调整) import PopupService from '../../services/popupService.js'
import PopupService from '../../services/popupService.js'
Page({
Page({ data: {
data: { ossImageUrl: '',
// 全局变量 apiBaseUrl: '',
ossImageUrl: '',
apiBaseUrl: '', shangpinData: null,
shangpinId: '',
// 从商品详情页传入的数据
shangpinData: null, nicheng: '',
shangpinId: '', beizhu: '',
zhiding: '',
// 表单数据 quantity: 1,
nicheng: '', // 游戏昵称/ID jine: 0,
beizhu: '', // 备注信息
zhiding: '', // 指定打手ID(新增) totalPriceInteger: '0',
jine: 0, // 总金额 totalPriceDecimal: '00',
// 页面状态 isSubmitting: false,
isSubmitting: false, canSubmit: false,
canSubmit: false, isPaying: false,
isPaying: false,
orderId: '',
// 支付相关 payParams: null,
orderId: '', checkCount: 0,
payParams: null, maxCheckCount: 3
checkCount: 0, },
maxCheckCount: 3
}, onLoad(options) {
this.setData({
onLoad(options) { ossImageUrl: app.globalData.ossImageUrl || '',
// 获取全局变量 apiBaseUrl: app.globalData.apiBaseUrl || ''
this.setData({ })
ossImageUrl: app.globalData.ossImageUrl || '',
apiBaseUrl: app.globalData.apiBaseUrl || '' wx.setNavigationBarTitle({ title: '提交订单' })
})
try {
// 设置页面标题 if (options.data) {
wx.setNavigationBarTitle({ const shangpinData = JSON.parse(decodeURIComponent(options.data))
title: '提交订单'
}) if (shangpinData.image && !shangpinData.image.startsWith('http')) {
shangpinData.image = this.data.ossImageUrl + shangpinData.image
// 解析传递的商品数据 }
try {
if (options.data) { if (shangpinData.price) {
const shangpinData = JSON.parse(decodeURIComponent(options.data)) const price = parseFloat(shangpinData.price) || 0
const priceInteger = Math.floor(price)
// 处理图片URL(拼接完整路径) let priceDecimal = '00'
if (shangpinData.image && !shangpinData.image.startsWith('http')) { const decimalPart = (price - priceInteger).toFixed(2)
shangpinData.image = this.data.ossImageUrl + shangpinData.image if (decimalPart > 0) {
} priceDecimal = decimalPart.toString().split('.')[1]
}
// 处理价格显示 shangpinData.priceInteger = priceInteger
if (shangpinData.price) { shangpinData.priceDecimal = priceDecimal
const price = parseFloat(shangpinData.price) || 0 }
const priceInteger = Math.floor(price)
let priceDecimal = '00' this.setData({
shangpinData: shangpinData,
const decimalPart = (price - priceInteger).toFixed(2) shangpinId: shangpinData.id || '',
if (decimalPart > 0) { jine: parseFloat(shangpinData.price) || 0,
priceDecimal = decimalPart.toString().split('.')[1] }, () => this.updateTotalPrice())
} } else {
this.handleDataError('商品数据不完整')
shangpinData.priceInteger = priceInteger }
shangpinData.priceDecimal = priceDecimal } catch (error) {
} this.handleDataError('解析商品数据失败')
}
// 设置商品数据 },
this.setData({
shangpinData: shangpinData, onShow() {
shangpinId: shangpinData.id || '', this.registerNotificationComponent()
jine: parseFloat(shangpinData.price) || 0, PopupService.checkAndShow(this, 'tijiao')
}) },
} else {
this.handleDataError('商品数据不完整') onHide() {
} const popupComp = this.selectComponent('#popupNotice')
} catch (error) { if (popupComp && popupComp.cleanup) popupComp.cleanup()
this.handleDataError('解析商品数据失败') },
}
}, registerNotificationComponent() {
const app = getApp()
onShow() { const notificationComp = this.selectComponent('#global-notification')
// 原有代码:注册通知组件 if (notificationComp && notificationComp.showNotification) {
this.registerNotificationComponent(); app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
// 🆕 进入页面时触发弹窗检查(PopupService 会自动判断是否展示) hide: () => notificationComp.hideNotification()
PopupService.checkAndShow(this, 'tijiao'); }
}, }
},
// pages/submit/submit.js 中添加 onHide 方法(如果已有则合并内容)
onHide() { updateTotalPrice() {
const popupComp = this.selectComponent('#popupNotice'); const total = this.data.jine * this.data.quantity
if (popupComp && popupComp.cleanup) { const intPart = Math.floor(total)
popupComp.cleanup(); const decPart = (total - intPart).toFixed(2).split('.')[1]
} this.setData({
}, totalPriceInteger: intPart,
totalPriceDecimal: decPart
// 新增:注册通知组件 })
registerNotificationComponent() { },
const app = getApp();
const notificationComp = this.selectComponent('#global-notification'); onQtyMinus() {
if (this.data.quantity <= 1) return
if (notificationComp && notificationComp.showNotification) { this.setData({ quantity: this.data.quantity - 1 }, () => this.updateTotalPrice())
//console.log('🏪 商城页面注册通知组件'); },
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data), onQtyPlus() {
hide: () => notificationComp.hideNotification() if (this.data.quantity >= 99) return
}; this.setData({ quantity: this.data.quantity + 1 }, () => this.updateTotalPrice())
} },
},
handleDataError(errorMsg) {
/** wx.showToast({ title: errorMsg || '数据错误', icon: 'none' })
* 处理数据错误 setTimeout(() => wx.navigateBack(), 1500)
*/ },
handleDataError(errorMsg) {
wx.showToast({ goBack() {
title: errorMsg || '数据错误', wx.navigateBack()
icon: 'none' },
})
onNicknameInput(e) {
setTimeout(() => { this.setData({ nicheng: e.detail.value.trim() }, () => this.checkFormValid())
wx.navigateBack() },
}, 1500)
}, onRemarkInput(e) {
this.setData({ beizhu: e.detail.value.trim() })
/** },
* 游戏昵称输入事件
*/ onZhidingInput(e) {
onNicknameInput(e) { this.setData({ zhiding: e.detail.value.trim() })
const value = e.detail.value.trim() },
this.setData({
nicheng: value checkFormValid() {
}, () => { const { nicheng } = this.data
this.checkFormValid() this.setData({ canSubmit: nicheng && nicheng.length > 0 && nicheng.length <= 15 })
}) },
},
onSubmitOrder() {
/** const { nicheng, beizhu, zhiding, jine, quantity, shangpinId, canSubmit, isSubmitting } = this.data
* 备注信息输入事件
*/ if (!canSubmit) {
onRemarkInput(e) { wx.showToast({ title: '请填写游戏昵称', icon: 'none' })
const value = e.detail.value.trim() return
this.setData({ }
beizhu: value
}) if (isSubmitting) return
},
this.setData({ isSubmitting: true, isPaying: false })
/**
* 指定打手ID输入事件 wx.showLoading({ title: '创建订单中...', mask: true })
*/
onZhidingInput(e) { const timeoutTimer = setTimeout(() => {
const value = e.detail.value.trim() wx.hideLoading()
this.setData({ wx.showToast({ title: '网络超时,请检查您是否已登录', icon: 'none' })
zhiding: value this.setData({ isSubmitting: false, isPaying: false })
}) }, 10000)
},
const orderData = {
/** shangpin_id: shangpinId,
* 检查表单是否有效 nicheng: nicheng,
*/ beizhu: beizhu || '',
checkFormValid() { zhiding: zhiding || '',
const { nicheng } = this.data jine: jine * quantity,
shuliang: quantity
const isValid = nicheng && nicheng.length > 0 && nicheng.length <= 15 }
this.setData({ request({
canSubmit: isValid url: '/dingdan/xiadan',
}) method: 'POST',
}, data: orderData
})
/** .then(res => {
* 提交订单 clearTimeout(timeoutTimer)
*/ wx.hideLoading()
onSubmitOrder() {
const { nicheng, beizhu, zhiding, jine, shangpinId, canSubmit, isSubmitting } = this.data if (res.statusCode === 200 && res.data) {
const response = res.data
// 验证表单 if (response.code === 0 && response.data) {
if (!canSubmit) { const orderId = response.data.dingdanid
wx.showToast({ const payParams = response.data.payParams || {}
title: '请填写游戏昵称', this.setData({
icon: 'none' orderId: orderId,
}) payParams: payParams,
return isSubmitting: false,
} isPaying: true,
checkCount: 0
// 防止重复提交 })
if (isSubmitting) { this.triggerWxPayment(payParams)
return } else {
} wx.showToast({ title: response.msg || '创建订单失败', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
this.setData({ }
isSubmitting: true, } else {
isPaying: false wx.showToast({ title: '网络错误,请重试', icon: 'none' })
}) this.setData({ isSubmitting: false, isPaying: false })
}
// 显示loading(超时设为10秒) })
wx.showLoading({ .catch(() => {
title: '创建订单中...', clearTimeout(timeoutTimer)
mask: true wx.hideLoading()
}) wx.showToast({ title: '创建订单失败', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
const timeoutTimer = setTimeout(() => { })
wx.hideLoading() },
wx.showToast({
title: '网络超时,请检查您是否已登录', triggerWxPayment(payParams) {
icon: 'none' if (!payParams || !payParams.timeStamp || !payParams.nonceStr ||
}) !payParams.package || !payParams.signType || !payParams.paySign) {
this.setData({ wx.showToast({ title: '支付参数不完整', icon: 'none' })
isSubmitting: false, this.setData({ isSubmitting: false, isPaying: false })
isPaying: false return
}) }
}, 10000)
wx.requestPayment({
// 构建请求数据(添加zhiding字段) timeStamp: payParams.timeStamp,
const orderData = { nonceStr: payParams.nonceStr,
shangpin_id: shangpinId, // 商品ID package: payParams.package,
nicheng: nicheng, // 游戏昵称 signType: payParams.signType,
beizhu: beizhu || '', // 备注(可为空) paySign: payParams.paySign,
zhiding: zhiding || '', // 指定打手ID(可为空) success: () => this.onPaymentSuccess(),
jine: jine // 金额 fail: (err) => {
} if (err.errMsg === 'requestPayment:fail cancel') {
this.handleUserCancel()
// 调用后端创建订单接口 } else {
request({ this.handlePaymentFail(err)
url: '/dingdan/xiadan', }
method: 'POST', }
data: orderData })
}) },
.then(res => {
clearTimeout(timeoutTimer) handleUserCancel() {
wx.hideLoading() const { orderId } = this.data
wx.showToast({ title: '已取消支付', icon: 'none', duration: 1500 })
if (res.statusCode === 200 && res.data) { if (orderId) {
const response = res.data request({
url: '/dingdan/shibai',
if (response.code === 0 && response.data) { method: 'POST',
// 保存订单ID和支付参数 data: { dingdanid: orderId, yuanyin: 'user_cancel' }
const orderId = response.data.dingdanid }).catch(() => {})
const payParams = response.data.payParams || {} }
this.setData({ isSubmitting: false, isPaying: false })
this.setData({ },
orderId: orderId,
payParams: payParams, handlePaymentFail(err) {
isSubmitting: false, const { orderId } = this.data
isPaying: true, wx.showToast({ title: '支付失败', icon: 'none', duration: 1500 })
checkCount: 0 if (orderId) {
}) request({
url: '/dingdan/shibai',
// 调起微信支付 method: 'POST',
this.triggerWxPayment(payParams) data: { dingdanid: orderId, yuanyin: 'payment_fail', cuowu: err.errMsg || '' }
} else { }).catch(() => {}).finally(() => {
wx.showToast({ this.setData({ isSubmitting: false, isPaying: false })
title: response.msg || '创建订单失败', })
icon: 'none' } else {
}) this.setData({ isSubmitting: false, isPaying: false })
this.setData({ }
isSubmitting: false, },
isPaying: false
}) onPaymentSuccess() {
} const { orderId } = this.data
} else { if (!orderId) {
wx.showToast({ wx.showToast({ title: '订单ID不存在', icon: 'none' })
title: '网络错误,请重试', this.setData({ isSubmitting: false, isPaying: false })
icon: 'none' return
}) }
this.setData({ wx.showToast({ title: '支付成功,确认中...', icon: 'none', duration: 1500 })
isSubmitting: false, this.checkOrderStatus()
isPaying: false },
})
} checkOrderStatus() {
}) const { orderId, checkCount, maxCheckCount } = this.data
.catch(error => { wx.showLoading({ title: `确认支付状态${checkCount > 0 ? `(${checkCount + 1})` : ''}`, mask: true })
clearTimeout(timeoutTimer)
wx.hideLoading() const timeoutTimer = setTimeout(() => {
wx.showToast({ wx.hideLoading()
title: '创建订单失败', this.handleConfirmError('网络超时')
icon: 'none' }, 5000)
})
this.setData({ request({
isSubmitting: false, url: '/dingdan/fucha',
isPaying: false method: 'POST',
}) data: { dingdanid: orderId }
}) })
}, .then(res => {
clearTimeout(timeoutTimer)
/** wx.hideLoading()
* 调起微信支付 if (res.statusCode === 200 && res.data) {
*/ if (res.data.code === 0) {
triggerWxPayment(payParams) { this.handleConfirmSuccess()
// 验证支付参数是否完整 } else {
if (!payParams || !payParams.timeStamp || !payParams.nonceStr || this.handleConfirmError(res.data.msg || '订单状态异常')
!payParams.package || !payParams.signType || !payParams.paySign) { }
wx.showToast({ } else {
title: '支付参数不完整', this.handleConfirmError('网络错误')
icon: 'none' }
}) })
this.setData({ .catch(() => {
isSubmitting: false, clearTimeout(timeoutTimer)
isPaying: false wx.hideLoading()
}) this.handleConfirmError('检查失败')
return })
} },
wx.requestPayment({ handleConfirmSuccess() {
timeStamp: payParams.timeStamp, wx.showToast({ title: '购买成功!', icon: 'success', duration: 1500 })
nonceStr: payParams.nonceStr, setTimeout(() => wx.switchTab({ url: '/pages/index/index' }), 1500)
package: payParams.package, this.setData({ isSubmitting: false, isPaying: false })
signType: payParams.signType, },
paySign: payParams.paySign,
handleConfirmError(errorMsg) {
success: (res) => { const newCheckCount = this.data.checkCount + 1
// 支付成功,开始双重确认 if (newCheckCount < this.data.maxCheckCount) {
this.onPaymentSuccess() this.setData({ checkCount: newCheckCount })
}, setTimeout(() => this.checkOrderStatus(), 1000)
} else {
fail: (err) => { wx.showModal({
// 根据错误类型处理 title: '支付确认中',
if (err.errMsg === 'requestPayment:fail cancel') { content: '支付已成功,但订单状态确认异常。请稍后在订单列表中查看状态,如有问题请联系客服。',
// 用户取消支付 showCancel: false,
this.handleUserCancel() confirmText: '我知道了',
} else { success: (res) => {
// 其他支付错误 if (res.confirm) wx.switchTab({ url: '/pages/index/index' })
this.handlePaymentFail(err) }
} })
} this.setData({ isSubmitting: false, isPaying: false })
}) }
}, }
})
/**
* 处理用户取消支付
*/
handleUserCancel() {
const { orderId } = this.data
wx.showToast({
title: '已取消支付',
icon: 'none',
duration: 1500
})
// 通知后端用户取消(静默请求,不阻塞用户)
if (orderId) {
request({
url: '/dingdan/shibai',
method: 'POST',
data: {
dingdanid: orderId,
yuanyin: 'user_cancel'
}
}).catch(() => {
// 静默失败
})
}
this.setData({
isSubmitting: false,
isPaying: false
})
},
/**
* 处理支付失败
*/
handlePaymentFail(err) {
const { orderId } = this.data
wx.showToast({
title: '支付失败',
icon: 'none',
duration: 1500
})
// 通知后端支付失败(简化流程,直接请求,不显示loading)
if (orderId) {
request({
url: '/dingdan/shibai',
method: 'POST',
data: {
dingdanid: orderId,
yuanyin: 'payment_fail',
cuowu: err.errMsg || ''
}
}).catch(() => {
// 静默失败
}).finally(() => {
this.setData({
isSubmitting: false,
isPaying: false
})
})
} else {
this.setData({
isSubmitting: false,
isPaying: false
})
}
},
/**
* 支付成功处理(双重确认)
*/
onPaymentSuccess() {
const { orderId } = this.data
if (!orderId) {
wx.showToast({
title: '订单ID不存在',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
return
}
// 显示支付成功提示(时间缩短)
wx.showToast({
title: '支付成功,确认中...',
icon: 'none',
duration: 1500
})
// 开始双重确认流程
this.checkOrderStatus()
},
/**
* 检查订单状态(双重确认)
*/
checkOrderStatus() {
const { orderId, checkCount, maxCheckCount } = this.data
// 显示加载(时间缩短)
wx.showLoading({
title: `确认支付状态${checkCount > 0 ? `(${checkCount + 1})` : ''}`,
mask: true
})
// 超时设为5秒
const timeoutTimer = setTimeout(() => {
wx.hideLoading()
this.handleConfirmError('网络超时')
}, 5000)
// 向后端发起支付复查请求
request({
url: '/dingdan/fucha',
method: 'POST',
data: {
dingdanid: orderId
}
})
.then(res => {
clearTimeout(timeoutTimer)
wx.hideLoading()
if (res.statusCode === 200 && res.data) {
const response = res.data
if (response.code === 0) {
// 确认成功,订单状态已更新
this.handleConfirmSuccess()
} else {
// 后端返回错误
this.handleConfirmError(response.msg || '订单状态异常')
}
} else {
// 网络错误
this.handleConfirmError('网络错误')
}
})
.catch(error => {
clearTimeout(timeoutTimer)
wx.hideLoading()
this.handleConfirmError('检查失败')
})
},
/**
* 处理确认成功
*/
handleConfirmSuccess() {
// 显示最终成功提示
wx.showToast({
title: '购买成功!',
icon: 'success',
duration: 1500
})
// 1.5秒后跳转到首页
setTimeout(() => {
wx.switchTab({
url: '/pages/index/index'
})
}, 1500)
this.setData({
isSubmitting: false,
isPaying: false
})
},
/**
* 处理确认错误
*/
handleConfirmError(errorMsg) {
const { checkCount, maxCheckCount } = this.data
const newCheckCount = checkCount + 1
if (newCheckCount < maxCheckCount) {
// 还可以重试
this.setData({
checkCount: newCheckCount
})
// 1秒后重试
setTimeout(() => {
this.checkOrderStatus()
}, 1000)
} else {
// 达到最大重试次数
wx.showModal({
title: '支付确认中',
content: '支付已成功,但订单状态确认异常。请稍后在订单列表中查看状态,如有问题请联系客服。',
showCancel: false,
confirmText: '我知道了',
success: (res) => {
if (res.confirm) {
wx.switchTab({
url: '/pages/index/index'
})
}
}
})
this.setData({
isSubmitting: false,
isPaying: false
})
}
}
})
+132 -124
View File
@@ -1,124 +1,132 @@
<!--pages/submit/submit.wxml--> <!--pages/submit/submit.wxml-->
<view class="submit-page"> <view class="page">
<!-- 商品信息区域 --> <!-- 商品摘要 - 拼多多SKU弹窗顶部风格 -->
<view class="product-section"> <view class="sku-header">
<view class="product-card"> <image class="sku-thumb" src="{{shangpinData.image || ''}}" mode="aspectFill" />
<!-- 商品图片 --> <view class="sku-header-info">
<view class="product-image-box"> <view class="sku-price-row">
<image <text class="sku-price-sym">¥</text>
src="{{shangpinData && shangpinData.image ? shangpinData.image : ''}}" <text class="sku-price-int">{{totalPriceInteger}}</text>
mode="aspectFill" <text class="sku-price-dec">.{{totalPriceDecimal}}</text>
class="product-image" </view>
/> <text class="sku-stock">库存{{shangpinData.kucun || 0}}件</text>
</view> <text class="sku-selected">已选:{{quantity}}份</text>
</view>
<!-- 商品信息 --> <view class="sku-close" bindtap="goBack">×</view>
<view class="product-info"> </view>
<view class="product-title">
<text class="title-text">{{shangpinData && shangpinData.title ? shangpinData.title : '商品标题'}}</text> <!-- 购买数量 -->
</view> <view class="sku-section">
<text class="sku-section-title">购买数量</text>
<!-- 价格信息 --> <view class="qty-row">
<view class="price-info"> <view class="qty-btn {{quantity <= 1 ? 'qty-btn--disabled' : ''}}" bindtap="onQtyMinus">-</view>
<text class="price-label">价格:</text> <text class="qty-val">{{quantity}}</text>
<view class="price-value"> <view class="qty-btn {{quantity >= 99 ? 'qty-btn--disabled' : ''}}" bindtap="onQtyPlus">+</view>
<text class="price-icon">¥</text> </view>
<text class="price-integer">{{shangpinData && shangpinData.priceInteger ? shangpinData.priceInteger : '0'}}</text> </view>
<text class="price-decimal">.{{shangpinData && shangpinData.priceDecimal ? shangpinData.priceDecimal : '00'}}</text>
</view> <!-- 服务信息 -->
</view> <view class="sku-section">
</view> <text class="sku-section-title">服务信息</text>
</view>
</view> <view class="form-field">
<view class="field-top">
<!-- 表单区域(左右布局) --> <text class="field-name">游戏昵称</text>
<view class="form-section"> <text class="field-required">必填</text>
<view class="form-card"> </view>
<!-- 游戏昵称 - 左右布局 --> <input
<view class="form-item-row"> class="field-input"
<view class="item-label-row"> type="text"
<text class="label-text-bold">昵称</text> placeholder="请输入您的游戏昵称"
<text class="label-required">*</text> placeholder-class="ph"
</view> maxlength="15"
<input value="{{nicheng}}"
class="item-input-row" bindinput="onNicknameInput"
type="text" />
placeholder="请输入您的昵称" </view>
placeholder-class="input-placeholder-gray"
maxlength="15" <view class="form-field">
value="{{nicheng}}" <view class="field-top">
bindinput="onNicknameInput" <text class="field-name">备注</text>
/> <text class="field-optional">选填</text>
</view> </view>
<input
<!-- 备注信息 - 左右布局 --> class="field-input"
<view class="form-item-row"> type="text"
<view class="item-label-row"> placeholder="补充其他联系信息"
<text class="label-text-bold">备注</text> placeholder-class="ph"
</view> maxlength="30"
<input value="{{beizhu}}"
class="item-input-row" bindinput="onRemarkInput"
type="text" />
placeholder="请补充您要补充的其他联系信息" </view>
placeholder-class="input-placeholder-gray"
maxlength="30" <view class="form-field">
value="{{beizhu}}" <view class="field-top">
bindinput="onRemarkInput" <text class="field-name">指定服务者</text>
/> <text class="field-optional">选填</text>
</view> </view>
<input
<!-- 指定打手ID - 左右布局 --> class="field-input"
<view class="form-item-row"> type="text"
<view class="item-label-row"> placeholder="填写指定服务者ID"
<text class="label-text-bold">指定服务者ID(选填)</text> placeholder-class="ph"
</view> maxlength="20"
<input value="{{zhiding}}"
class="item-input-row" bindinput="onZhidingInput"
type="text" />
placeholder="填写您需要指定的服务者" </view>
placeholder-class="input-placeholder-gray" </view>
maxlength="20"
value="{{zhiding}}" <!-- 服务保障 -->
bindinput="onZhidingInput" <view class="sku-section">
/> <text class="sku-section-title">服务保障</text>
</view> <view class="guarantee-row">
<view class="guarantee-item">
<!-- 支付方式 --> <text class="g-check">✓</text>
<view class="payment-item"> <text class="g-text">极速接单</text>
<view class="payment-method"> </view>
<image src="/images/wechat-pay.png" class="payment-icon" /> <view class="guarantee-item">
<text class="payment-text">微信支付</text> <text class="g-check">✓</text>
<view class="payment-checkbox"> <text class="g-text">资金担保</text>
<text class="checkbox-text">✓</text> </view>
</view> <view class="guarantee-item">
</view> <text class="g-check">✓</text>
</view> <text class="g-text">售后保障</text>
</view> </view>
</view> <view class="guarantee-item">
<text class="g-check">✓</text>
<!-- 底部操作栏 --> <text class="g-text">隐私保护</text>
<view class="footer-section"> </view>
<view class="total-price"> </view>
<text class="total-label">总价:</text> </view>
<view class="total-value">
<text class="price-icon">¥</text> <!-- 支付方式 -->
<text class="price-integer">{{shangpinData && shangpinData.priceInteger ? shangpinData.priceInteger : '0'}}</text> <view class="sku-section">
<text class="price-decimal">.{{shangpinData && shangpinData.priceDecimal ? shangpinData.priceDecimal : '00'}}</text> <text class="sku-section-title">支付方式</text>
</view> <view class="pay-row">
</view> <image src="/images/wechat-pay.png" class="pay-icon" />
<text class="pay-label">微信支付</text>
<view <view class="pay-check">
class="submit-btn {{canSubmit && !isSubmitting ? 'submit-btn-active' : 'submit-btn-disabled'}}" <text class="pay-check-icon">✓</text>
bindtap="onSubmitOrder" </view>
> </view>
<text class="btn-text">{{isSubmitting ? '提交中...' : '提交订单'}}</text> </view>
<view class="btn-glow"></view>
</view> <!-- 底部留白 -->
</view> <view class="bottom-space"></view>
<!-- 全局通知组件 --> <!-- 底部确认按钮 -->
<global-notification id="global-notification" /> <view class="confirm-bar">
<view
<!-- 弹窗公告组件(供 PopupService 调用) --> class="confirm-btn {{canSubmit && !isSubmitting ? 'confirm-btn--active' : 'confirm-btn--disabled'}}"
<popup-notice id="popupNotice" /> bindtap="onSubmitOrder"
</view> hover-class="confirm-btn--hover"
>
{{isSubmitting ? '提交中...' : '确定'}}
</view>
</view>
<global-notification id="global-notification" />
<popup-notice id="popupNotice" />
</view>
+296 -280
View File
@@ -1,280 +1,296 @@
/* pages/submit/submit.wxss */ /* pages/submit/submit.wxss */
.submit-page { page {
display: flex; background: #f2f2f2;
flex-direction: column; }
min-height: 100vh;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f3ff 100%); .page {
box-sizing: border-box; min-height: 100vh;
} padding-bottom: 140rpx;
box-sizing: border-box;
/* 商品信息区域 */ }
.product-section {
padding: 30rpx 30rpx 20rpx 30rpx; /* SKU头部 - 拼多多弹窗风格 */
flex-shrink: 0; .sku-header {
} display: flex;
background: #fff;
.product-card { padding: 24rpx;
background: white; position: relative;
border-radius: 20rpx; margin-bottom: 16rpx;
padding: 30rpx; }
display: flex;
align-items: center; .sku-thumb {
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); width: 180rpx;
} height: 180rpx;
border-radius: 12rpx;
.product-image-box { flex-shrink: 0;
width: 160rpx; margin-right: 20rpx;
height: 160rpx; background: #f5f5f5;
border-radius: 16rpx; }
overflow: hidden;
background: #f5f5f5; .sku-header-info {
flex-shrink: 0; flex: 1;
margin-right: 24rpx; display: flex;
} flex-direction: column;
justify-content: space-between;
.product-image { padding: 4rpx 0;
width: 100%; }
height: 100%;
} .sku-price-row {
display: flex;
.product-info { align-items: baseline;
flex: 1; }
display: flex;
flex-direction: column; .sku-price-sym {
justify-content: space-between; font-size: 28rpx;
height: 160rpx; color: #e02e24;
} font-weight: 700;
}
.product-title {
margin-bottom: 20rpx; .sku-price-int {
} font-size: 48rpx;
color: #e02e24;
.title-text { font-weight: 800;
font-size: 30rpx; line-height: 1;
font-weight: 600; margin-left: 2rpx;
color: #222; }
line-height: 1.4;
display: -webkit-box; .sku-price-dec {
-webkit-box-orient: vertical; font-size: 28rpx;
-webkit-line-clamp: 2; color: #e02e24;
overflow: hidden; font-weight: 700;
} }
.price-info { .sku-stock {
display: flex; font-size: 24rpx;
align-items: baseline; color: #999;
} }
.price-label { .sku-selected {
font-size: 26rpx; font-size: 24rpx;
color: #666; color: #666;
font-weight: 500; }
}
.sku-close {
.price-value { position: absolute;
display: flex; top: 16rpx;
align-items: baseline; right: 16rpx;
} width: 48rpx;
height: 48rpx;
.price-icon { display: flex;
font-size: 28rpx; align-items: center;
color: #ff4d4f; justify-content: center;
font-weight: 600; font-size: 36rpx;
} color: #999;
border-radius: 50%;
.price-integer { background: #f5f5f5;
font-size: 38rpx; line-height: 1;
color: #ff4d4f; }
font-weight: 700;
margin-left: 4rpx; /* SKU区块 */
} .sku-section {
background: #fff;
.price-decimal { padding: 24rpx 28rpx;
font-size: 28rpx; margin-bottom: 16rpx;
color: #ff4d4f; }
font-weight: 600;
} .sku-section-title {
font-size: 28rpx;
/* 表单区域 */ font-weight: 700;
.form-section { color: #222;
padding: 0 30rpx 20rpx 30rpx; margin-bottom: 20rpx;
flex-shrink: 0; display: block;
} }
.form-card { /* 数量选择器 */
background: white; .qty-row {
border-radius: 20rpx; display: flex;
padding: 30rpx; align-items: center;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.05); }
}
.qty-btn {
.form-item-row { width: 64rpx;
display: flex; height: 64rpx;
align-items: center; border-radius: 8rpx;
padding: 25rpx 0; background: #f5f5f5;
border-bottom: 1rpx solid #f5f5f5; display: flex;
} align-items: center;
justify-content: center;
.form-item-row:last-child { font-size: 32rpx;
border-bottom: none; font-weight: 700;
} color: #333;
border: 1rpx solid #e8e8e8;
.item-label-row { }
width: 220rpx;
flex-shrink: 0; .qty-btn--disabled {
display: flex; color: #ccc;
align-items: center; border-color: #f0f0f0;
} background: #fafafa;
}
.label-text-bold {
font-size: 30rpx; .qty-val {
font-weight: 600; min-width: 80rpx;
color: #333; text-align: center;
} font-size: 32rpx;
font-weight: 700;
.label-required { color: #222;
font-size: 30rpx; margin: 0 20rpx;
color: #ff4d4f; }
margin-left: 8rpx;
} /* 表单字段 */
.form-field {
.item-input-row { padding: 16rpx 0;
flex: 1; border-bottom: 1rpx solid #f5f5f5;
font-size: 28rpx; }
color: #222;
height: 60rpx; .form-field:last-child {
line-height: 60rpx; border-bottom: none;
padding: 0 20rpx; padding-bottom: 0;
text-align: right; }
background: #fafafa;
border-radius: 10rpx; .field-top {
} display: flex;
align-items: center;
.input-placeholder-gray { margin-bottom: 10rpx;
color: #999; }
font-size: 28rpx;
} .field-name {
font-size: 26rpx;
/* 支付方式 */ font-weight: 600;
.payment-item { color: #333;
margin-top: 30rpx; }
padding-top: 30rpx;
border-top: 1rpx solid #f0f0f0; .field-required {
} font-size: 20rpx;
color: #e02e24;
.payment-method { background: #fff1f0;
display: flex; padding: 2rpx 10rpx;
align-items: center; border-radius: 4rpx;
padding: 20rpx 0; margin-left: 10rpx;
} }
.payment-icon { .field-optional {
width: 48rpx; font-size: 20rpx;
height: 48rpx; color: #bbb;
margin-right: 20rpx; margin-left: 10rpx;
} }
.payment-text { .field-input {
font-size: 28rpx; width: 100%;
color: #333; height: 72rpx;
font-weight: 500; background: #f7f7f7;
flex: 1; border-radius: 8rpx;
} padding: 0 20rpx;
font-size: 26rpx;
.payment-checkbox { color: #333;
width: 40rpx; border: 1rpx solid #eee;
height: 40rpx; box-sizing: border-box;
border-radius: 50%; }
background: #52c41a;
display: flex; .ph {
align-items: center; color: #bbb;
justify-content: center; font-size: 26rpx;
} }
.checkbox-text { /* 服务保障 */
color: white; .guarantee-row {
font-size: 28rpx; display: flex;
font-weight: 600; flex-wrap: wrap;
} gap: 12rpx 20rpx;
}
/* 底部操作栏 */
.footer-section { .guarantee-item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; }
padding: 20rpx 30rpx;
background: white; .g-check {
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08); font-size: 20rpx;
margin-top: auto; color: #e02e24;
flex-shrink: 0; font-weight: 700;
} margin-right: 4rpx;
}
.total-price {
display: flex; .g-text {
align-items: baseline; font-size: 24rpx;
flex: 1; color: #666;
} }
.total-label { /* 支付方式 */
font-size: 30rpx; .pay-row {
color: #333; display: flex;
font-weight: 600; align-items: center;
} }
.total-value { .pay-icon {
display: flex; width: 44rpx;
align-items: baseline; height: 44rpx;
} margin-right: 16rpx;
}
.submit-btn {
width: 240rpx; .pay-label {
height: 88rpx; flex: 1;
border-radius: 44rpx; font-size: 28rpx;
display: flex; color: #333;
align-items: center; font-weight: 500;
justify-content: center; }
position: relative;
overflow: hidden; .pay-check {
transition: all 0.3s; width: 36rpx;
} height: 36rpx;
border-radius: 50%;
.submit-btn-active { background: #52c41a;
background: linear-gradient(135deg, #52c41a, #389e0d); display: flex;
box-shadow: 0 8rpx 32rpx rgba(255, 77, 79, 0.4); align-items: center;
} justify-content: center;
}
.submit-btn-disabled {
background: #ccc; .pay-check-icon {
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1); color: #fff;
} font-size: 22rpx;
font-weight: 700;
.submit-btn-active:active { }
transform: scale(0.98);
box-shadow: 0 4rpx 20rpx rgba(255, 77, 79, 0.5); /* 底部留白 */
} .bottom-space {
height: 20rpx;
.btn-text { }
font-size: 32rpx;
color: white; /* 底部确认按钮 */
font-weight: 600; .confirm-bar {
position: relative; position: fixed;
z-index: 2; bottom: 0;
} right: 0;
width: 50%;
.btn-glow { height: 100rpx;
position: absolute; background: #fff;
top: 0; padding-bottom: env(safe-area-inset-bottom);
left: 0; z-index: 9999;
right: 0; display: flex;
bottom: 0; }
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transform: translateX(-100%); .confirm-btn {
animation: btnGlow 3s infinite; width: 100%;
} height: 100%;
border-radius: 0;
@keyframes btnGlow { display: flex;
0% { transform: translateX(-100%); } align-items: center;
50% { transform: translateX(100%); } justify-content: center;
100% { transform: translateX(100%); } font-size: 32rpx;
} font-weight: 700;
color: #fff;
letter-spacing: 4rpx;
}
.confirm-btn--active {
background: #e02e24;
}
.confirm-btn--disabled {
background: #ccc;
}
.confirm-btn--hover {
opacity: 0.9;
transform: scale(0.98);
}
+159 -169
View File
@@ -1,169 +1,159 @@
// pages/verify/verify.js // pages/verify/verify.js
import request from '../../utils/request.js' import { createPage, request } from '../../utils/base-page.js'
import { enterLockedRole } from '../../utils/primary-role.js' import { enterLockedRole } from '../../utils/primary-role.js'
const API_MAP = { const API_MAP = {
dashou: '/yonghu/dashouzhuce', dashou: '/yonghu/dashouzhuce',
shangjia: '/yonghu/shangjiahuce', shangjia: '/yonghu/shangjiahuce',
guanshi: '/yonghu/guanshizhuce', guanshi: '/yonghu/guanshizhuce',
zuzhang: '/yonghu/zuzhangzhuce', zuzhang: '/yonghu/zuzhangzhuce',
kaoheguan: '/dengji/khgzc' kaoheguan: '/dengji/khgzc'
} }
const STATUS_KEY_MAP = { const STATUS_KEY_MAP = {
dashou: 'dashoustatus', dashou: 'dashoustatus',
shangjia: 'shangjiastatus', shangjia: 'shangjiastatus',
guanshi: 'guanshistatus', guanshi: 'guanshistatus',
zuzhang: 'zuzhangstatus', zuzhang: 'zuzhangstatus',
kaoheguan: 'kaoheguanstatus' kaoheguan: 'kaoheguanstatus'
} }
const NAME_MAP = { const NAME_MAP = {
dashou: '接单员', dashou: '接单员',
shangjia: '商家', shangjia: '商家',
guanshi: '管事', guanshi: '管事',
zuzhang: '组长', zuzhang: '组长',
kaoheguan: '考核官' kaoheguan: '考核官'
} }
Page({ Page(createPage({
data: { data: {
authType: '', authType: '',
authName: '', authName: '',
pageState: 'need_register', pageState: 'need_register',
inviteCode: '', inviteCode: '',
isLoading: false, isLoading: false,
}, },
onLoad(options) { onLoad(options) {
const type = options.type || '' const type = options.type || ''
if (!API_MAP[type]) { if (!API_MAP[type]) {
wx.showToast({ title: '认证类型错误', icon: 'none' }) wx.showToast({ title: '认证类型错误', icon: 'none' })
setTimeout(() => wx.navigateBack(), 1500) setTimeout(() => wx.navigateBack(), 1500)
return return
} }
this.setData({ authType: type, authName: NAME_MAP[type] }) this.setData({ authType: type, authName: NAME_MAP[type] })
this.checkStatus() this.checkStatus()
}, },
onShow() { onShow() {
if (this.data.authType) this.checkStatus() if (this.data.authType) this.checkStatus()
}, },
checkStatus() { checkStatus() {
const key = STATUS_KEY_MAP[this.data.authType] const key = STATUS_KEY_MAP[this.data.authType]
const val = wx.getStorageSync(key) const val = wx.getStorageSync(key)
if (val === 1) { if (val === 1) {
this.setData({ pageState: 'done' }) this.setData({ pageState: 'done' })
} else { } else {
this.setData({ pageState: 'need_register' }) this.setData({ pageState: 'need_register' })
} }
}, },
onInviteCodeInput(e) { onInviteCodeInput(e) {
this.setData({ inviteCode: e.detail.value.trim() }) this.setData({ inviteCode: e.detail.value.trim() })
}, },
async onRegister() { async onRegister() {
const { inviteCode, authType } = this.data const { inviteCode, authType } = this.data
if (!inviteCode) { if (!inviteCode) {
wx.showToast({ title: '请输入邀请码', icon: 'none' }) wx.showToast({ title: '请输入邀请码', icon: 'none' })
return return
} }
if (inviteCode.length > 100) { if (inviteCode.length > 100) {
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' }) wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' })
return return
} }
this.setData({ isLoading: true }) this.setData({ isLoading: true })
wx.showLoading({ title: '注册中...', mask: true }) wx.showLoading({ title: '注册中...', mask: true })
try { try {
const res = await request({ const res = await request({
url: API_MAP[authType], url: API_MAP[authType],
method: 'POST', method: 'POST',
data: { inviteCode } data: { inviteCode }
}) })
if (res && res.data.code === 200) { if (res && res.data.code === 200) {
const data = res.data.data || res.data const data = res.data.data || res.data
const app = getApp() const app = getApp()
// 缓存身份状态 // 缓存身份状态
const key = STATUS_KEY_MAP[authType] const key = STATUS_KEY_MAP[authType]
if (data[key] !== undefined) { if (data[key] !== undefined) {
wx.setStorageSync(key, data[key]) wx.setStorageSync(key, data[key])
app.globalData[key] = data[key] app.globalData[key] = data[key]
} else { } else {
wx.setStorageSync(key, 1) wx.setStorageSync(key, 1)
app.globalData[key] = 1 app.globalData[key] = 1
} }
// 更新其他可能返回的身份状态字段 // 更新其他可能返回的身份状态字段
const allKeys = ['dashoustatus', 'shangjiastatus', 'guanshistatus', 'zuzhangstatus', 'kaoheguanstatus'] const allKeys = ['dashoustatus', 'shangjiastatus', 'guanshistatus', 'zuzhangstatus', 'kaoheguanstatus']
allKeys.forEach(k => { allKeys.forEach(k => {
if (data[k] !== undefined && k !== key) { if (data[k] !== undefined && k !== key) {
wx.setStorageSync(k, data[k]) wx.setStorageSync(k, data[k])
app.globalData[k] = data[k] app.globalData[k] = data[k]
} }
}) })
// 保存各身份详细信息 // 保存各身份详细信息
if (authType === 'dashou') { if (authType === 'dashou') {
const fields = ['dashounicheng','zhanghaostatus','yongjin','zonge','yajin','chenghao','jifen','clumber','chengjiaoliang','zaixianzhuangtai'] const fields = ['dashounicheng','zhanghaostatus','yongjin','zonge','yajin','chenghao','jifen','clumber','chengjiaoliang','zaixianzhuangtai']
fields.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] }) fields.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] })
} else if (authType === 'shangjia') { } else if (authType === 'shangjia') {
app.globalData.shangjia = app.globalData.shangjia || {} app.globalData.shangjia = app.globalData.shangjia || {}
const sf = ['nicheng','sjyue','fabu','chengjiao','tuikuan','jinridingdan','jinriliushui','jinyuedingdan','jinyueliushui','zhuangtai'] const sf = ['nicheng','sjyue','fabu','chengjiao','tuikuan','jinridingdan','jinriliushui','jinyuedingdan','jinyueliushui','zhuangtai']
sf.forEach(f => { if (data[f] !== undefined) app.globalData.shangjia[f] = data[f] }) sf.forEach(f => { if (data[f] !== undefined) app.globalData.shangjia[f] = data[f] })
} else if (authType === 'guanshi') { } else if (authType === 'guanshi') {
app.globalData.guanshi = app.globalData.guanshi || {} app.globalData.guanshi = app.globalData.guanshi || {}
const gf = ['gszhstatus','yaoqingzongshu','fenyongzonge','fenyongtixian','yichongzhiDashou'] const gf = ['gszhstatus','yaoqingzongshu','fenyongzonge','fenyongtixian','yichongzhiDashou']
gf.forEach(f => { if (data[f] !== undefined) app.globalData.guanshi[f] = data[f] }) gf.forEach(f => { if (data[f] !== undefined) app.globalData.guanshi[f] = data[f] })
} else if (authType === 'zuzhang') { } else if (authType === 'zuzhang') {
const zf = ['ketixian','guanshiCount','fenhongZonge'] const zf = ['ketixian','guanshiCount','fenhongZonge']
zf.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] }) zf.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] })
} else if (authType === 'kaoheguan') { } else if (authType === 'kaoheguan') {
app.globalData.kaoheguan = app.globalData.kaoheguan || {} app.globalData.kaoheguan = app.globalData.kaoheguan || {}
const kf = ['shenhe_zongshu','tongguo_zongshu','yue','zonge','shenhe_zhuangtai','bankuai_list'] const kf = ['shenhe_zongshu','tongguo_zongshu','yue','zonge','shenhe_zhuangtai','bankuai_list']
kf.forEach(f => { if (data[f] !== undefined) app.globalData.kaoheguan[f] = data[f] }) kf.forEach(f => { if (data[f] !== undefined) app.globalData.kaoheguan[f] = data[f] })
} }
wx.hideLoading() wx.hideLoading()
this.setData({ isLoading: false, pageState: 'done' }) this.setData({ isLoading: false, pageState: 'done' })
wx.showToast({ title: '认证成功', icon: 'success' }) wx.showToast({ title: '认证成功', icon: 'success' })
if (authType === 'dashou' || authType === 'shangjia') { if (authType === 'dashou' || authType === 'shangjia') {
setTimeout(() => enterLockedRole(authType), 1200) setTimeout(() => enterLockedRole(authType), 1200)
} else if (authType === 'guanshi') { } else if (authType === 'guanshi') {
setTimeout(() => { setTimeout(() => {
const pages = getCurrentPages() const pages = getCurrentPages()
if (pages.length > 1) { if (pages.length > 1) {
wx.navigateBack() wx.navigateBack()
} else { } else {
wx.redirectTo({ url: '/pages/fighter/fighter' }) wx.redirectTo({ url: '/pages/fighter/fighter' })
} }
}, 800) }, 800)
} }
} else { } else {
wx.hideLoading() wx.hideLoading()
this.setData({ isLoading: false }) this.setData({ isLoading: false })
wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none' }) wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none' })
} }
} catch (err) { } catch (err) {
wx.hideLoading() wx.hideLoading()
this.setData({ isLoading: false }) this.setData({ isLoading: false })
wx.showToast({ title: err.message || '注册失败', icon: 'none' }) wx.showToast({ title: err.message || '注册失败', icon: 'none' })
} }
}, },
}))
goToKefu() {
const app = getApp()
const { link, enterpriseId } = app.globalData.kefuConfig || {}
if (!link || !enterpriseId) {
wx.showToast({ title: '客服配置未加载', icon: 'none' })
return
}
wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId })
}
})
+54 -72
View File
@@ -1,72 +1,54 @@
// pages/withdraw/withdraw.js // pages/withdraw/withdraw.js
import request from '../../utils/request.js'; import { createPage, request } from '../../utils/base-page.js';
import PopupService from '../../services/popupService.js'; import PopupService from '../../services/popupService.js';
const app = getApp(); const app = getApp();
Page({ Page(createPage({
data: { data: {
pageMode: 0, pageMode: 0,
pageOptions: {}, pageOptions: {},
loadError: false, loadError: false,
}, },
onLoad(options) { onLoad(options) {
this.setData({ this.setData({
pageOptions: options || {} pageOptions: options || {}
}); });
this.fetchPageMode(); this.fetchPageMode();
}, },
onShow() { onShow() {
this.registerNotificationComponent(); this.registerNotificationComponent();
PopupService.checkAndShow(this, 'tixian'); PopupService.checkAndShow(this, 'tixian');
}, },
onHide() { async fetchPageMode() {
const popupComp = this.selectComponent('#popupNotice'); try {
if (popupComp && popupComp.cleanup) { const res = await request({
popupComp.cleanup(); url: '/peizhi/hqtxzsym',
} method: 'POST'
}, });
// 注册通知组件 if (res.statusCode === 200 && res.data && res.data.code === 0) {
registerNotificationComponent() { const mode = parseInt(res.data.data.mode);
const notificationComp = this.selectComponent('#global-notification'); this.setData({
if (notificationComp && notificationComp.showNotification) { pageMode: (mode === 1 || mode === 2) ? mode : 1,
app.globalData.globalNotification = { loadError: false
show: (data) => notificationComp.showNotification(data), });
hide: () => notificationComp.hideNotification() } else {
}; this.setData({ pageMode: 1, loadError: false });
} }
}, } catch (err) {
console.error('获取展示模式失败:', err);
async fetchPageMode() { this.setData({ loadError: true });
try { }
const res = await request({ },
url: '/peizhi/hqtxzsym',
method: 'POST' retryFetch() {
}); this.setData({ loadError: false, pageMode: 0 });
this.fetchPageMode();
if (res.statusCode === 200 && res.data && res.data.code === 0) { },
const mode = parseInt(res.data.data.mode);
this.setData({ stopPropagation() {}
pageMode: (mode === 1 || mode === 2) ? mode : 1, }))
loadError: false
});
} else {
this.setData({ pageMode: 1, loadError: false });
}
} catch (err) {
console.error('获取展示模式失败:', err);
this.setData({ loadError: true });
}
},
retryFetch() {
this.setData({ loadError: false, pageMode: 0 });
this.fetchPageMode();
},
stopPropagation() {}
});
+8 -7
View File
@@ -34,6 +34,7 @@
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.8); box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.8);
padding: 0 20rpx; padding: 0 20rpx;
position: relative; position: relative;
overflow: hidden;
} }
.tab-capsule::before { .tab-capsule::before {
@@ -46,19 +47,19 @@
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
transparent 0%, transparent 0%,
rgba(255, 255, 255, 0.25) 20%, rgba(255, 255, 255, 0.15) 20%,
rgba(255, 255, 255, 0.35) 50%, rgba(255, 255, 255, 0.22) 50%,
rgba(255, 255, 255, 0.25) 80%, rgba(255, 255, 255, 0.15) 80%,
transparent 100% transparent 100%
); );
animation: shine 3s infinite; animation: shine 8s infinite;
z-index: 1; z-index: 1;
} }
@keyframes shine { @keyframes shine {
0% { left: -100%; } 0% { left: -60%; }
25% { left: 150%; } 15% { left: 100%; }
100% { left: 150%; } 100% { left: 100%; }
} }
.tab-item { .tab-item {
+46
View File
@@ -0,0 +1,46 @@
// utils/api-helper.js - API 响应解析
export function isApiSuccess(body) {
if (!body) return false;
const code = Number(body.code);
return code === 200 || code === 0;
}
export function getApiMsg(body) {
if (!body) return '';
return body.msg || body.message || '';
}
export function extractUserData(body) {
if (!body) return {};
if (body.data && typeof body.data === 'object' && !Array.isArray(body.data)) {
return body.data;
}
const userData = { ...body };
delete userData.code;
delete userData.msg;
delete userData.message;
return userData;
}
/** 解析扫码场景参数 */
export function parseSceneOptions(options) {
if (!options || !options.scene) return options || {};
try {
const scene = decodeURIComponent(options.scene);
return { ...options, inviteCode: scene };
} catch (e) {
return options;
}
}
/** 订单状态中文映射 */
const ORDER_STATUS_MAP = {
1: '已下单', 2: '进行中', 3: '已完成',
4: '退款中', 5: '已退款', 6: '退款失败',
7: '指定中', 8: '结算中',
};
export function getOrderStatusText(status) {
return ORDER_STATUS_MAP[status] || '未知状态';
}
+362
View File
@@ -0,0 +1,362 @@
// utils/base-page.js - 页面公共框架
import request from './request.js';
import popupService from '../services/popupService.js';
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
syncGroupFields,
ensureRoleOnCenterPage,
clearCacheAndEnterNormal,
refreshTabBar,
} from './role-tab-bar.js';
import { lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole } from './primary-role.js';
import { resolveAvatarFromStorage } from './avatar.js';
import { isApiSuccess, getApiMsg, extractUserData, parseSceneOptions } from './api-helper.js';
// ===== 基类数据 =====
const BASE_DATA = {
isLoading: false,
lastRefreshTime: 0,
canRefresh: true,
};
// ===== 基类方法 =====
const BASE_METHODS = {
// ---------- 通知组件 ----------
registerNotificationComponent() {
const app = getApp();
const comp = this.selectComponent('#global-notification');
if (comp && comp.showNotification) {
app.globalData.globalNotification = {
show: (data) => comp.showNotification(data),
hide: () => comp.hideNotification(),
};
}
},
// ---------- 弹窗清理 ----------
cleanupPopup() {
const comp = this.selectComponent('#popupNotice');
if (comp && comp.cleanup) comp.cleanup();
},
// ---------- 冷启动弹窗 ----------
checkColdStartPopup(pageId) {
const app = getApp();
if (!app.globalData.hasShownPopupOnColdStart) {
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => popupService.checkAndShow(this, pageId), 300);
}
},
// ---------- 头像加载 ----------
loadAvatar() {
this.setData({ avatarUrl: resolveAvatarFromStorage(getApp()) });
},
// ---------- 通用用户信息加载 ----------
loadUserInfo() {
const app = getApp();
const { nick, uid, avatarUrl } = (app.getUserProfileForDisplay && app.getUserProfileForDisplay()) || {};
this.setData({
avatarUrl: avatarUrl || resolveAvatarFromStorage(app),
uid: uid || wx.getStorageSync('uid') || '',
nicheng: nick || wx.getStorageSync('nicheng') || '',
});
},
// ---------- 通用数据加载 ----------
async loadData(opts) {
const {
url, method = 'POST', data: reqData,
fields, loadingKey = 'isLoading',
successCode = 200, errorMsg = '加载失败',
onSuccess, onError,
} = opts;
this.setData({ [loadingKey]: true });
try {
const res = await request({ url, method, data: reqData });
if (res && (res.data.code === successCode || (successCode === 0 && res.data.code === 0))) {
const resData = res.data.data || {};
if (fields) {
const updateData = {};
Object.keys(fields).forEach((pageKey) => {
const apiField = fields[pageKey];
updateData[pageKey] = apiField in resData ? resData[apiField] : (typeof apiField === 'object' ? apiField.default : '');
});
this.setData({ ...updateData, [loadingKey]: false });
}
if (onSuccess) onSuccess(resData, res);
return resData;
}
throw new Error(res?.data?.msg || res?.data?.message || errorMsg);
} catch (error) {
this.setData({ [loadingKey]: false });
if (onError) {
onError(error);
} else {
wx.showToast({ title: error.message || errorMsg, icon: 'none' });
}
return null;
}
},
// ---------- 节流刷新 ----------
throttledRefresh(fn, interval = 3000) {
const now = Date.now();
if (!this.data.canRefresh || (now - this.data.lastRefreshTime < interval)) {
wx.showToast({ title: '操作过快', icon: 'none' });
return;
}
this.setData({ lastRefreshTime: now, canRefresh: false });
fn.call(this);
setTimeout(() => this.setData({ canRefresh: true }), interval);
},
// ---------- 复制 UID ----------
copyUid() {
const uid = this.data.uid;
if (!uid) {
wx.showToast({ title: 'UID不存在', icon: 'none' });
return;
}
wx.setClipboardData({
data: uid,
success: () => wx.showToast({ title: '已复制UID', icon: 'success' }),
fail: () => wx.showToast({ title: '复制失败', icon: 'none' }),
});
},
// ---------- 清除缓存 ----------
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: (r) => { if (r.confirm) clearCacheAndEnterNormal(this); },
});
},
// ---------- 返回点单端 ----------
switchToNormal() {
lockPrimaryRole('normal');
wx.reLaunch({ url: '/pages/mine/mine' });
},
// ---------- 客服 ----------
goToKefu() {
const app = getApp();
const { link, enterpriseId } = app.globalData.kefuConfig || {};
if (!link || !enterpriseId) {
wx.showToast({ title: '客服配置未加载', icon: 'none' });
return;
}
wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId });
},
// ---------- API 辅助(快捷访问) ----------
_isApiSuccess: isApiSuccess,
_getApiMsg: getApiMsg,
_extractUserData: extractUserData,
// ---------- 邀请码注册通用流程 ----------
async registerWithInviteCode(opts) {
const {
inviteCode, apiUrl, role, statusKey,
alreadyMsg, successMsg = '注册成功',
onSuccess,
} = opts;
if (!inviteCode) {
wx.showToast({ title: '请输入邀请码', icon: 'none' });
return;
}
if (inviteCode.length > 100) {
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' });
return;
}
this.setData({ isLoading: true });
try {
const res = await request({ url: apiUrl, method: 'POST', data: { inviteCode } });
const body = res?.data || {};
if (isApiSuccess(body)) {
const userData = extractUserData(body);
syncRoleStatuses(userData);
syncProfileFields(userData);
syncGroupFields(userData);
if (userData.token) {
wx.setStorageSync('token', userData.token);
getApp().globalData.token = userData.token;
}
if (!Object.prototype.hasOwnProperty.call(userData, statusKey) &&
!isRoleStatusActive(wx.getStorageSync(statusKey))) {
wx.setStorageSync(statusKey, 1);
getApp().globalData[statusKey] = 1;
}
if (onSuccess) {
await onSuccess(userData, body);
}
this.setData({ isLoading: false });
wx.showToast({ title: successMsg, icon: 'success' });
} else {
const msg = getApiMsg(body);
if (alreadyMsg && msg && msg.includes(alreadyMsg)) {
if (onSuccess) {
await onSuccess(extractUserData(body), body);
}
this.setData({ isLoading: false });
wx.showToast({ title: msg, icon: 'success' });
} else {
this.setData({ isLoading: false });
wx.showToast({ title: msg || '注册失败', icon: 'none' });
}
}
} catch (error) {
this.setData({ isLoading: false });
wx.showToast({ title: error.message || '注册失败', icon: 'none' });
}
},
// ---------- 邀请码 + 登录检查 ----------
handleInviteCodeWithLoginCheck(opts) {
const { inviteCode, role, statusKey, alreadyMsg, onNeedLogin, onAlreadyRegistered } = opts;
const token = wx.getStorageSync('token');
const uid = wx.getStorageSync('uid');
if (!token || !uid) {
if (onNeedLogin) onNeedLogin(inviteCode);
return;
}
const isActive = isRoleStatusActive(wx.getStorageSync(statusKey));
if (!isActive) {
wx.showLoading({ title: '注册中...', mask: true });
this.registerWithInviteCode(opts);
} else {
if (onAlreadyRegistered) onAlreadyRegistered();
else wx.showToast({ title: alreadyMsg || '您已注册', icon: 'success' });
}
},
// ---------- 微信登录 + 注册 ----------
loginAndRegisterWithInviteCode(opts) {
const { inviteCode, onLoginSuccess } = opts;
wx.showLoading({ title: '登录注册中...', mask: true });
wx.login({
success: (loginRes) => {
if (loginRes.code) {
if (onLoginSuccess) onLoginSuccess(loginRes.code, inviteCode);
} else {
wx.hideLoading();
wx.showToast({ title: '微信登录失败', icon: 'none' });
}
},
fail: () => {
wx.hideLoading();
wx.showToast({ title: '微信登录失败', icon: 'none' });
},
});
},
// ---------- 图片 URL 初始化 ----------
initImageUrls(urlMap) {
const ossImageUrl = getApp().globalData.ossImageUrl || '';
const result = {};
Object.keys(urlMap).forEach((key) => {
const path = urlMap[key];
result[key] = path.startsWith('http') || path.startsWith('/') ? path : ossImageUrl + path;
});
return result;
},
};
// ===== 角色中心页增强 =====
function createRoleCenterMixin(roleConfig) {
const { role, statusKey, dataFlag, pageId, apiUrl } = roleConfig;
return {
onReady() {
if (isCenterPageActive(this, dataFlag, statusKey)) {
ensureRoleOnCenterPage(this, role);
}
},
onHide() {
this.cleanupPopup();
},
checkRoleStatus() {
try {
const status = wx.getStorageSync(statusKey);
const uid = wx.getStorageSync('uid');
if (isRoleStatusActive(status)) {
this.setData({ [dataFlag]: true, uid: uid || '' });
this.loadAvatar();
if (this.checkGlobalData) this.checkGlobalData();
ensureRoleOnCenterPage(this, role);
} else {
this.setData({ [dataFlag]: false });
}
} catch (error) {
this.setData({ [dataFlag]: false });
}
},
};
}
/**
* 创建页面配置(合并基类方法)
* @param {Object} pageConfig - 页面特有配置
* @param {Object} [options] - 可选配置
* @param {Object} [options.roleConfig] - 角色中心页配置 { role, statusKey, dataFlag, pageId, apiUrl }
* @returns {Object} 完整的 Page 配置
*/
export function createPage(pageConfig, options = {}) {
const { roleConfig } = options;
// 合并 data
const data = { ...BASE_DATA, ...(pageConfig.data || {}) };
// 合并方法
const methods = { ...BASE_METHODS };
// 如果是角色中心页,混入角色相关方法
if (roleConfig) {
Object.assign(methods, createRoleCenterMixin(roleConfig));
}
// 页面特有方法覆盖基类方法
const allMethods = { ...methods, ...pageConfig };
// 确保 data 正确
allMethods.data = data;
return allMethods;
}
// 导出工具函数供页面直接使用
export {
request,
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
syncGroupFields,
ensureRoleOnCenterPage,
clearCacheAndEnterNormal,
refreshTabBar,
lockPrimaryRole,
migrateLegacyCenterRole,
enterLockedRole,
isApiSuccess,
getApiMsg,
extractUserData,
parseSceneOptions,
};