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

This commit is contained in:
2026-06-13 18:19:46 +08:00
parent 6fbae9b32c
commit 3dc03f6fe5
51 changed files with 7154 additions and 9035 deletions

View File

@@ -1,218 +1,163 @@
// pages/assessor/assessor.js
// 只新增了两个字段的接收,其余所有代码未变
import request from '../../utils/request.js';
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
ensureRoleOnCenterPage,
} from '../../utils/role-tab-bar.js';
const app = getApp();
Page({
data: {
isKaoheguan: false,
isLoading: false,
inviteCode: '',
statusText: '用户尚未注册或账号已被封禁',
uid: '',
avatarUrl: '',
icons: {
refresh: '',
tixian: '',
daofen: '',
jilu: '',
zhongxin: '',
bgImage: ''
},
kaoheData: {
shenheZongshu: 0,
tongguoZongshu: 0,
yue: '0.00',
zonge: '0.00',
shenheZhuangtai: 0,
bankuaiList: [],
isRenzheng: false, // 🆕 是否认证
pendingShenheCount: 0 // 🆕 审核中数量
},
lastRefreshTime: 0,
canRefresh: true,
planetPaused: {
top: false,
bottom: false,
left: false,
right: false
}
},
_planetTimers: {},
onLoad() {
wx.redirectTo({ url: '/pages/fighter/fighter' });
return;
const ossUrl = app.globalData.ossImageUrl || '';
const iconBase = 'beijing/kaohe/';
this.setData({
'icons.refresh': ossUrl + iconBase + 'shuaxin.png',
'icons.tixian': ossUrl + iconBase + 'tixian.png',
'icons.daofen': ossUrl + iconBase + 'daofen.png',
'icons.jilu': ossUrl + iconBase + 'jilu.png',
'icons.zhongxin': ossUrl + iconBase + 'zhongxin.png',
'icons.bgImage': ossUrl + iconBase + 'top_bg.jpg'
});
const kaoheguanstatus = wx.getStorageSync('kaoheguanstatus');
if (isRoleStatusActive(kaoheguanstatus)) {
this.loadUserInfo();
this.setData({ isKaoheguan: true });
this.getKaoheguanInfo();
ensureRoleOnCenterPage(this, 'kaoheguan');
} else {
this.setData({ isKaoheguan: false });
}
},
onReady() {
if (isCenterPageActive(this, 'isKaoheguan', 'kaoheguanstatus')) {
ensureRoleOnCenterPage(this, 'kaoheguan');
}
},
onShow() {
if (isCenterPageActive(this, 'isKaoheguan', 'kaoheguanstatus')) {
if (!this.data.isKaoheguan) this.setData({ isKaoheguan: true });
ensureRoleOnCenterPage(this, 'kaoheguan');
this.refreshKaoheInfo();
}
},
loadUserInfo() {
const uid = wx.getStorageSync('uid') || '';
const touxiang = wx.getStorageSync('touxiang') || '';
const ossUrl = app.globalData.ossImageUrl || '';
const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
let avatarUrl = defaultAvatar;
if (touxiang) {
avatarUrl = ossUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
}
this.setData({ uid, avatarUrl });
},
async getKaoheguanInfo() {
this.setData({ isLoading: true });
try {
const res = await request({ url: '/dengji/khgsx', method: 'POST' });
if (res && res.data.code === 200) {
const data = res.data.data;
if (data.zhuangtai === 0 || data.zhuangtai === 2) {
wx.setStorageSync('kaoheguanstatus', 0);
this.setData({ isKaoheguan: false, isLoading: false, statusText: '用户尚未注册或账号已被封禁' });
return;
}
wx.setStorageSync('kaoheguanstatus', 1);
this.setData({
isKaoheguan: true,
isLoading: false,
kaoheData: {
shenheZongshu: data.shenhe_zongshu || 0,
tongguoZongshu: data.tongguo_zongshu || 0,
yue: data.yue || '0.00',
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 // 🆕
}
});
ensureRoleOnCenterPage(this, 'kaoheguan');
} else {
wx.showToast({ title: res?.data?.msg || '获取信息失败', icon: 'none' });
this.setData({ isLoading: false });
}
} catch (error) {
console.error('获取考核官信息失败:', error);
wx.showToast({ title: '网络错误', icon: 'none' });
this.setData({ isLoading: false });
}
},
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: '/dengji/khgzc',
method: 'POST',
data: { inviteCode: inviteCode }
});
if (res && res.data.code === 200) {
const data = res.data.data || {};
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);
}
});
// pages/assessor/assessor.js
import { createPage, request, isCenterPageActive, ensureRoleOnCenterPage } from '../../utils/base-page.js';
Page(createPage({
data: {
isKaoheguan: false,
inviteCode: '',
statusText: '用户尚未注册或账号已被封禁',
uid: '',
avatarUrl: '',
icons: {
refresh: '',
tixian: '',
daofen: '',
jilu: '',
zhongxin: '',
bgImage: ''
},
kaoheData: {
shenheZongshu: 0,
tongguoZongshu: 0,
yue: '0.00',
zonge: '0.00',
shenheZhuangtai: 0,
bankuaiList: [],
isRenzheng: false,
pendingShenheCount: 0
},
planetPaused: {
top: false,
bottom: false,
left: false,
right: false
}
},
_planetTimers: {},
onLoad() {
wx.redirectTo({ url: '/pages/fighter/fighter' });
return;
const icons = this.initImageUrls({
'icons.refresh': 'beijing/kaohe/shuaxin.png',
'icons.tixian': 'beijing/kaohe/tixian.png',
'icons.daofen': 'beijing/kaohe/daofen.png',
'icons.jilu': 'beijing/kaohe/jilu.png',
'icons.zhongxin': 'beijing/kaohe/zhongxin.png',
'icons.bgImage': 'beijing/kaohe/top_bg.jpg'
});
this.setData(icons);
this.checkRoleStatus();
if (this.data.isKaoheguan) {
this.getKaoheguanInfo();
}
this.checkColdStartPopup('kaoheguanduan');
},
onShow() {
if (isCenterPageActive(this, 'isKaoheguan', 'kaoheguanstatus')) {
if (!this.data.isKaoheguan) this.setData({ isKaoheguan: true });
ensureRoleOnCenterPage(this, 'dashou');
this.refreshKaoheInfo();
}
},
async getKaoheguanInfo() {
this.setData({ isLoading: true });
try {
const res = await request({ url: '/dengji/khgsx', method: 'POST' });
if (res && res.data.code === 200) {
const data = res.data.data;
if (data.zhuangtai === 0 || data.zhuangtai === 2) {
wx.setStorageSync('kaoheguanstatus', 0);
this.setData({ isKaoheguan: false, isLoading: false, statusText: '用户尚未注册或账号已被封禁' });
return;
}
wx.setStorageSync('kaoheguanstatus', 1);
this.setData({
isKaoheguan: true,
isLoading: false,
kaoheData: {
shenheZongshu: data.shenhe_zongshu || 0,
tongguoZongshu: data.tongguo_zongshu || 0,
yue: data.yue || '0.00',
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
}
});
ensureRoleOnCenterPage(this, 'dashou');
} else {
wx.showToast({ title: res?.data?.msg || '获取信息失败', icon: 'none' });
this.setData({ isLoading: false });
}
} catch (error) {
console.error('获取考核官信息失败:', error);
wx.showToast({ title: '网络错误', icon: 'none' });
this.setData({ isLoading: false });
}
},
onInviteCodeInput(e) {
this.setData({ inviteCode: e.detail.value.trim() });
},
async onRegister() {
this.registerWithInviteCode({
inviteCode: this.data.inviteCode,
apiUrl: '/dengji/khgzc',
role: 'dashou',
statusKey: 'kaoheguanstatus',
successMsg: '注册成功',
onSuccess: async () => {
this.loadUserInfo();
await this.getKaoheguanInfo();
ensureRoleOnCenterPage(this, 'dashou');
}
});
},
refreshKaoheInfo() {
this.throttledRefresh(this.getKaoheguanInfo);
},
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);
}
}, {
roleConfig: {
role: 'dashou',
statusKey: 'kaoheguanstatus',
dataFlag: 'isKaoheguan',
pageId: 'kaoheguanduan',
apiUrl: '/dengji/khgsx'
}
}));

View File

@@ -1,331 +1,321 @@
// pages/category/category.js
const app = getApp()
Page({
data: {
ossImageUrl: '',
searchText: '',
showSearchResult: false,
searchResults: [],
isSearching: false,
shangpinleixing: [],
shangpinzhuanqu: [],
shangpinliebiao: [],
selectedLeixingId: null,
selectedZhuanquId: null,
filteredZhuanquList: [],
filteredShangpinList: [],
zhuanquByLeixing: {},
shangpinByZhuanqu: {},
zhuanquGoodsCount: {},
isLoading: false,
hasInitialized: false
},
onLoad(options) {
this.setData({
ossImageUrl: app.globalData.ossImageUrl
})
this.initPageData()
},
onShow() {
this.registerNotificationComponent();
},
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()
};
}
},
initPageData() {
const that = this
this.setData({ isLoading: true })
if (app.globalData.shangpinleixing && app.globalData.shangpinleixing.length > 0) {
this.initFromGlobalData()
} else {
this.loadAllShangpinData()
}
},
initFromGlobalData() {
const leixingList = app.globalData.shangpinleixing || []
const zhuanquList = app.globalData.shangpinzhuanqu || []
const shangpinList = app.globalData.shangpinliebiao || []
const zhuanquByLeixing = {}
const shangpinByZhuanqu = {}
const zhuanquGoodsCount = {}
zhuanquList.forEach(zhuanqu => {
const leixingId = zhuanqu.leixing_id
if (!zhuanquByLeixing[leixingId]) {
zhuanquByLeixing[leixingId] = []
}
zhuanquByLeixing[leixingId].push(zhuanqu)
})
shangpinList.forEach(shangpin => {
const zhuanquId = shangpin.zhuanqu_id
if (zhuanquId) {
if (!shangpinByZhuanqu[zhuanquId]) {
shangpinByZhuanqu[zhuanquId] = []
zhuanquGoodsCount[zhuanquId] = 0
}
const jiage = parseFloat(shangpin.jiage) || 0
const priceInteger = Math.floor(jiage)
let priceDecimal = '00'
const decimalPart = (jiage - priceInteger).toFixed(2)
if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1]
}
shangpinByZhuanqu[zhuanquId].push({
id: shangpin.id,
biaoqian: shangpin.biaoqian,
jiage: jiage,
priceInteger: priceInteger,
priceDecimal: priceDecimal,
tupian_url: shangpin.tupian_url,
duiwai_xiaoliang: shangpin.duiwai_xiaoliang || 0
})
zhuanquGoodsCount[zhuanquId] += 1
}
})
const firstLeixing = leixingList[0]
let firstZhuanquId = null
if (firstLeixing && zhuanquByLeixing[firstLeixing.id]) {
firstZhuanquId = zhuanquByLeixing[firstLeixing.id][0]?.id || null
}
this.setData({
shangpinleixing: leixingList,
shangpinzhuanqu: zhuanquList,
shangpinliebiao: shangpinList,
zhuanquByLeixing: zhuanquByLeixing,
shangpinByZhuanqu: shangpinByZhuanqu,
zhuanquGoodsCount: zhuanquGoodsCount,
selectedLeixingId: firstLeixing?.id || null,
selectedZhuanquId: firstZhuanquId
}, () => {
this.filterData()
this.setData({ isLoading: false, hasInitialized: true })
})
},
loadAllShangpinData() {
const that = this
wx.request({
url: app.globalData.apiBaseUrl + '/shangpin/shangpinhuoqu/',
method: 'POST',
header: {
'content-type': 'application/json'
},
success(res) {
if (res.statusCode === 200 && res.data) {
const data = res.data
app.globalData.shangpinleixing = data.shangpinleixing || []
app.globalData.shangpinzhuanqu = data.shangpinzhuanqu || []
app.globalData.shangpinliebiao = data.shangpinliebiao || []
that.initFromGlobalData()
} else {
that.handleLoadError()
}
},
fail(error) {
console.error('加载商品数据失败:', error)
that.handleLoadError()
}
})
},
handleLoadError() {
wx.showToast({
title: '加载失败,请重试',
icon: 'none'
})
this.setData({ isLoading: false })
},
filterData() {
const { selectedLeixingId, selectedZhuanquId, zhuanquByLeixing, shangpinByZhuanqu } = this.data
const filteredZhuanquList = zhuanquByLeixing[selectedLeixingId] || []
let filteredShangpinList = []
if (selectedZhuanquId) {
filteredShangpinList = shangpinByZhuanqu[selectedZhuanquId] || []
} else if (filteredZhuanquList.length > 0) {
const firstZhuanquId = filteredZhuanquList[0].id
filteredShangpinList = shangpinByZhuanqu[firstZhuanquId] || []
this.setData({ selectedZhuanquId: firstZhuanquId })
}
this.setData({
filteredZhuanquList: filteredZhuanquList,
filteredShangpinList: filteredShangpinList
})
},
selectLeixing(e) {
const leixingId = e.currentTarget.dataset.id
if (this.data.selectedLeixingId === leixingId) {
return
}
const zhuanquList = this.data.zhuanquByLeixing[leixingId] || []
const firstZhuanquId = zhuanquList.length > 0 ? zhuanquList[0].id : null
this.setData({
selectedLeixingId: leixingId,
selectedZhuanquId: firstZhuanquId
}, () => {
this.filterData()
})
},
selectZhuanqu(e) {
const zhuanquId = e.currentTarget.dataset.id
if (this.data.selectedZhuanquId === zhuanquId) {
return
}
this.setData({
selectedZhuanquId: zhuanquId
}, () => {
const filteredShangpinList = this.data.shangpinByZhuanqu[zhuanquId] || []
this.setData({
filteredShangpinList: filteredShangpinList
})
})
},
onSearchInput(e) {
this.setData({
searchText: e.detail.value
})
},
onSearchTap() {
const searchText = this.data.searchText.trim()
if (!searchText) {
wx.showToast({
title: '请输入搜索内容',
icon: 'none'
})
return
}
this.startSearch(searchText)
},
onSearchConfirm(e) {
const searchText = e.detail.value.trim()
if (!searchText) {
wx.showToast({
title: '请输入搜索内容',
icon: 'none'
})
return
}
this.setData({
searchText: searchText
})
this.startSearch(searchText)
},
startSearch(searchText) {
this.setData({
isSearching: true,
showSearchResult: true
})
const allShangpin = this.data.shangpinliebiao
const results = []
for (const item of allShangpin) {
if (item.biaoqian && item.biaoqian.toLowerCase().includes(searchText.toLowerCase())) {
const jiage = parseFloat(item.jiage) || 0
const priceInteger = Math.floor(jiage)
let priceDecimal = '00'
const decimalPart = (jiage - priceInteger).toFixed(2)
if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1]
}
results.push({
id: item.id,
biaoqian: item.biaoqian,
jiage: jiage,
priceInteger: priceInteger,
priceDecimal: priceDecimal,
tupian_url: item.tupian_url,
leixing_name: this.getLeixingName(item.leixing_id),
zhuanqu_name: this.getZhuanquName(item.zhuanqu_id)
})
}
}
this.setData({
searchResults: results,
isSearching: false
})
},
onClearSearch() {
this.setData({
searchText: '',
showSearchResult: false,
searchResults: []
})
},
getLeixingName(leixingId) {
const leixingList = this.data.shangpinleixing
for (const item of leixingList) {
if (item.id === leixingId) {
return item.jieshao
}
}
return ''
},
getZhuanquName(zhuanquId) {
const zhuanquList = this.data.shangpinzhuanqu
for (const item of zhuanquList) {
if (item.id === zhuanquId) {
return item.mingzi
}
}
return ''
},
goToDetail(e) {
const shangpinId = e.currentTarget.dataset.id
if (!shangpinId) return
wx.navigateTo({
url: `/pages/product-detail/product-detail?id=${shangpinId}`
})
},
onRefresh() {
wx.showLoading({
title: '刷新中...',
mask: true
})
app.globalData.shangpinleixing = []
app.globalData.shangpinzhuanqu = []
app.globalData.shangpinliebiao = []
this.loadAllShangpinData()
setTimeout(() => {
wx.hideLoading()
wx.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500
})
}, 1000)
},
onShareAppMessage() {
return {
title: '阿龙电竞 - 商品分类',
path: '/pages/category/category'
}
}
})
// pages/category/category.js
const app = getApp()
import { createPage } from '../../utils/base-page.js';
Page(createPage({
data: {
ossImageUrl: '',
searchText: '',
showSearchResult: false,
searchResults: [],
isSearching: false,
shangpinleixing: [],
shangpinzhuanqu: [],
shangpinliebiao: [],
selectedLeixingId: null,
selectedZhuanquId: null,
filteredZhuanquList: [],
filteredShangpinList: [],
zhuanquByLeixing: {},
shangpinByZhuanqu: {},
zhuanquGoodsCount: {},
isLoading: false,
hasInitialized: false
},
onLoad(options) {
this.setData({
ossImageUrl: app.globalData.ossImageUrl
})
this.initPageData()
},
onShow() {
this.registerNotificationComponent();
},
initPageData() {
const that = this
this.setData({ isLoading: true })
if (app.globalData.shangpinleixing && app.globalData.shangpinleixing.length > 0) {
this.initFromGlobalData()
} else {
this.loadAllShangpinData()
}
},
initFromGlobalData() {
const leixingList = app.globalData.shangpinleixing || []
const zhuanquList = app.globalData.shangpinzhuanqu || []
const shangpinList = app.globalData.shangpinliebiao || []
const zhuanquByLeixing = {}
const shangpinByZhuanqu = {}
const zhuanquGoodsCount = {}
zhuanquList.forEach(zhuanqu => {
const leixingId = zhuanqu.leixing_id
if (!zhuanquByLeixing[leixingId]) {
zhuanquByLeixing[leixingId] = []
}
zhuanquByLeixing[leixingId].push(zhuanqu)
})
shangpinList.forEach(shangpin => {
const zhuanquId = shangpin.zhuanqu_id
if (zhuanquId) {
if (!shangpinByZhuanqu[zhuanquId]) {
shangpinByZhuanqu[zhuanquId] = []
zhuanquGoodsCount[zhuanquId] = 0
}
const jiage = parseFloat(shangpin.jiage) || 0
const priceInteger = Math.floor(jiage)
let priceDecimal = '00'
const decimalPart = (jiage - priceInteger).toFixed(2)
if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1]
}
shangpinByZhuanqu[zhuanquId].push({
id: shangpin.id,
biaoqian: shangpin.biaoqian,
jiage: jiage,
priceInteger: priceInteger,
priceDecimal: priceDecimal,
tupian_url: shangpin.tupian_url,
duiwai_xiaoliang: shangpin.duiwai_xiaoliang || 0
})
zhuanquGoodsCount[zhuanquId] += 1
}
})
const firstLeixing = leixingList[0]
let firstZhuanquId = null
if (firstLeixing && zhuanquByLeixing[firstLeixing.id]) {
firstZhuanquId = zhuanquByLeixing[firstLeixing.id][0]?.id || null
}
this.setData({
shangpinleixing: leixingList,
shangpinzhuanqu: zhuanquList,
shangpinliebiao: shangpinList,
zhuanquByLeixing: zhuanquByLeixing,
shangpinByZhuanqu: shangpinByZhuanqu,
zhuanquGoodsCount: zhuanquGoodsCount,
selectedLeixingId: firstLeixing?.id || null,
selectedZhuanquId: firstZhuanquId
}, () => {
this.filterData()
this.setData({ isLoading: false, hasInitialized: true })
})
},
loadAllShangpinData() {
const that = this
wx.request({
url: app.globalData.apiBaseUrl + '/shangpin/shangpinhuoqu/',
method: 'POST',
header: {
'content-type': 'application/json'
},
success(res) {
if (res.statusCode === 200 && res.data) {
const data = res.data
app.globalData.shangpinleixing = data.shangpinleixing || []
app.globalData.shangpinzhuanqu = data.shangpinzhuanqu || []
app.globalData.shangpinliebiao = data.shangpinliebiao || []
that.initFromGlobalData()
} else {
that.handleLoadError()
}
},
fail(error) {
console.error('加载商品数据失败:', error)
that.handleLoadError()
}
})
},
handleLoadError() {
wx.showToast({
title: '加载失败,请重试',
icon: 'none'
})
this.setData({ isLoading: false })
},
filterData() {
const { selectedLeixingId, selectedZhuanquId, zhuanquByLeixing, shangpinByZhuanqu } = this.data
const filteredZhuanquList = zhuanquByLeixing[selectedLeixingId] || []
let filteredShangpinList = []
if (selectedZhuanquId) {
filteredShangpinList = shangpinByZhuanqu[selectedZhuanquId] || []
} else if (filteredZhuanquList.length > 0) {
const firstZhuanquId = filteredZhuanquList[0].id
filteredShangpinList = shangpinByZhuanqu[firstZhuanquId] || []
this.setData({ selectedZhuanquId: firstZhuanquId })
}
this.setData({
filteredZhuanquList: filteredZhuanquList,
filteredShangpinList: filteredShangpinList
})
},
selectLeixing(e) {
const leixingId = e.currentTarget.dataset.id
if (this.data.selectedLeixingId === leixingId) {
return
}
const zhuanquList = this.data.zhuanquByLeixing[leixingId] || []
const firstZhuanquId = zhuanquList.length > 0 ? zhuanquList[0].id : null
this.setData({
selectedLeixingId: leixingId,
selectedZhuanquId: firstZhuanquId
}, () => {
this.filterData()
})
},
selectZhuanqu(e) {
const zhuanquId = e.currentTarget.dataset.id
if (this.data.selectedZhuanquId === zhuanquId) {
return
}
this.setData({
selectedZhuanquId: zhuanquId
}, () => {
const filteredShangpinList = this.data.shangpinByZhuanqu[zhuanquId] || []
this.setData({
filteredShangpinList: filteredShangpinList
})
})
},
onSearchInput(e) {
this.setData({
searchText: e.detail.value
})
},
onSearchTap() {
const searchText = this.data.searchText.trim()
if (!searchText) {
wx.showToast({
title: '请输入搜索内容',
icon: 'none'
})
return
}
this.startSearch(searchText)
},
onSearchConfirm(e) {
const searchText = e.detail.value.trim()
if (!searchText) {
wx.showToast({
title: '请输入搜索内容',
icon: 'none'
})
return
}
this.setData({
searchText: searchText
})
this.startSearch(searchText)
},
startSearch(searchText) {
this.setData({
isSearching: true,
showSearchResult: true
})
const allShangpin = this.data.shangpinliebiao
const results = []
for (const item of allShangpin) {
if (item.biaoqian && item.biaoqian.toLowerCase().includes(searchText.toLowerCase())) {
const jiage = parseFloat(item.jiage) || 0
const priceInteger = Math.floor(jiage)
let priceDecimal = '00'
const decimalPart = (jiage - priceInteger).toFixed(2)
if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1]
}
results.push({
id: item.id,
biaoqian: item.biaoqian,
jiage: jiage,
priceInteger: priceInteger,
priceDecimal: priceDecimal,
tupian_url: item.tupian_url,
leixing_name: this.getLeixingName(item.leixing_id),
zhuanqu_name: this.getZhuanquName(item.zhuanqu_id)
})
}
}
this.setData({
searchResults: results,
isSearching: false
})
},
onClearSearch() {
this.setData({
searchText: '',
showSearchResult: false,
searchResults: []
})
},
getLeixingName(leixingId) {
const leixingList = this.data.shangpinleixing
for (const item of leixingList) {
if (item.id === leixingId) {
return item.jieshao
}
}
return ''
},
getZhuanquName(zhuanquId) {
const zhuanquList = this.data.shangpinzhuanqu
for (const item of zhuanquList) {
if (item.id === zhuanquId) {
return item.mingzi
}
}
return ''
},
goToDetail(e) {
const shangpinId = e.currentTarget.dataset.id
if (!shangpinId) return
wx.navigateTo({
url: `/pages/product-detail/product-detail?id=${shangpinId}`
})
},
onRefresh() {
wx.showLoading({
title: '刷新中...',
mask: true
})
app.globalData.shangpinleixing = []
app.globalData.shangpinzhuanqu = []
app.globalData.shangpinliebiao = []
this.loadAllShangpinData()
setTimeout(() => {
wx.hideLoading()
wx.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500
})
}, 1000)
},
onShareAppMessage() {
return {
title: '阿龙电竞 - 商品分类',
path: '/pages/category/category'
}
}
}))

View File

@@ -1,31 +1,31 @@
<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}}"
refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
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">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}">
<image wx:if="{{item.senderId !== currentUser.id}}" class="avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" />
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}"
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" />
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="order-bubble">
<text class="order-label">[订单]</text>
<text class="order-info">ID: {{item.payload.orderId}}</text>
<text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble">
<text class="chat-order-label">[订单]</text>
<text class="chat-order-info">ID: {{item.payload.orderId}}</text>
</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 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 ? '已读' : '未读'}}
</view>
</view>
@@ -33,53 +33,53 @@
<view id="msg-bottom" style="height:20rpx;"></view>
</scroll-view>
<view class="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="plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text>
<text class="plus-label">表情</text>
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="chat-plus-grid">
<view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="chat-plus-icon">😀</text>
<text class="chat-plus-label">表情</text>
</view>
<view class="plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text>
<text class="plus-label">图片</text>
<view class="chat-plus-item" catchtap="chooseImage">
<text class="chat-plus-icon">📷</text>
<text class="chat-plus-label">图片</text>
</view>
<view class="plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text>
<text class="plus-label">订单</text>
<view class="chat-plus-item" catchtap="openOrderSender">
<text class="chat-plus-icon">📋</text>
<text class="chat-plus-label">订单</text>
</view>
</view>
</view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll">
<view class="emoji-grid">
<view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="chat-emoji-grid">
<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>
</view>
</scroll-view>
</view>
<view class="pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view>
<view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view>
<view class="write-row">
<input class="text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single"></text>
<view class="chat-write-row">
<input class="chat-text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="chat-plus-icon-single"></text>
</view>
</view>
</view>
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button>
<view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="chat-detail-content" catchtap="noop">
<text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view>
</view>

View File

@@ -1,98 +1 @@
.chat-page { width:100%; height:100vh; background:#f5f5f5; }
.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; }
/* 使用全局 chat-* 样式 */

View File

@@ -1,37 +1,37 @@
<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}}"
refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
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">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}">
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<!-- 对方头像(客服) -->
<image wx:if="{{item.senderId !== currentUser.id}}" class="avatar" src="{{teamAvatar}}" mode="aspectFill" />
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}"
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{teamAvatar}}" mode="aspectFill" />
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
data-messageid="{{item.messageId}}"
data-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="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"/>
<view wx:elif="{{item.type === 'order'}}" class="order-bubble">
<text class="order-label">📋 订单消息</text>
<view class="order-brief">
<text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<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="chat-order-bubble">
<text class="chat-order-label">📋 订单消息</text>
<view class="chat-order-info">
<text>订单号:{{item.payload.dingdan_id || '无'}}</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>
<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>
<text wx:else class="msg-text">{{item.payload.text || '[未知消息]'}}</text>
<text wx:else class="chat-msg-text">{{item.payload.text || '[未知消息]'}}</text>
</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>
@@ -40,70 +40,70 @@
</scroll-view>
<!-- 底部输入区域(保持不变) -->
<view class="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="plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text>
<text class="plus-label">表情</text>
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="chat-plus-grid">
<view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="chat-plus-icon">😀</text>
<text class="chat-plus-label">表情</text>
</view>
<view class="plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text>
<text class="plus-label">图片</text>
<view class="chat-plus-item" catchtap="chooseImage">
<text class="chat-plus-icon">📷</text>
<text class="chat-plus-label">图片</text>
</view>
<view class="plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text>
<text class="plus-label">订单</text>
<view class="chat-plus-item" catchtap="openOrderSender">
<text class="chat-plus-icon">📋</text>
<text class="chat-plus-label">订单</text>
</view>
</view>
</view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll">
<view class="emoji-grid">
<view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="chat-emoji-grid">
<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>
</view>
</scroll-view>
</view>
<view class="pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view>
<view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view>
<view class="write-row">
<input class="text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single"></text>
<view class="chat-write-row">
<input class="chat-text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="chat-plus-icon-single"></text>
</view>
</view>
</view>
<!-- 文本双击放大弹窗 -->
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button>
<view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="chat-detail-content" catchtap="noop">
<text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view>
</view>
<!-- 订单详情弹窗 -->
<view class="order-detail-mask" wx:if="{{showOrderDetail}}" catchtap="hideOrderDetail">
<view class="order-detail-panel" catchtap="noop">
<view class="order-detail-title">订单详情</view>
<view class="order-detail-item">订单号:{{detailOrder.dingdan_id}}</view>
<view class="order-detail-item">服务描述:{{detailOrder.jieshao}}</view>
<view class="order-detail-item">金额:¥{{detailOrder.jine}}</view>
<view class="order-detail-item">类型:{{detailOrder.leixing || '无'}}</view>
<view class="order-detail-item">时间:{{detailOrder.shijian || '无'}}</view>
<button class="order-detail-close-btn" catchtap="hideOrderDetail">关闭</button>
<view class="chat-detail-modal" wx:if="{{showOrderDetail}}" catchtap="hideOrderDetail">
<view class="chat-detail-content" catchtap="noop">
<view class="chat-detail-text">订单详情</view>
<view class="chat-detail-text">订单号:{{detailOrder.dingdan_id}}</view>
<view class="chat-detail-text">服务描述:{{detailOrder.jieshao}}</view>
<view class="chat-detail-text">金额:¥{{detailOrder.jine}}</view>
<view class="chat-detail-text">类型:{{detailOrder.leixing || '无'}}</view>
<view class="chat-detail-text">时间:{{detailOrder.shijian || '无'}}</view>
<button class="chat-copy-btn" catchtap="hideOrderDetail">关闭</button>
</view>
</view>
<!-- 订单发送组件 -->
<order-sender visible="{{showOrderSender}}" bind:send="onSendOrder" bind:close="closeOrderSender" />
</view>
</view>

View File

@@ -1,67 +1 @@
.chat-page { width:100%; height:100vh; background:#f5f5f5; }
.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; }
/* 使用全局 chat-* 样式 */

View File

@@ -215,14 +215,14 @@
<!-- 弹窗底部按钮 -->
<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>
</view>
<!-- 申诉按钮(只有待处罚状态可点击) -->
<view
class="btn {{ xuanzhongChufa.sqzhuangtai === 0 ? 'btn-shensu' : 'btn-shensu-disabled' }}"
<view
class="cbtn {{ xuanzhongChufa.sqzhuangtai === 0 ? 'cbtn-shensu' : 'cbtn-shensu-disabled' }}"
bindtap="{{ xuanzhongChufa.sqzhuangtai === 0 ? 'openShensuModal' : '' }}"
>
<text>申诉</text>
@@ -299,11 +299,11 @@
</view>
</view>
<view class="modal-footer">
<view class="btn-group">
<view class="btn btn-quxiao" bindtap="closeShensuModal">
<view class="cbtn-group">
<view class="cbtn cbtn-quxiao" bindtap="closeShensuModal">
<text>取消</text>
</view>
<view class="btn btn-queren" bindtap="submitShensu">
<view class="cbtn cbtn-queren" bindtap="submitShensu">
<text>提交申诉</text>
</view>
</view>

View File

@@ -625,12 +625,12 @@
flex-shrink: 0;
}
.btn-group {
.cbtn-group {
display: flex;
gap: 20rpx;
}
.btn {
.cbtn {
flex: 1;
height: 90rpx;
display: flex;
@@ -645,37 +645,37 @@
color: var(--cyber-text) !important;
}
.btn:active {
.cbtn:active {
transform: scale(0.98);
}
/* 按钮样式 */
.btn-kefu {
.cbtn-kefu {
background: rgba(0, 243, 255, 0.1);
border-color: var(--cyber-blue);
box-shadow: 0 0 12rpx rgba(0, 243, 255, 0.2);
}
.btn-shensu {
.cbtn-shensu {
background: rgba(255, 165, 0, 0.1);
border-color: #FFA500;
box-shadow: 0 0 12rpx rgba(255, 165, 0, 0.2);
}
.btn-shensu-disabled {
.cbtn-shensu-disabled {
background: rgba(102, 102, 102, 0.1);
border-color: #666;
box-shadow: 0 0 12rpx rgba(102, 102, 102, 0.2);
opacity: 0.5;
}
.btn-quxiao {
.cbtn-quxiao {
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 12rpx rgba(255, 255, 255, 0.1);
}
.btn-queren {
.cbtn-queren {
background: rgba(0, 255, 157, 0.1);
border-color: var(--cyber-green);
box-shadow: 0 0 12rpx rgba(0, 255, 157, 0.2);

View File

@@ -1,307 +1,289 @@
const app = getApp();
import request from '../../utils/request.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
Page({
data: {
// 商品类型
shangpinleixing: [],
xuanzhongLeixingId: null,
// 订单类型: normal 普通, peihu 陪护
orderType: 'normal',
// 搜索关键字
searchKeyword: '',
// 状态列表(增加全部)
statusList: [
{ name: '全部', key: 'all', zhuangtaiList: [], color: '#333333' },
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
{ name: '退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' },
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' }
],
currentStatusKey: 'all',
// 每个状态独立的数据集
dsDingdanShuju: {
all: { list: [], page: 1, hasMore: true, isLoading: false },
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
},
currentList: [],
hasMore: true,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false,
defaultImg: '/images/default-order.png',
ossImageUrl: app.globalData.ossImageUrl || '',
},
onLoad() {
wx.setNavigationBarTitle({ title: '我的接单' });
this.loadShangpinLeixing();
this.registerNotificationComponent();
},
onShow() {
this.registerNotificationComponent();
if (wx.getStorageSync('uid')) {
reconnectForRole('dashou');
if (app.startImWhenReady) app.startImWhenReady();
}
if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) {
this.loadCurrentStatusOrders(true);
}
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
// 加载商品类型(图片拼接)
async loadShangpinLeixing() {
try {
const res = await request({
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 || [];
if (!Array.isArray(list)) list = [];
const oss = this.data.ossImageUrl;
const processed = list.map(item => ({
...item,
full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http')
? oss + item.tupian_url
: (item.tupian_url || '/images/default-type.png')
}));
this.setData({
shangpinleixing: processed,
xuanzhongLeixingId: processed[0]?.id || null
});
this.loadCurrentStatusOrders(true);
}
} catch (e) {
console.error('加载商品类型失败', e);
}
},
selectLeixing(e) {
const id = e.currentTarget.dataset.id;
if (id === this.data.xuanzhongLeixingId) return;
this.setData({ xuanzhongLeixingId: id });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
switchOrderType(e) {
const type = e.currentTarget.dataset.type;
if (type === this.data.orderType) return;
this.setData({ orderType: type });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value });
if (this._searchTimer) clearTimeout(this._searchTimer);
this._searchTimer = setTimeout(() => {
this.onSearchConfirm();
}, 500);
},
onSearchConfirm() {
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
clearSearch() {
this.setData({ searchKeyword: '' });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
switchStatus(e) {
const key = e.currentTarget.dataset.key;
if (key === this.data.currentStatusKey) return;
this.setData({ currentStatusKey: key });
const tabData = this.data.dsDingdanShuju[key];
if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) {
this.loadCurrentStatusOrders(true);
} else {
this.refreshCurrentListView();
}
},
// 加载订单(核心)
async loadCurrentStatusOrders(isRefresh = false) {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading) return;
const page = isRefresh ? 1 : tabData.page;
this.setData({
[`dsDingdanShuju.${key}.isLoading`]: true,
isLoading: true,
isLoadingMore: !isRefresh
});
try {
const apiUrl = this.data.orderType === 'peihu'
? '/dingdan/phddlbhq'
: '/dingdan/dshqdingdan';
const statusItem = this.data.statusList.find(s => s.key === key);
const zhuangtaiList = statusItem.zhuangtaiList.length > 0 ? statusItem.zhuangtaiList : undefined;
const params = {
zhuangtai_list: zhuangtaiList,
page: page,
page_size: 5,
leixing_id: this.data.xuanzhongLeixingId,
keyword: this.data.searchKeyword || undefined
};
const res = await request({
url: apiUrl,
method: 'POST',
data: params,
header: { 'content-type': 'application/json' }
});
const code = res.data.code;
if (code === 200 || code === 0) {
const newList = res.data.data.list || [];
const hasMore = res.data.data.has_more || false;
// ✅ 图片URL拼接核心
const processed = newList.map(item => ({
...item,
zhuangtaiZh: this.getZhuangtaiZh(item.zhuangtai),
zhuangtaiColor: statusItem.color,
tupian: item.tupian
? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian)
: this.data.defaultImg
}));
const currentList = this.data.dsDingdanShuju[key].list;
const updatedList = isRefresh ? processed : [...currentList, ...processed];
this.setData({
[`dsDingdanShuju.${key}.list`]: updatedList,
[`dsDingdanShuju.${key}.page`]: page,
[`dsDingdanShuju.${key}.hasMore`]: hasMore,
[`dsDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false
});
this.refreshCurrentListView();
} else {
throw new Error(res.data.msg || '加载失败');
}
} catch (err) {
console.error('加载订单失败', err);
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
this.setData({
[`dsDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false
});
this.refreshCurrentListView();
}
},
refreshCurrentListView() {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
this.setData({
currentList: tabData.list,
hasMore: tabData.hasMore,
isLoading: tabData.isLoading
});
},
resetCurrentStatusData() {
const key = this.data.currentStatusKey;
this.setData({
[`dsDingdanShuju.${key}.list`]: [],
[`dsDingdanShuju.${key}.page`]: 1,
[`dsDingdanShuju.${key}.hasMore`]: true,
[`dsDingdanShuju.${key}.isLoading`]: false
});
this.refreshCurrentListView();
},
// 下拉刷新(由 scroll-view 触发)
onPullDownRefresh() {
if (this.data.isLoading) {
this.setData({ scrollViewRefreshing: false });
return;
}
this.setData({ scrollViewRefreshing: true });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 上拉加载更多(由 scroll-view 触发)
onReachBottom() {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false);
},
// ✅ 手动点击“加载更多”按钮
onLoadMoreTap() {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false);
},
getZhuangtaiZh(zhuangtai) {
const map = {
1: '已下单', 2: '进行中', 3: '已完成',
4: '退款中', 5: '已退款', 6: '退款失败',
7: '指定中', 8: '结算中'
};
return map[zhuangtai] || '未知状态';
},
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() {}
});
const app = getApp();
import { createPage, request } from '../../utils/base-page.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getOrderStatusText } from '../../utils/api-helper.js';
Page(createPage({
data: {
// 商品类型
shangpinleixing: [],
xuanzhongLeixingId: null,
// 订单类型: normal 普通, peihu 陪护
orderType: 'normal',
// 搜索关键字
searchKeyword: '',
// 状态列表(增加全部)
statusList: [
{ name: '全部', key: 'all', zhuangtaiList: [], color: '#333333' },
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
{ name: '退款', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
{ name: '退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' },
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' }
],
currentStatusKey: 'all',
// 每个状态独立的数据集
dsDingdanShuju: {
all: { list: [], page: 1, hasMore: true, isLoading: false },
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
},
currentList: [],
hasMore: true,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false,
defaultImg: '/images/default-order.png',
ossImageUrl: app.globalData.ossImageUrl || '',
},
onLoad() {
wx.setNavigationBarTitle({ title: '我的接单' });
this.loadShangpinLeixing();
this.registerNotificationComponent();
},
onShow() {
this.registerNotificationComponent();
if (wx.getStorageSync('uid')) {
reconnectForRole('dashou');
if (app.startImWhenReady) app.startImWhenReady();
}
if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) {
this.loadCurrentStatusOrders(true);
}
},
// 加载商品类型(图片拼接)
async loadShangpinLeixing() {
try {
const res = await request({
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 || [];
if (!Array.isArray(list)) list = [];
const oss = this.data.ossImageUrl;
const processed = list.map(item => ({
...item,
full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http')
? oss + item.tupian_url
: (item.tupian_url || '/images/default-type.png')
}));
this.setData({
shangpinleixing: processed,
xuanzhongLeixingId: processed[0]?.id || null
});
this.loadCurrentStatusOrders(true);
}
} catch (e) {
console.error('加载商品类型失败', e);
}
},
selectLeixing(e) {
const id = e.currentTarget.dataset.id;
if (id === this.data.xuanzhongLeixingId) return;
this.setData({ xuanzhongLeixingId: id });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
switchOrderType(e) {
const type = e.currentTarget.dataset.type;
if (type === this.data.orderType) return;
this.setData({ orderType: type });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value });
if (this._searchTimer) clearTimeout(this._searchTimer);
this._searchTimer = setTimeout(() => {
this.onSearchConfirm();
}, 500);
},
onSearchConfirm() {
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
clearSearch() {
this.setData({ searchKeyword: '' });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
switchStatus(e) {
const key = e.currentTarget.dataset.key;
if (key === this.data.currentStatusKey) return;
this.setData({ currentStatusKey: key });
const tabData = this.data.dsDingdanShuju[key];
if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) {
this.loadCurrentStatusOrders(true);
} else {
this.refreshCurrentListView();
}
},
// 加载订单(核心)
async loadCurrentStatusOrders(isRefresh = false) {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading) return;
const page = isRefresh ? 1 : tabData.page;
this.setData({
[`dsDingdanShuju.${key}.isLoading`]: true,
isLoading: true,
isLoadingMore: !isRefresh
});
try {
const apiUrl = this.data.orderType === 'peihu'
? '/dingdan/phddlbhq'
: '/dingdan/dshqdingdan';
const statusItem = this.data.statusList.find(s => s.key === key);
const zhuangtaiList = statusItem.zhuangtaiList.length > 0 ? statusItem.zhuangtaiList : undefined;
const params = {
zhuangtai_list: zhuangtaiList,
page: page,
page_size: 5,
leixing_id: this.data.xuanzhongLeixingId,
keyword: this.data.searchKeyword || undefined
};
const res = await request({
url: apiUrl,
method: 'POST',
data: params,
header: { 'content-type': 'application/json' }
});
const code = res.data.code;
if (code === 200 || code === 0) {
const newList = res.data.data.list || [];
const hasMore = res.data.data.has_more || false;
// ✅ 图片URL拼接核心
const processed = newList.map(item => ({
...item,
zhuangtaiZh: getOrderStatusText(item.zhuangtai),
zhuangtaiColor: statusItem.color,
tupian: item.tupian
? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian)
: this.data.defaultImg
}));
const currentList = this.data.dsDingdanShuju[key].list;
const updatedList = isRefresh ? processed : [...currentList, ...processed];
this.setData({
[`dsDingdanShuju.${key}.list`]: updatedList,
[`dsDingdanShuju.${key}.page`]: page,
[`dsDingdanShuju.${key}.hasMore`]: hasMore,
[`dsDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false
});
this.refreshCurrentListView();
} else {
throw new Error(res.data.msg || '加载失败');
}
} catch (err) {
console.error('加载订单失败', err);
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
this.setData({
[`dsDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false
});
this.refreshCurrentListView();
}
},
refreshCurrentListView() {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
this.setData({
currentList: tabData.list,
hasMore: tabData.hasMore,
isLoading: tabData.isLoading
});
},
resetCurrentStatusData() {
const key = this.data.currentStatusKey;
this.setData({
[`dsDingdanShuju.${key}.list`]: [],
[`dsDingdanShuju.${key}.page`]: 1,
[`dsDingdanShuju.${key}.hasMore`]: true,
[`dsDingdanShuju.${key}.isLoading`]: false
});
this.refreshCurrentListView();
},
// 下拉刷新(由 scroll-view 触发)
onPullDownRefresh() {
if (this.data.isLoading) {
this.setData({ scrollViewRefreshing: false });
return;
}
this.setData({ scrollViewRefreshing: true });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 上拉加载更多(由 scroll-view 触发)
onReachBottom() {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false);
},
// ✅ 手动点击"加载更多"按钮
onLoadMoreTap() {
const key = this.data.currentStatusKey;
const tabData = this.data.dsDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false);
},
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() {}
}))

View File

@@ -503,7 +503,7 @@
<!-- ======================= -->
<view class="tech-footer">
<view class="footer-line"></view>
<view class="footer-text">星阙网络 © 7891 科技</view>
<view class="footer-text">星阙网络 © 科技</view>
</view>
</view>

View File

@@ -1,26 +1,15 @@
// pages/dashouzhongxin/dashouzhongxin.js
import request from '../../utils/request.js'
import popupService from '../../services/popupService.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 { createPage, request, isRoleStatusActive, isCenterPageActive, syncRoleStatuses, syncProfileFields, syncGroupFields, ensureRoleOnCenterPage, clearCacheAndEnterNormal, lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole, parseSceneOptions } from '../../utils/base-page.js'
import { resolveAvatarUrl } from '../../utils/avatar.js'
import { openPrivateChat, buildInviterPeerId } from '../../utils/im-user.js'
const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC'
Page({
Page(createPage({
data: {
// 图片路径对象
imgUrls: {},
isDashou: false,
isGuanshi: false,
isZuzhang: false,
@@ -31,7 +20,6 @@ Page({
assetBreakdown: [],
pendingAuthList: [],
showAuthSection: false,
isLoading: false,
inviteCode: '',
guanshiInviteCode: '',
zuzhangInviteCode: '',
@@ -48,8 +36,6 @@ Page({
clumber: [],
chengjiaoliang: '',
zaixianZhuangtai: 0,
lastRefreshTime: 0,
canRefresh: true,
isAutoRegistering: false,
inviterCache: null,
zuzhangInviterCache: null,
@@ -165,17 +151,10 @@ Page({
});
this.checkRoleStatuses();
const registerType = options.registerType || 'dashou';
if (options.scene) {
try {
const scene = decodeURIComponent(options.scene);
options.inviteCode = scene;
} catch (e) {
console.error('scene解码失败', e);
}
}
const parsed = parseSceneOptions(options);
const registerType = parsed.registerType || 'dashou';
const { inviteCode } = parsed;
this.checkDashouStatus();
const { inviteCode } = options || {};
if (inviteCode) {
if (registerType === 'guanshi') {
this.setData({ guanshiInviteCode: inviteCode });
@@ -189,26 +168,7 @@ Page({
}, 500);
}
}
// 注意:不再重复声明 app
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();
}
this.checkColdStartPopup('dashouduan');
},
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) {
const now = Date.now();
if (!this.data.canRefresh || (now - this.data.lastRefreshTime < 3000)) return;
this.setData({ lastRefreshTime: now, canRefresh: false, isLoading: true });
try {
this.checkRoleStatuses();
const results = await Promise.allSettled([
this._fetchDashouInfoSilent(),
this.fetchChenghaoList(),
this._fetchGuanshiInfoSilent(),
this.fetchGuanshiChenghaoList(),
this._fetchZuzhangInfoSilent(),
this._fetchKaoheguanInfoSilent(),
]);
this.checkRoleStatuses();
const dashouOk = results[0].status === 'fulfilled';
if (showToast) {
wx.showToast({
title: dashouOk ? '刷新成功' : '刷新失败',
icon: dashouOk ? 'success' : 'none',
duration: 1500,
});
this.throttledRefresh(async () => {
this.setData({ isLoading: true });
try {
this.checkRoleStatuses();
const results = await Promise.allSettled([
this._fetchDashouInfoSilent(),
this.fetchChenghaoList(),
this._fetchGuanshiInfoSilent(),
this.fetchGuanshiChenghaoList(),
this._fetchZuzhangInfoSilent(),
this._fetchKaoheguanInfoSilent(),
]);
this.checkRoleStatuses();
const dashouOk = results[0].status === 'fulfilled';
if (showToast) {
wx.showToast({
title: dashouOk ? '刷新成功' : '刷新失败',
icon: dashouOk ? 'success' : 'none',
duration: 1500,
});
}
} catch (e) {
if (showToast) {
wx.showToast({ title: '刷新失败', icon: 'none' });
}
} finally {
this.setData({ isLoading: false });
}
} catch (e) {
if (showToast) {
wx.showToast({ title: '刷新失败', icon: 'none' });
}
} finally {
this.setData({ isLoading: false });
setTimeout(() => this.setData({ canRefresh: true }), 3000);
}
}, 3000);
},
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() {
try {
const dashoustatus = wx.getStorageSync('dashoustatus');
@@ -905,11 +830,6 @@ Page({
}
},
loadAvatar() {
const avatarUrl = resolveAvatarFromStorage(getApp());
this.setData({ avatarUrl });
},
checkGlobalData() {
const app = getApp();
const globalData = app.globalData || {};
@@ -1058,19 +978,6 @@ Page({
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() {
const { avatarUrl } = this.data;
if (avatarUrl) wx.previewImage({ urls: [avatarUrl] });
@@ -1079,19 +986,6 @@ Page({
goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }) },
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) {
const type = e.currentTarget.dataset.type;
if (!type || !['dashou', 'shangjia', 'guanshi', 'zuzhang'].includes(type)) return;
@@ -1171,20 +1065,4 @@ Page({
});
},
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: (r) => {
if (r.confirm) clearCacheAndEnterNormal(this);
},
});
},
switchToNormal() {
lockPrimaryRole('normal');
wx.reLaunch({ url: '/pages/mine/mine' });
},
})
}, { roleConfig: { role: 'dashou', statusKey: 'dashoustatus', dataFlag: 'isDashou', pageId: 'dashouduan', apiUrl: '/yonghu/dashouxinxi' } }))

View File

@@ -193,7 +193,7 @@
>
<view class="rank-cell-inner">
<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" />
<text class="rank-cell-name">{{item.name}}</text>
<text class="rank-cell-sub">{{item.sub}}</text>

View File

@@ -469,7 +469,7 @@ page {
position: relative;
display: flex;
align-items: center;
padding: 0 20rpx;
padding: 0 24rpx;
box-sizing: border-box;
border-radius: 16rpx;
overflow: hidden;
@@ -477,25 +477,27 @@ page {
.rank-bg-icon {
position: absolute;
width: 120rpx;
height: 120rpx;
right: -12rpx;
width: 100rpx;
height: 100rpx;
right: 8rpx;
top: 50%;
transform: translateY(-50%) rotate(-10deg);
opacity: 0.15;
opacity: 0.12;
z-index: 0;
pointer-events: none;
}
.rank-content {
.rank-cell-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
}
.rank-main-icon {
width: 56rpx;
height: 56rpx;
margin-bottom: 6rpx;
width: 44rpx;
height: 44rpx;
margin-bottom: 8rpx;
}
.rank-shape-right .rank-cell-inner {
@@ -509,7 +511,7 @@ page {
transform: translateY(-50%) rotate(10deg);
}
.rank-shape-right .rank-content {
.rank-shape-right .rank-cell-content {
text-align: right;
}

View File

@@ -1,92 +1,92 @@
<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}}"
refresher-enabled="{{true}}"
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
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">
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}">
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
<!-- 对方头像及昵称 -->
<view wx:if="{{item.senderId !== currentUser.id}}" class="sender-info">
<image class="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>
</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-text="{{item.type==='text' ? item.payload.text : ''}}"
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
data-url="{{item.type==='image' ? item.payload.url : ''}}">
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="order-bubble" data-payload="{{item.payload}}" bindtap="viewOrderDetail">
<text class="order-label">[订单]</text>
<text class="order-info">ID: {{item.payload.orderId}}</text>
<text class="order-info">内容: {{item.payload.jieshao}}</text>
<text class="order-info">金额: ¥{{item.payload.jine}}</text>
<view class="order-detail-link">点击查看详情</view>
<text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
<image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" />
<view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble" data-payload="{{item.payload}}" bindtap="viewOrderDetail">
<text class="chat-order-label">[订单]</text>
<text class="chat-order-info">ID: {{item.payload.orderId}}</text>
<text class="chat-order-info">内容: {{item.payload.jieshao}}</text>
<text class="chat-order-info">金额: ¥{{item.payload.jine}}</text>
<view class="chat-order-info" style="color:#007aff;margin-top:8rpx">点击查看详情</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>
</block>
<view id="msg-bottom" style="height:20rpx;"></view>
</scroll-view>
<view class="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="plus-grid">
<view class="plus-item" catchtap="openEmojiFromPlus">
<text class="plus-icon">😀</text>
<text class="plus-label">表情</text>
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
<view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
<view class="chat-plus-grid">
<view class="chat-plus-item" catchtap="openEmojiFromPlus">
<text class="chat-plus-icon">😀</text>
<text class="chat-plus-label">表情</text>
</view>
<view class="plus-item" catchtap="chooseImage">
<text class="plus-icon">📷</text>
<text class="plus-label">图片</text>
<view class="chat-plus-item" catchtap="chooseImage">
<text class="chat-plus-icon">📷</text>
<text class="chat-plus-label">图片</text>
</view>
<view class="plus-item" catchtap="openOrderSender">
<text class="plus-icon">📋</text>
<text class="plus-label">订单</text>
<view class="chat-plus-item" catchtap="openOrderSender">
<text class="chat-plus-icon">📋</text>
<text class="chat-plus-label">订单</text>
</view>
</view>
</view>
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="emoji-scroll">
<view class="emoji-grid">
<view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
<view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
<scroll-view scroll-y="true" class="chat-emoji-scroll">
<view class="chat-emoji-grid">
<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>
</view>
</scroll-view>
</view>
<view class="pending-image" wx:if="{{pendingImage}}">
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="pending-remove" catchtap="clearPendingImage">✕</view>
<view class="chat-pending-image" wx:if="{{pendingImage}}">
<image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
<view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
</view>
<view class="write-row">
<input class="text-input" placeholder="发送消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="send-btn" bindtap="sendMessage">发送</view>
<view class="plus-btn" bindtap="togglePlusPanel">
<text class="plus-icon-single"></text>
<view class="chat-write-row">
<input class="chat-text-input" placeholder="发送消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
<view class="chat-send-btn" bindtap="sendMessage">发送</view>
<view class="chat-plus-btn" bindtap="togglePlusPanel">
<text class="chat-plus-icon-single"></text>
</view>
</view>
</view>
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="detail-content" catchtap="noop">
<text class="detail-text" user-select="true">{{detailText}}</text>
<button class="copy-btn" bindtap="copyDetail">复制</button>
<view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
<view class="chat-detail-content" catchtap="noop">
<text class="chat-detail-text" user-select="true">{{detailText}}</text>
<button class="chat-copy-btn" bindtap="copyDetail">复制</button>
</view>
</view>
<order-sender visible="{{showOrderSender}}" bind:send="onSendOrder" bind:close="closeOrderSender" />
</view>
</view>

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 {
display: flex;
flex-direction: column;
@@ -106,4 +15,4 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View File

@@ -4,11 +4,12 @@ import { ensureLogin } from '../../utils/login';
import { fetchShopGoods, bindShop } from '../../utils/shop';
import { backfillUserProfileCache, getSessionToken } from '../../utils/role-tab-bar';
import PopupService from '../../services/popupService.js';
import { createPage } from '../../utils/base-page.js';
// 扫码场景参数名
const SCENE_PARAM = 'scene';
Page({
Page(createPage({
data: {
ossImageUrl: '',
lunbozhanwei: '',
@@ -26,7 +27,7 @@ Page({
isLoading: false,
isRefreshing: false,
hasInitialized: false,
currentTime: ''
},
@@ -43,7 +44,7 @@ Page({
console.error('scene解码失败', e);
}
}
// 等待全局配置加载完成
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() {
const now = new Date();
const year = now.getFullYear();
@@ -234,12 +225,12 @@ Page({
*/
filterShangpinData() {
const { selectedLeixingId, shangpinzhuanqu, shangpinliebiao } = this.data;
if (!selectedLeixingId || !shangpinzhuanqu || !shangpinliebiao) {
this.setData({ filteredData: [] });
return;
}
const zhuanquByLeixing = new Map();
shangpinzhuanqu.forEach(zhuanqu => {
if (zhuanqu.leixing_id === selectedLeixingId) {
@@ -249,7 +240,7 @@ Page({
zhuanquByLeixing.get(selectedLeixingId).push(zhuanqu);
}
});
const shangpinByZhuanqu = new Map();
shangpinliebiao.forEach(shangpin => {
if (shangpin.leixing_id === selectedLeixingId && shangpin.zhuanqu_id) {
@@ -257,7 +248,7 @@ Page({
if (!shangpinByZhuanqu.has(zhuanquId)) {
shangpinByZhuanqu.set(zhuanquId, []);
}
const jiage = parseFloat(shangpin.jiage) || 0;
const priceInteger = Math.floor(jiage);
let priceDecimal = '00';
@@ -265,7 +256,7 @@ Page({
if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1];
}
shangpinByZhuanqu.get(zhuanquId).push({
id: shangpin.id,
biaoqian: shangpin.biaoqian,
@@ -278,10 +269,10 @@ Page({
});
}
});
const filteredData = [];
const currentZhuanquList = zhuanquByLeixing.get(selectedLeixingId) || [];
currentZhuanquList.forEach(zhuanqu => {
const shangpinList = shangpinByZhuanqu.get(zhuanqu.id) || [];
if (shangpinList.length > 0) {
@@ -295,14 +286,14 @@ Page({
});
}
});
const sortedFilteredData = filteredData.sort((a, b) => {
const paixuA = a.zhuanqu.paixu || 0;
const paixuB = b.zhuanqu.paixu || 0;
if (paixuB !== paixuA) return paixuB - paixuA;
return a.zhuanqu.id - b.zhuanqu.id;
});
this.setData({ filteredData: sortedFilteredData });
},
@@ -315,18 +306,21 @@ Page({
},
showGonggaoDetail() {
this.setData({
this.setData({
showGonggaoModal: true,
_modalLeaving: false,
gonggaoAnim: false,
currentTime: this.getCurrentTime()
});
},
hideGonggaoDetail() {
this.setData({
this.setData({
showGonggaoModal: false,
gonggaoAnim: true
_modalLeaving: true,
gonggaoAnim: true
});
setTimeout(() => this.setData({ _modalLeaving: false }), 300);
},
/**
@@ -388,4 +382,4 @@ Page({
onUnload() {
this.setData({ gonggaoAnim: false });
}
});
}))

View File

@@ -146,9 +146,9 @@
</view>
<!-- 公告详情弹窗 -->
<view class="gonggao-modal" wx:if="{{showGonggaoModal}}">
<view class="modal-mask" bindtap="hideGonggaoDetail"></view>
<view class="modal-content">
<view class="gonggao-modal {{showGonggaoModal ? 'gonggao-modal--show' : 'gonggao-modal--hide'}}" wx:if="{{showGonggaoModal || _modalLeaving}}">
<view class="modal-mask {{showGonggaoModal ? 'modal-mask--in' : 'modal-mask--out'}}" bindtap="hideGonggaoDetail"></view>
<view class="modal-content {{showGonggaoModal ? 'modal-content--in' : 'modal-content--out'}}">
<view class="modal-header">
<text class="modal-title">公告详情</text>
<view class="modal-close" bindtap="hideGonggaoDetail">×</view>

View File

@@ -411,3 +411,82 @@
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;
}

View File

@@ -1,225 +1,135 @@
// pages/leader/leader.js
import request from '../../utils/request.js'
import {
isRoleStatusActive,
isCenterPageActive,
syncRoleStatuses,
syncProfileFields,
ensureRoleOnCenterPage,
} from '../../utils/role-tab-bar.js'
Page({
data: {
// 页面状态
isZuzhang: false,
isLoading: false,
// 邀请码
inviteCode: '',
// 用户信息
uid: '',
nicheng: '',
avatarUrl: '',
// 组长信息
ketixian: '0.00',
guanshiCount: 0,
fenhongZonge: '0.00',
},
onLoad() {
wx.redirectTo({ url: '/pages/fighter/fighter' });
return;
// 以下保留供恢复参考
this.checkLoginStatus()
},
onReady() {
if (isCenterPageActive(this, 'isZuzhang', 'zuzhangstatus')) {
ensureRoleOnCenterPage(this, 'zuzhang')
}
},
onShow() {
this.registerNotificationComponent()
if (isCenterPageActive(this, 'isZuzhang', 'zuzhangstatus')) {
if (!this.data.isZuzhang) this.setData({ isZuzhang: true })
ensureRoleOnCenterPage(this, 'zuzhang')
this.getZuzhangInfo()
}
},
// 检查登录状态
checkLoginStatus() {
const token = wx.getStorageSync('token')
if (!token) {
wx.showModal({
title: '提示',
content: '您尚未登录,请先登录',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' })
}
}
})
return false
}
this.checkZuzhangStatus()
return true
},
// 检查组长状态
checkZuzhangStatus() {
try {
const zuzhangstatus = wx.getStorageSync('zuzhangstatus')
if (isRoleStatusActive(zuzhangstatus)) {
this.setData({ isZuzhang: true })
this.loadUserInfo()
this.getZuzhangInfo()
ensureRoleOnCenterPage(this, 'zuzhang')
} else {
this.setData({ isZuzhang: false })
}
} catch (error) {
console.error('读取缓存失败:', error)
this.setData({ isZuzhang: false })
}
},
// 加载用户信息
loadUserInfo() {
const app = getApp()
const globalData = app.globalData || {}
const touxiang = wx.getStorageSync('touxiang')
const ossImageUrl = globalData.ossImageUrl || ''
const morentouxiang = globalData.morentouxiang || 'avatar/default.jpg'
let avatarUrl = ''
if (touxiang) {
avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang)
} else {
avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang)
}
const uid = wx.getStorageSync('uid') || ''
const nicheng = wx.getStorageSync('nicheng') || globalData.nicheng || ''
this.setData({ avatarUrl, uid, nicheng })
},
// 注册通知组件
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()
}
}
},
// 获取组长信息(刷新)
async getZuzhangInfo() {
this.setData({ isLoading: true })
try {
const res = await request({
url: '/yonghu/zuzhangxinxi',
method: 'POST'
})
if (res && res.data.code === 200) {
const data = res.data.data
this.setData({
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' })
}
})
// pages/leader/leader.js
import { createPage, request, syncRoleStatuses, syncProfileFields, ensureRoleOnCenterPage, isCenterPageActive, parseSceneOptions } from '../../utils/base-page.js'
Page(createPage({
data: {
isZuzhang: false,
// 邀请码
inviteCode: '',
// 用户信息
uid: '',
nicheng: '',
avatarUrl: '',
// 组长信息
ketixian: '0.00',
guanshiCount: 0,
fenhongZonge: '0.00',
},
onLoad(options) {
const parsed = parseSceneOptions(options)
if (parsed.inviteCode) {
this.setData({ inviteCode: parsed.inviteCode })
}
wx.redirectTo({ url: '/pages/fighter/fighter' })
return
// 以下保留供恢复参考
this.checkLoginStatus()
},
onShow() {
this.registerNotificationComponent()
this.checkColdStartPopup('zuzhangduan')
if (isCenterPageActive(this, 'isZuzhang', 'zuzhangstatus')) {
if (!this.data.isZuzhang) this.setData({ isZuzhang: true })
ensureRoleOnCenterPage(this, 'zuzhang')
this.getZuzhangInfo()
}
},
// 检查登录状态
checkLoginStatus() {
const token = wx.getStorageSync('token')
if (!token) {
wx.showModal({
title: '提示',
content: '您尚未登录,请先登录',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' })
}
}
})
return false
}
this.checkRoleStatus()
return true
},
// 角色状态检查后的额外数据加载(由 mixin 的 checkRoleStatus 调用)
checkGlobalData() {
this.loadUserInfo()
this.getZuzhangInfo()
},
// 获取组长信息(刷新)
async getZuzhangInfo() {
this.loadData({
url: '/yonghu/zuzhangxinxi',
method: 'POST',
errorMsg: '获取信息失败',
onSuccess: (data) => {
this.setData({
ketixian: data.ketixian || '0.00',
guanshiCount: data.guanshiCount || 0,
fenhongZonge: data.fenhongZonge || '0.00',
})
wx.showToast({ title: '刷新成功', icon: 'success', duration: 1500 })
}
})
},
// 刷新按钮点击
refreshZuzhangInfo() {
this.throttledRefresh(this.getZuzhangInfo)
},
// 邀请码输入
onInviteCodeInput(e) {
this.setData({ inviteCode: e.detail.value.trim() })
},
// 注册组长
onRegister() {
const { inviteCode } = this.data
this.registerWithInviteCode({
inviteCode,
apiUrl: '/yonghu/zuzhangzhuce',
role: 'zuzhang',
statusKey: 'zuzhangstatus',
successMsg: '注册成功!',
onSuccess: (userData) => {
this.setData({
isZuzhang: true,
ketixian: userData.ketixian || '0.00',
guanshiCount: userData.guanshiCount || 0,
fenhongZonge: userData.fenhongZonge || '0.00',
})
this.loadUserInfo()
ensureRoleOnCenterPage(this, 'zuzhang')
}
})
},
// 跳转到提现页面
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' })
}
}, { roleConfig: { role: 'dashou', statusKey: 'zuzhangstatus', dataFlag: 'isZuzhang', pageId: 'zuzhangduan', apiUrl: '/yonghu/zuzhangxinxi' } }))

View File

@@ -1,235 +1,205 @@
<!-- pages/guanshi-paihang/guanshi-paihang.wxml -->
<view class="guanshi-paihang-page">
<!-- pages/manager-rank/manager-rank.wxml -->
<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="stream stream-1"></view>
<view class="stream stream-2"></view>
<view class="stream stream-3"></view>
<view class="rank-data-streams">
<view class="rank-stream rank-stream--1"></view>
<view class="rank-stream rank-stream--2"></view>
<view class="rank-stream rank-stream--3"></view>
</view>
<!-- 浮动粒子 -->
<view class="floating-particles">
<view class="particle" wx:for="{{12}}" wx:key="index"></view>
</view>
<view class="rank-particle" wx:for="{{12}}" wx:key="index"></view>
</view>
<!-- 页面头部 -->
<view class="page-header">
<view class="rank-page-header">
<!-- 标题 -->
<view class="title-container">
<text class="main-title">管事排行榜</text>
<view class="title-line"></view>
<text class="sub-title">GUANSHI RANKING</text>
<view class="rank-title-container">
<text class="rank-main-title">管事排行榜</text>
<view class="rank-title-line"></view>
<text class="rank-sub-title">GUANSHI RANKING</text>
</view>
<!-- 切换按钮 -->
<view class="toggle-container">
<view class="toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view
class="toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
<view class="rank-toggle-container">
<view class="rank-toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view
class="rank-toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="1"
bindtap="qiehuangPaihangLeixing"
>
<text class="toggle-text">今日充值人数</text>
<view class="toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
<text class="rank-toggle-text">今日充值人数</text>
<view class="rank-toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
</view>
<view
class="toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
<view
class="rank-toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="2"
bindtap="qiehuangPaihangLeixing"
>
<text class="toggle-text">今月充值人数</text>
<view class="toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
<text class="rank-toggle-text">今月充值人数</text>
<view class="rank-toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view wx:if="{{jiazaiZhuangtai}}" class="loading-state">
<view class="loading-content">
<view class="loading-spinner">
<view class="spinner-outer"></view>
<view class="spinner-inner"></view>
<view class="spinner-core"></view>
</view>
<text class="loading-text">数据加载中</text>
<text class="loading-sub">Connecting to server...</text>
<view wx:if="{{jiazaiZhuangtai}}" class="rank-loading-state">
<view class="rank-loading-spinner">
<view class="rank-spinner-outer"></view>
<view class="rank-spinner-inner"></view>
<view class="rank-spinner-core"></view>
</view>
<text class="rank-loading-text">数据加载中</text>
<text class="rank-loading-sub">Connecting to server...</text>
</view>
<!-- 排行榜内容 -->
<view wx:else class="ranking-content">
<view wx:else class="rank-content">
<!-- 虚拟数据提示 -->
<view wx:if="{{!xianshiZhenshiShuju}}" class="demo-notice">
<view class="notice-icon">📡</view>
<view class="notice-content">
<text class="notice-title">排行榜数据准备中</text>
<text class="notice-desc">当前显示为模拟数据,实时排名即将开放</text>
<view wx:if="{{!xianshiZhenshiShuju}}" class="rank-demo-notice">
<view class="rank-notice-icon">📡</view>
<view class="rank-notice-content">
<text class="rank-notice-title">排行榜数据准备中</text>
<text class="rank-notice-desc">当前显示为模拟数据,实时排名即将开放</text>
</view>
<view class="notice-glow"></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 : ''}}"
data-index="1" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-silver">
<view class="podium-glow"></view>
<text class="podium-number">2</text>
<view class="rank-podium-base rank-podium-silver">
<text class="rank-podium-number">2</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-silver">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-silver"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
</view>
<view class="rank-avatar-frame rank-frame-silver">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-silver"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
</view>
<view class="rank-badge badge-silver">2</view>
<view class="rank-badge rank-badge-silver">2</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">充值大手:</text>
<view class="income-value">
<text class="amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0'}}</text>
<text class="unit">人</text>
<view class="rank-income-row">
<text class="rank-income-label">充值大手:</text>
<view class="rank-income-value">
<text class="rank-amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0'}}</text>
<text class="rank-unit">人</text>
</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 : ''}}"
data-index="0" data-type="top3" bindtap="handleCardTap">
<!-- 皇冠 -->
<view class="crown-container" animation="{{crownAnimation}}">
<view class="crown">
<text class="crown-icon">👑</text>
<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 class="rank-crown-container" animation="{{crownAnimation}}">
<text class="rank-crown-icon">👑</text>
<view class="rank-crown-glow"></view>
</view>
<view class="podium-base podium-gold">
<view class="podium-glow"></view>
<text class="podium-number">1</text>
<view class="rank-podium-base rank-podium-gold">
<text class="rank-podium-number">1</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-gold">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-gold"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
<view class="sparkle s4"></view>
</view>
<view class="rank-avatar-frame rank-frame-gold">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-gold"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="rank-sparkle" style="bottom:15%;right:15%;animation-delay:1.5s"></view>
</view>
<view class="rank-badge badge-gold">1</view>
<view class="rank-badge rank-badge-gold">1</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">充值大手:</text>
<view class="income-value">
<text class="amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0'}}</text>
<text class="unit">人</text>
<view class="rank-income-row">
<text class="rank-income-label">充值大手:</text>
<view class="rank-income-value">
<text class="rank-amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0'}}</text>
<text class="rank-unit">人</text>
</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 : ''}}"
data-index="2" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-bronze">
<view class="podium-glow"></view>
<text class="podium-number">3</text>
<view class="rank-podium-base rank-podium-bronze">
<text class="rank-podium-number">3</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-bronze">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-bronze"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
</view>
<view class="rank-avatar-frame rank-frame-bronze">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-bronze"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
</view>
<view class="rank-badge badge-bronze">3</view>
<view class="rank-badge rank-badge-bronze">3</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">充值大手:</text>
<view class="income-value">
<text class="amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0'}}</text>
<text class="unit">人</text>
<view class="rank-income-row">
<text class="rank-income-label">充值大手:</text>
<view class="rank-income-value">
<text class="rank-amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0'}}</text>
<text class="rank-unit">人</text>
</view>
</view>
</view>
@@ -237,52 +207,48 @@
</view>
</view>
</view>
<!-- ========== 其他排名列表 ========== -->
<view class="other-ranks-section" wx:if="{{paihangShuju.length > 0}}">
<view class="section-title">
<view class="title-line"></view>
<text class="title-text">其他排名</text>
<view class="title-line"></view>
<view wx:if="{{paihangShuju.length > 0}}">
<view class="rank-section-title">
<view class="rank-section-line"></view>
<text class="rank-section-text">其他排名</text>
<view class="rank-section-line"></view>
</view>
<view class="rank-list">
<view class="rank-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
wx:for="{{paihangShuju}}" wx:key="index"
animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}"
data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
<view class="rank-number">
<text class="number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<view class="number-glow"></view>
</view>
<view class="rank-avatar">
<image class="rank-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo"></view>
</view>
<view class="rank-info">
<text class="rank-name">{{item.nicheng}}</text>
<text class="rank-uid">UID: {{item.uid}}</text>
</view>
<view class="rank-income">
<text class="income-amount {{item.jineClass}}">{{item.jine}}</text>
<text class="income-unit"></text>
<view class="income-glow"></view>
</view>
<view class="rank-hover"></view>
<view class="rank-list-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
wx:for="{{paihangShuju}}" wx:key="index"
animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}"
data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
<view class="rank-number">
<text class="rank-number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<view class="rank-number-glow"></view>
</view>
<view class="rank-list-avatar">
<image class="rank-list-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-list-avatar-halo"></view>
</view>
<view class="rank-list-info">
<text class="rank-list-name">{{item.nicheng}}</text>
<text class="rank-list-uid">UID: {{item.uid}}</text>
</view>
<view class="rank-list-income">
<text class="rank-income-amount {{item.jineClass}}">{{item.jine}}</text>
<text class="rank-income-unit">人</text>
<view class="rank-income-glow"></view>
</view>
</view>
</view>
<!-- 空状态 -->
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="empty-state">
<view class="empty-icon">🏆</view>
<text class="empty-title">暂无排名数据</text>
<text class="empty-desc">成为第一个登榜的管事吧</text>
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="rank-empty-state">
<view class="rank-empty-icon">🏆</view>
<text class="rank-empty-title">暂无排名数据</text>
<text class="rank-empty-desc">成为第一个登榜的管事吧</text>
</view>
</view>
</view>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,418 +1,406 @@
// pages/merchant-dispatch/merchant-dispatch.js
import request from '../../utils/request.js'
import PopupService from '../../services/popupService.js' // 引入弹窗服务
Page({
data: {
// 商家余额 (从全局变量获取)
sjyue: '0.00',
// 商品类型列表
shangpinList: [],
// 当前选中的商品类型信息
selectedTypeId: null,
selectedHuiyuanId: null,
selectedYaoqiuleixing: null,
// 当前商品类型下的称号列表(用于标签选择器)
currentTypeChenghaoList: [],
// 表单字段
dingdanJieshao: '', // 订单介绍
dingdanBeizhu: '', // 订单备注
laobanName: '', // 老板游戏昵称/UID
zhidingUid: '', // 指定打手UID
jiage: '', // 订单价格
// 新增字段:抢单标签(称号)
selectedLabelId: '', // 选中的标签ID
selectedLabelName: '', // 选中的标签名称
// 新增字段:佣金要求(压金)
commissionEnabled: false, // 是否开启佣金要求
commissionValue: '', // 佣金金额(元)
// 页面状态
isLoading: false, // 加载商品类型中
isSubmitting: false, // 提交派单中
// 教程按钮状态
tutorialBtnHidden: false, // false=完全显示true=缩进隐藏(只露边)
// 背景图URL动态拼接
bgImageUrl: ''
},
onLoad() {
this.loadBgImage() // 拼接背景图
this.loadShangjiaYue() // 从全局变量获取商家余额
this.loadShangpinTypes() // 加载商品类型
this.startTutorialBtnTimer() // 启动按钮缩进定时器
},
onShow() {
// 每次显示页面时刷新余额(可能从其他页面充值后返回)
this.loadShangjiaYue()
// 重新拼接背景图(确保最新)
this.loadBgImage()
// 注册通知组件(用于全局消息提示)
this.registerNotificationComponent()
// 重置按钮缩进定时器重新开始5秒倒计时
this.startTutorialBtnTimer()
},
onHide() {
// 页面隐藏时重置弹窗服务状态,避免影响其他页面
PopupService.reset()
},
onUnload() {
// 清除缩进定时器,避免内存泄漏
if (this.tutorialTimer) {
clearTimeout(this.tutorialTimer)
}
// 页面卸载时重置弹窗服务
PopupService.reset()
},
// ==================== 背景图动态拼接 ====================
loadBgImage() {
const app = getApp()
const ossBase = app.globalData.ossImageUrl || 'https://your-oss-bucket.oss-cn-region.aliyuncs.com'
// 拼接完整路径beijing/shangjiapaidan/bg.jpg
const bgUrl = ossBase.replace(/\/$/, '') + '/beijing/shangjiapaidan/bg.jpg'
this.setData({ bgImageUrl: bgUrl })
},
// ==================== 基础数据加载 ====================
loadShangjiaYue() {
const app = getApp()
const shangjiaData = app.globalData.shangjia || {}
this.setData({
sjyue: shangjiaData.sjyue || '0.00'
})
},
// 加载商品类型(与极速派单完全一致)
async loadShangpinTypes() {
this.setData({ isLoading: true })
try {
const res = await request({
url: '/dingdan/sjspleixing',
method: 'POST'
})
if (res && res.data.code === 200) {
let list = res.data.data || []
// 强制倒序排列(与极速派单保持一致)
list.reverse()
this.setData({
shangpinList: list,
isLoading: false
})
// 默认选中第一个
if (list.length > 0) {
this.setSelectedType(list[0])
}
} else {
throw new Error(res.data.msg || '加载失败')
}
} catch (error) {
console.error('加载商品类型失败:', error)
this.setData({ isLoading: false })
wx.showToast({
title: '加载类型失败',
icon: 'error',
duration: 2000
})
}
},
// 设置选中的商品类型,并提取该类型下的称号列表
setSelectedType(item) {
this.setData({
selectedTypeId: item.id,
selectedHuiyuanId: item.huiyuan_id,
selectedYaoqiuleixing: item.yaoqiuleixing,
currentTypeChenghaoList: item.chenghaoList || [],
// 切换商品类型时,清空之前选择的标签和佣金
selectedLabelId: '',
selectedLabelName: '',
commissionEnabled: false,
commissionValue: ''
})
},
// 注册全局通知组件(用于顶部提示)
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()
}
}
},
// ==================== 教程按钮控制 ====================
// 启动定时器5秒后让按钮缩进
startTutorialBtnTimer() {
// 清除旧的定时器
if (this.tutorialTimer) {
clearTimeout(this.tutorialTimer)
}
// 5秒后将按钮缩进隐藏
this.tutorialTimer = setTimeout(() => {
this.setData({ tutorialBtnHidden: true })
}, 5000)
},
// 重置按钮状态完全显示并重新开始5秒倒计时
resetTutorialBtn() {
// 如果按钮当前是缩进状态,先恢复完全显示
if (this.data.tutorialBtnHidden) {
this.setData({ tutorialBtnHidden: false })
}
// 重新开始计时清除旧定时器新定时器5秒后缩进
if (this.tutorialTimer) {
clearTimeout(this.tutorialTimer)
}
this.tutorialTimer = setTimeout(() => {
this.setData({ tutorialBtnHidden: true })
}, 5000)
},
// 点击教程按钮:重置按钮状态,并调用 PopupService 弹出教程
async onTutorialBtnTap() {
// 首先重置按钮状态(完全显示并重新计时)
this.resetTutorialBtn()
// 调用弹窗服务,页面标识为 'sjpd1'
PopupService.checkAndShow(this, 'sjpd1')
},
// ==================== 表单输入事件 ====================
onDingdanJieshaoInput(e) {
this.setData({ dingdanJieshao: e.detail.value })
},
onDingdanBeizhuInput(e) {
this.setData({ dingdanBeizhu: e.detail.value })
},
onLaobanNameInput(e) {
this.setData({ laobanName: e.detail.value })
},
onZhidingUidInput(e) {
this.setData({ zhidingUid: e.detail.value })
},
onJiageInput(e) {
let value = e.detail.value
// 前端验证:确保是数字且最多两位小数
if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) {
this.setData({ jiage: value })
}
},
// 标签选择器picker事件
onLabelChange(e) {
const idx = e.detail.value
const label = this.data.currentTypeChenghaoList[idx]
if (label) {
this.setData({
selectedLabelId: label.id,
selectedLabelName: label.mingcheng
})
} else {
this.setData({
selectedLabelId: '',
selectedLabelName: ''
})
}
},
// 佣金开关
onCommissionToggle() {
this.setData({ commissionEnabled: !this.data.commissionEnabled })
},
onCommissionValueInput(e) {
let value = e.detail.value
if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) {
this.setData({ commissionValue: value })
}
},
// ==================== 表单验证与提交 ====================
validateForm() {
const {
selectedTypeId,
dingdanJieshao,
laobanName,
jiage,
sjyue,
commissionEnabled,
commissionValue
} = this.data
// 1. 验证必填项
if (!selectedTypeId) {
wx.showToast({ title: '请选择订单类型', icon: 'none' })
return false
}
if (!dingdanJieshao.trim()) {
wx.showToast({ title: '请输入订单介绍', icon: 'none' })
return false
}
if (!laobanName.trim()) {
wx.showToast({ title: '请输入老板游戏昵称', icon: 'none' })
return false
}
if (!jiage) {
wx.showToast({ title: '请输入订单价格', icon: 'none' })
return false
}
// 2. 验证价格格式和范围
const price = parseFloat(jiage)
if (isNaN(price) || price < 0.1 || price > 10000) {
wx.showToast({ title: '价格需在0.1-10000元之间', icon: 'none' })
return false
}
if (jiage.includes('.') && jiage.split('.')[1].length > 2) {
wx.showToast({ title: '最多支持两位小数', icon: 'none' })
return false
}
// 3. 验证佣金要求(若开启)
if (commissionEnabled) {
const commission = parseFloat(commissionValue)
if (isNaN(commission) || commission <= 0) {
wx.showToast({ title: '请输入有效的佣金金额', icon: 'none' })
return false
}
if (commission > 50000) {
wx.showToast({ title: '佣金建议不超过50000元', icon: 'none' })
return false
}
}
// 4. 验证余额是否足够
const balance = parseFloat(sjyue)
if (price > balance) {
wx.showModal({
title: '余额不足',
content: `当前余额${sjyue}元,需要${jiage}元。请先充值。`,
confirmText: '去充值',
success: (res) => {
if (res.confirm) {
wx.navigateTo({
url: '/pages/merchant-recharge/merchant-recharge'
})
}
}
})
return false
}
return true
},
// 重置表单(保留商品类型选中状态)
resetForm() {
this.setData({
dingdanJieshao: '',
dingdanBeizhu: '',
laobanName: '',
zhidingUid: '',
jiage: '',
selectedLabelId: '',
selectedLabelName: '',
commissionEnabled: false,
commissionValue: ''
})
// 重置选中第一个商品类型(可选)
if (this.data.shangpinList.length > 0) {
this.setSelectedType(this.data.shangpinList[0])
}
},
// 提交派单
async onSubmit() {
if (this.data.isSubmitting) return
if (!this.validateForm()) return
this.setData({ isSubmitting: true })
// 准备提交数据(原有字段 + 新增字段)
const postData = {
// 原有必传字段
shangpinTypeId: this.data.selectedTypeId,
huiyuanId: this.data.selectedHuiyuanId,
yaoqiuleixing: this.data.selectedYaoqiuleixing,
dingdanJieshao: this.data.dingdanJieshao.trim(),
dingdanBeizhu: this.data.dingdanBeizhu.trim() || '',
laobanName: this.data.laobanName.trim(),
zhidingUid: this.data.zhidingUid.trim() || '',
jiage: this.data.jiage,
// 新增字段
labelId: this.data.selectedLabelId || '',
labelName: this.data.selectedLabelName || '',
commissionEnabled: this.data.commissionEnabled,
commissionValue: this.data.commissionEnabled ? this.data.commissionValue : ''
}
try {
const res = await request({
url: '/dingdan/sjpaifa',
method: 'POST',
data: postData
})
if (res && res.data.code === 200) {
// 派单成功
wx.showToast({
title: '派单成功',
icon: 'success',
duration: 2000
})
// 更新全局变量中的商家信息(余额、发单量、流水)
const orderAmount = parseFloat(this.data.jiage)
const app = getApp()
if (app.globalData.shangjia) {
const newBalance = parseFloat(app.globalData.shangjia.sjyue || '0') - orderAmount
app.globalData.shangjia.sjyue = newBalance.toFixed(2)
app.globalData.shangjia.fadanzong = (parseInt(app.globalData.shangjia.fadanzong || '0') + 1).toString()
app.globalData.shangjia.riliushui = (parseFloat(app.globalData.shangjia.riliushui || '0') + orderAmount).toFixed(2)
app.globalData.shangjia.yueliushui = (parseFloat(app.globalData.shangjia.yueliushui || '0') + orderAmount).toFixed(2)
this.setData({ sjyue: app.globalData.shangjia.sjyue })
}
// 延迟重置表单,避免用户连续提交
setTimeout(() => {
this.resetForm()
this.setData({ isSubmitting: false })
}, 1500)
} else {
wx.showModal({
title: '派单失败',
content: res.data.msg || '请稍后重试',
showCancel: false
})
this.setData({ isSubmitting: false })
}
} catch (error) {
console.error('派单请求失败:', error)
wx.showModal({
title: '请求失败',
content: error.message || '网络错误,请检查连接',
showCancel: false
})
this.setData({ isSubmitting: false })
}
},
// ==================== 商品类型选择 ====================
onSelectType(e) {
const { id, item } = e.currentTarget.dataset
this.setSelectedType(item)
}
})
// pages/merchant-dispatch/merchant-dispatch.js
import { createPage, request } from '../../utils/base-page.js'
import PopupService from '../../services/popupService.js' // 引入弹窗服务
Page(createPage({
data: {
// 商家余额 (从全局变量获取)
sjyue: '0.00',
// 商品类型列表
shangpinList: [],
// 当前选中的商品类型信息
selectedTypeId: null,
selectedHuiyuanId: null,
selectedYaoqiuleixing: null,
// 当前商品类型下的称号列表(用于标签选择器)
currentTypeChenghaoList: [],
// 表单字段
dingdanJieshao: '', // 订单介绍
dingdanBeizhu: '', // 订单备注
laobanName: '', // 老板游戏昵称/UID
zhidingUid: '', // 指定打手UID
jiage: '', // 订单价格
// 新增字段:抢单标签(称号)
selectedLabelId: '', // 选中的标签ID
selectedLabelName: '', // 选中的标签名称
// 新增字段:佣金要求(压金)
commissionEnabled: false, // 是否开启佣金要求
commissionValue: '', // 佣金金额(元)
// 页面状态
isLoading: false, // 加载商品类型中
isSubmitting: false, // 提交派单中
// 教程按钮状态
tutorialBtnHidden: false, // false=完全显示true=缩进隐藏(只露边)
// 背景图URL动态拼接
bgImageUrl: ''
},
onLoad() {
this.loadBgImage() // 拼接背景图
this.loadShangjiaYue() // 从全局变量获取商家余额
this.loadShangpinTypes() // 加载商品类型
this.startTutorialBtnTimer() // 启动按钮缩进定时器
},
onShow() {
// 每次显示页面时刷新余额(可能从其他页面充值后返回)
this.loadShangjiaYue()
// 重新拼接背景图(确保最新)
this.loadBgImage()
// 注册通知组件(用于全局消息提示)
this.registerNotificationComponent()
// 重置按钮缩进定时器重新开始5秒倒计时
this.startTutorialBtnTimer()
},
onHide() {
// 页面隐藏时重置弹窗服务状态,避免影响其他页面
PopupService.reset()
},
onUnload() {
// 清除缩进定时器,避免内存泄漏
if (this.tutorialTimer) {
clearTimeout(this.tutorialTimer)
}
// 页面卸载时重置弹窗服务
PopupService.reset()
},
// ==================== 背景图动态拼接 ====================
loadBgImage() {
const app = getApp()
const ossBase = app.globalData.ossImageUrl || 'https://your-oss-bucket.oss-cn-region.aliyuncs.com'
// 拼接完整路径beijing/shangjiapaidan/bg.jpg
const bgUrl = ossBase.replace(/\/$/, '') + '/beijing/shangjiapaidan/bg.jpg'
this.setData({ bgImageUrl: bgUrl })
},
// ==================== 基础数据加载 ====================
loadShangjiaYue() {
const app = getApp()
const shangjiaData = app.globalData.shangjia || {}
this.setData({
sjyue: shangjiaData.sjyue || '0.00'
})
},
// 加载商品类型(与极速派单完全一致)
async loadShangpinTypes() {
this.setData({ isLoading: true })
try {
const res = await request({
url: '/dingdan/sjspleixing',
method: 'POST'
})
if (res && res.data.code === 200) {
let list = res.data.data || []
// 强制倒序排列(与极速派单保持一致)
list.reverse()
this.setData({
shangpinList: list,
isLoading: false
})
// 默认选中第一个
if (list.length > 0) {
this.setSelectedType(list[0])
}
} else {
throw new Error(res.data.msg || '加载失败')
}
} catch (error) {
console.error('加载商品类型失败:', error)
this.setData({ isLoading: false })
wx.showToast({
title: '加载类型失败',
icon: 'error',
duration: 2000
})
}
},
// 设置选中的商品类型,并提取该类型下的称号列表
setSelectedType(item) {
this.setData({
selectedTypeId: item.id,
selectedHuiyuanId: item.huiyuan_id,
selectedYaoqiuleixing: item.yaoqiuleixing,
currentTypeChenghaoList: item.chenghaoList || [],
// 切换商品类型时,清空之前选择的标签和佣金
selectedLabelId: '',
selectedLabelName: '',
commissionEnabled: false,
commissionValue: ''
})
},
// ==================== 教程按钮控制 ====================
// 启动定时器5秒后让按钮缩进
startTutorialBtnTimer() {
// 清除旧的定时器
if (this.tutorialTimer) {
clearTimeout(this.tutorialTimer)
}
// 5秒后将按钮缩进隐藏
this.tutorialTimer = setTimeout(() => {
this.setData({ tutorialBtnHidden: true })
}, 5000)
},
// 重置按钮状态完全显示并重新开始5秒倒计时
resetTutorialBtn() {
// 如果按钮当前是缩进状态,先恢复完全显示
if (this.data.tutorialBtnHidden) {
this.setData({ tutorialBtnHidden: false })
}
// 重新开始计时清除旧定时器新定时器5秒后缩进
if (this.tutorialTimer) {
clearTimeout(this.tutorialTimer)
}
this.tutorialTimer = setTimeout(() => {
this.setData({ tutorialBtnHidden: true })
}, 5000)
},
// 点击教程按钮:重置按钮状态,并调用 PopupService 弹出教程
async onTutorialBtnTap() {
// 首先重置按钮状态(完全显示并重新计时)
this.resetTutorialBtn()
// 调用弹窗服务,页面标识为 'sjpd1'
PopupService.checkAndShow(this, 'sjpd1')
},
// ==================== 表单输入事件 ====================
onDingdanJieshaoInput(e) {
this.setData({ dingdanJieshao: e.detail.value })
},
onDingdanBeizhuInput(e) {
this.setData({ dingdanBeizhu: e.detail.value })
},
onLaobanNameInput(e) {
this.setData({ laobanName: e.detail.value })
},
onZhidingUidInput(e) {
this.setData({ zhidingUid: e.detail.value })
},
onJiageInput(e) {
let value = e.detail.value
// 前端验证:确保是数字且最多两位小数
if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) {
this.setData({ jiage: value })
}
},
// 标签选择器picker事件
onLabelChange(e) {
const idx = e.detail.value
const label = this.data.currentTypeChenghaoList[idx]
if (label) {
this.setData({
selectedLabelId: label.id,
selectedLabelName: label.mingcheng
})
} else {
this.setData({
selectedLabelId: '',
selectedLabelName: ''
})
}
},
// 佣金开关
onCommissionToggle() {
this.setData({ commissionEnabled: !this.data.commissionEnabled })
},
onCommissionValueInput(e) {
let value = e.detail.value
if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) {
this.setData({ commissionValue: value })
}
},
// ==================== 表单验证与提交 ====================
validateForm() {
const {
selectedTypeId,
dingdanJieshao,
laobanName,
jiage,
sjyue,
commissionEnabled,
commissionValue
} = this.data
// 1. 验证必填项
if (!selectedTypeId) {
wx.showToast({ title: '请选择订单类型', icon: 'none' })
return false
}
if (!dingdanJieshao.trim()) {
wx.showToast({ title: '请输入订单介绍', icon: 'none' })
return false
}
if (!laobanName.trim()) {
wx.showToast({ title: '请输入老板游戏昵称', icon: 'none' })
return false
}
if (!jiage) {
wx.showToast({ title: '请输入订单价格', icon: 'none' })
return false
}
// 2. 验证价格格式和范围
const price = parseFloat(jiage)
if (isNaN(price) || price < 0.1 || price > 10000) {
wx.showToast({ title: '价格需在0.1-10000元之间', icon: 'none' })
return false
}
if (jiage.includes('.') && jiage.split('.')[1].length > 2) {
wx.showToast({ title: '最多支持两位小数', icon: 'none' })
return false
}
// 3. 验证佣金要求(若开启)
if (commissionEnabled) {
const commission = parseFloat(commissionValue)
if (isNaN(commission) || commission <= 0) {
wx.showToast({ title: '请输入有效的佣金金额', icon: 'none' })
return false
}
if (commission > 50000) {
wx.showToast({ title: '佣金建议不超过50000元', icon: 'none' })
return false
}
}
// 4. 验证余额是否足够
const balance = parseFloat(sjyue)
if (price > balance) {
wx.showModal({
title: '余额不足',
content: `当前余额${sjyue}元,需要${jiage}元。请先充值。`,
confirmText: '去充值',
success: (res) => {
if (res.confirm) {
wx.navigateTo({
url: '/pages/merchant-recharge/merchant-recharge'
})
}
}
})
return false
}
return true
},
// 重置表单(保留商品类型选中状态)
resetForm() {
this.setData({
dingdanJieshao: '',
dingdanBeizhu: '',
laobanName: '',
zhidingUid: '',
jiage: '',
selectedLabelId: '',
selectedLabelName: '',
commissionEnabled: false,
commissionValue: ''
})
// 重置选中第一个商品类型(可选)
if (this.data.shangpinList.length > 0) {
this.setSelectedType(this.data.shangpinList[0])
}
},
// 提交派单
async onSubmit() {
if (this.data.isSubmitting) return
if (!this.validateForm()) return
this.setData({ isSubmitting: true })
// 准备提交数据(原有字段 + 新增字段)
const postData = {
// 原有必传字段
shangpinTypeId: this.data.selectedTypeId,
huiyuanId: this.data.selectedHuiyuanId,
yaoqiuleixing: this.data.selectedYaoqiuleixing,
dingdanJieshao: this.data.dingdanJieshao.trim(),
dingdanBeizhu: this.data.dingdanBeizhu.trim() || '',
laobanName: this.data.laobanName.trim(),
zhidingUid: this.data.zhidingUid.trim() || '',
jiage: this.data.jiage,
// 新增字段
labelId: this.data.selectedLabelId || '',
labelName: this.data.selectedLabelName || '',
commissionEnabled: this.data.commissionEnabled,
commissionValue: this.data.commissionEnabled ? this.data.commissionValue : ''
}
try {
const res = await request({
url: '/dingdan/sjpaifa',
method: 'POST',
data: postData
})
if (res && res.data.code === 200) {
// 派单成功
wx.showToast({
title: '派单成功',
icon: 'success',
duration: 2000
})
// 更新全局变量中的商家信息(余额、发单量、流水)
const orderAmount = parseFloat(this.data.jiage)
const app = getApp()
if (app.globalData.shangjia) {
const newBalance = parseFloat(app.globalData.shangjia.sjyue || '0') - orderAmount
app.globalData.shangjia.sjyue = newBalance.toFixed(2)
app.globalData.shangjia.fadanzong = (parseInt(app.globalData.shangjia.fadanzong || '0') + 1).toString()
app.globalData.shangjia.riliushui = (parseFloat(app.globalData.shangjia.riliushui || '0') + orderAmount).toFixed(2)
app.globalData.shangjia.yueliushui = (parseFloat(app.globalData.shangjia.yueliushui || '0') + orderAmount).toFixed(2)
this.setData({ sjyue: app.globalData.shangjia.sjyue })
}
// 延迟重置表单,避免用户连续提交
setTimeout(() => {
this.resetForm()
this.setData({ isSubmitting: false })
}, 1500)
} else {
wx.showModal({
title: '派单失败',
content: res.data.msg || '请稍后重试',
showCancel: false
})
this.setData({ isSubmitting: false })
}
} catch (error) {
console.error('派单请求失败:', error)
wx.showModal({
title: '请求失败',
content: error.message || '网络错误,请检查连接',
showCancel: false
})
this.setData({ isSubmitting: false })
}
},
// ==================== 商品类型选择 ====================
onSelectType(e) {
const { id, item } = e.currentTarget.dataset
this.setSelectedType(item)
}
}))

View File

@@ -1,327 +1,307 @@
// pages/merchant-orders/merchant-orders.js
const app = getApp();
import request from '../../utils/request.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
Page({
data: {
// 商品类型(和打手端用同一个接口)
shangpinleixing: [],
xuanzhongLeixingId: null,
// 搜索关键字(模糊搜索)
searchKeyword: '',
// 状态列表(含全部)
statusList: [
{ name: '全部', key: 'all', zhuangtaiList: [1, 2, 3, 4, 5, 6, 7, 8], color: '#333333' },
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
{ name: '退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' },
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' }
],
currentStatusKey: 'all',
// 每个状态独立的数据集
sjDingdanShuju: {
all: { list: [], page: 1, hasMore: true, isLoading: false },
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
},
currentList: [],
hasMore: true,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false,
defaultImg: '/images/default-order.png',
ossImageUrl: app.globalData.ossImageUrl || '',
// 待结算数量
pendingCount: 0,
},
onLoad() {
wx.setNavigationBarTitle({ title: '我的派单' });
this.loadShangpinLeixing();
this.registerNotificationComponent();
},
onShow() {
this.registerNotificationComponent();
this.loadPendingCount();
if (wx.getStorageSync('uid')) {
reconnectForRole('shangjia');
if (app.startImWhenReady) app.startImWhenReady();
}
if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) {
this.loadCurrentStatusOrders(true);
}
},
// 注册全局通知组件
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
// 🆕 加载商品类型(和打手端用同一个接口 /dingdan/dsqdhqddlx
async loadShangpinLeixing() {
try {
const res = await request({
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 || [];
if (!Array.isArray(list)) list = [];
const oss = this.data.ossImageUrl;
// 拼接图片完整URL
const processed = list.map(item => ({
...item,
full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http')
? oss + item.tupian_url
: (item.tupian_url || '/images/default-type.png')
}));
this.setData({
shangpinleixing: processed,
xuanzhongLeixingId: processed[0]?.id || null
});
// 初始加载全部状态订单
this.loadCurrentStatusOrders(true);
}
} catch (e) {
console.error('加载商品类型失败', e);
}
},
// 选择商品类型
selectLeixing(e) {
const id = e.currentTarget.dataset.id;
if (id === this.data.xuanzhongLeixingId) return;
this.setData({ xuanzhongLeixingId: id });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 搜索输入(防抖)
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value });
if (this._searchTimer) clearTimeout(this._searchTimer);
this._searchTimer = setTimeout(() => {
this.onSearchConfirm();
}, 500);
},
// 搜索确认
onSearchConfirm() {
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 清除搜索
clearSearch() {
this.setData({ searchKeyword: '' });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 切换状态Tab
switchStatus(e) {
const key = e.currentTarget.dataset.key;
if (key === this.data.currentStatusKey) return;
this.setData({ currentStatusKey: key });
const tabData = this.data.sjDingdanShuju[key];
// 如果该状态没有数据,则加载;否则只刷新视图
if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) {
this.loadCurrentStatusOrders(true);
} else {
this.refreshCurrentListView();
}
},
// 🆕 加载订单(核心方法,接口固定用商家订单接口 /dingdan/sjdingdanhq
async loadCurrentStatusOrders(isRefresh = false) {
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key];
if (tabData.isLoading) return;
const page = isRefresh ? 1 : tabData.page;
this.setData({
[`sjDingdanShuju.${key}.isLoading`]: true,
isLoading: true,
isLoadingMore: !isRefresh
});
try {
const statusItem = this.data.statusList.find(s => s.key === key);
const zhuangtaiList = statusItem.zhuangtaiList;
// 构建请求参数
const params = {
zhuangtai_list: zhuangtaiList,
page: page,
page_size: 5,
leixing_id: this.data.xuanzhongLeixingId, // 🆕 商品类型筛选
keyword: this.data.searchKeyword || undefined // 🆕 模糊搜索
};
const res = await request({
url: '/dingdan/sjdingdanhq', // 商家订单接口(和原来一样)
method: 'POST',
data: params,
header: { 'content-type': 'application/json' }
});
const code = res.data.code;
if (code === 200 || code === 0) {
const newList = res.data.data.list || [];
const hasMore = res.data.data.has_more || false;
// 处理数据:拼接图片、转换状态中文
const processed = newList.map(item => ({
...item,
zhuangtaiZh: this.getZhuangtaiZh(item.zhuangtai),
zhuangtaiColor: statusItem.color,
// 如果有商品图片则用商品图片,否则用默认图
tupian: item.tupian
? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian)
: this.data.defaultImg
}));
const currentList = this.data.sjDingdanShuju[key].list;
const updatedList = isRefresh ? processed : [...currentList, ...processed];
this.setData({
[`sjDingdanShuju.${key}.list`]: updatedList,
[`sjDingdanShuju.${key}.page`]: page,
[`sjDingdanShuju.${key}.hasMore`]: hasMore,
[`sjDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false
});
this.refreshCurrentListView();
} else {
throw new Error(res.data.msg || '加载失败');
}
} catch (err) {
console.error('加载订单失败', err);
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
this.setData({
[`sjDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false
});
this.refreshCurrentListView();
}
},
// 刷新当前视图
refreshCurrentListView() {
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key];
this.setData({
currentList: tabData.list,
hasMore: tabData.hasMore,
isLoading: tabData.isLoading
});
},
// 重置当前状态数据
resetCurrentStatusData() {
const key = this.data.currentStatusKey;
this.setData({
[`sjDingdanShuju.${key}.list`]: [],
[`sjDingdanShuju.${key}.page`]: 1,
[`sjDingdanShuju.${key}.hasMore`]: true,
[`sjDingdanShuju.${key}.isLoading`]: false
});
this.refreshCurrentListView();
},
// 🆕 加载待结算数量
async loadPendingCount() {
try {
const res = await request({
url: '/dingdan/sjdingdanhq',
method: 'POST',
data: {
zhuangtai_list: [8],
page: 1,
page_size: 1
}
});
if (res && res.data.code === 0) {
this.setData({ pendingCount: res.data.data.pending_count || 0 });
}
} catch (error) {
console.error('加载待结算数量失败:', error);
}
},
// 下拉刷新(由 scroll-view 触发)
onPullDownRefresh() {
if (this.data.isLoading) {
this.setData({ scrollViewRefreshing: false });
return;
}
this.setData({ scrollViewRefreshing: true });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 上拉加载更多(由 scroll-view 触发)
onReachBottom() {
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false);
},
// 🆕 手动点击"加载更多"按钮
onLoadMoreTap() {
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false);
},
// 状态码转中文
getZhuangtaiZh(zhuangtai) {
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() {}
});
// pages/merchant-orders/merchant-orders.js
const app = getApp();
import { createPage, request } from '../../utils/base-page.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getOrderStatusText } from '../../utils/api-helper.js';
Page(createPage({
data: {
// 商品类型(和打手端用同一个接口)
shangpinleixing: [],
xuanzhongLeixingId: null,
// 搜索关键字(模糊搜索)
searchKeyword: '',
// 状态列表(含全部)
statusList: [
{ name: '全部', key: 'all', zhuangtaiList: [1, 2, 3, 4, 5, 6, 7, 8], color: '#333333' },
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
{ name: '退款', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
{ name: '退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' },
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' },
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' }
],
currentStatusKey: 'all',
// 每个状态独立的数据集
sjDingdanShuju: {
all: { list: [], page: 1, hasMore: true, isLoading: false },
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
},
currentList: [],
hasMore: true,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false,
defaultImg: '/images/default-order.png',
ossImageUrl: app.globalData.ossImageUrl || '',
// 待结算数量
pendingCount: 0,
},
onLoad() {
wx.setNavigationBarTitle({ title: '我的派单' });
this.loadShangpinLeixing();
this.registerNotificationComponent();
},
onShow() {
this.registerNotificationComponent();
this.loadPendingCount();
if (wx.getStorageSync('uid')) {
reconnectForRole('shangjia');
if (app.startImWhenReady) app.startImWhenReady();
}
if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) {
this.loadCurrentStatusOrders(true);
}
},
// 🆕 加载商品类型(和打手端用同一个接口 /dingdan/dsqdhqddlx
async loadShangpinLeixing() {
try {
const res = await request({
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 || [];
if (!Array.isArray(list)) list = [];
const oss = this.data.ossImageUrl;
// 拼接图片完整URL
const processed = list.map(item => ({
...item,
full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http')
? oss + item.tupian_url
: (item.tupian_url || '/images/default-type.png')
}));
this.setData({
shangpinleixing: processed,
xuanzhongLeixingId: processed[0]?.id || null
});
// 初始加载全部状态订单
this.loadCurrentStatusOrders(true);
}
} catch (e) {
console.error('加载商品类型失败', e);
}
},
// 选择商品类型
selectLeixing(e) {
const id = e.currentTarget.dataset.id;
if (id === this.data.xuanzhongLeixingId) return;
this.setData({ xuanzhongLeixingId: id });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 搜索输入(防抖)
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value });
if (this._searchTimer) clearTimeout(this._searchTimer);
this._searchTimer = setTimeout(() => {
this.onSearchConfirm();
}, 500);
},
// 搜索确认
onSearchConfirm() {
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 清除搜索
clearSearch() {
this.setData({ searchKeyword: '' });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 切换状态Tab
switchStatus(e) {
const key = e.currentTarget.dataset.key;
if (key === this.data.currentStatusKey) return;
this.setData({ currentStatusKey: key });
const tabData = this.data.sjDingdanShuju[key];
// 如果该状态没有数据,则加载;否则只刷新视图
if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) {
this.loadCurrentStatusOrders(true);
} else {
this.refreshCurrentListView();
}
},
// 🆕 加载订单(核心方法,接口固定用商家订单接口 /dingdan/sjdingdanhq
async loadCurrentStatusOrders(isRefresh = false) {
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key];
if (tabData.isLoading) return;
const page = isRefresh ? 1 : tabData.page;
this.setData({
[`sjDingdanShuju.${key}.isLoading`]: true,
isLoading: true,
isLoadingMore: !isRefresh
});
try {
const statusItem = this.data.statusList.find(s => s.key === key);
const zhuangtaiList = statusItem.zhuangtaiList;
// 构建请求参数
const params = {
zhuangtai_list: zhuangtaiList,
page: page,
page_size: 5,
leixing_id: this.data.xuanzhongLeixingId, // 🆕 商品类型筛选
keyword: this.data.searchKeyword || undefined // 🆕 模糊搜索
};
const res = await request({
url: '/dingdan/sjdingdanhq', // 商家订单接口(和原来一样)
method: 'POST',
data: params,
header: { 'content-type': 'application/json' }
});
const code = res.data.code;
if (code === 200 || code === 0) {
const newList = res.data.data.list || [];
const hasMore = res.data.data.has_more || false;
// 处理数据:拼接图片、转换状态中文
const processed = newList.map(item => ({
...item,
zhuangtaiZh: getOrderStatusText(item.zhuangtai),
zhuangtaiColor: statusItem.color,
// 如果有商品图片则用商品图片,否则用默认图
tupian: item.tupian
? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian)
: this.data.defaultImg
}));
const currentList = this.data.sjDingdanShuju[key].list;
const updatedList = isRefresh ? processed : [...currentList, ...processed];
this.setData({
[`sjDingdanShuju.${key}.list`]: updatedList,
[`sjDingdanShuju.${key}.page`]: page,
[`sjDingdanShuju.${key}.hasMore`]: hasMore,
[`sjDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false
});
this.refreshCurrentListView();
} else {
throw new Error(res.data.msg || '加载失败');
}
} catch (err) {
console.error('加载订单失败', err);
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
this.setData({
[`sjDingdanShuju.${key}.isLoading`]: false,
isLoading: false,
isLoadingMore: false,
scrollViewRefreshing: false
});
this.refreshCurrentListView();
}
},
// 刷新当前视图
refreshCurrentListView() {
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key];
this.setData({
currentList: tabData.list,
hasMore: tabData.hasMore,
isLoading: tabData.isLoading
});
},
// 重置当前状态数据
resetCurrentStatusData() {
const key = this.data.currentStatusKey;
this.setData({
[`sjDingdanShuju.${key}.list`]: [],
[`sjDingdanShuju.${key}.page`]: 1,
[`sjDingdanShuju.${key}.hasMore`]: true,
[`sjDingdanShuju.${key}.isLoading`]: false
});
this.refreshCurrentListView();
},
// 🆕 加载待结算数量
async loadPendingCount() {
try {
const res = await request({
url: '/dingdan/sjdingdanhq',
method: 'POST',
data: {
zhuangtai_list: [8],
page: 1,
page_size: 1
}
});
if (res && res.data.code === 0) {
this.setData({ pendingCount: res.data.data.pending_count || 0 });
}
} catch (error) {
console.error('加载待结算数量失败:', error);
}
},
// 下拉刷新(由 scroll-view 触发)
onPullDownRefresh() {
if (this.data.isLoading) {
this.setData({ scrollViewRefreshing: false });
return;
}
this.setData({ scrollViewRefreshing: true });
this.resetCurrentStatusData();
this.loadCurrentStatusOrders(true);
},
// 上拉加载更多(由 scroll-view 触发)
onReachBottom() {
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false);
},
// 🆕 手动点击"加载更多"按钮
onLoadMoreTap() {
const key = this.data.currentStatusKey;
const tabData = this.data.sjDingdanShuju[key];
if (tabData.isLoading || !tabData.hasMore) return;
this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 });
this.loadCurrentStatusOrders(false);
},
// 跳转订单详情
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() {}
}))

View File

@@ -1,235 +1,205 @@
<!-- pages/shangjia-paihang/shangjia-paihang.wxml -->
<view class="shangjia-paihang-page">
<!-- pages/merchant-rank/merchant-rank.wxml -->
<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="stream stream-1"></view>
<view class="stream stream-2"></view>
<view class="stream stream-3"></view>
<view class="rank-data-streams">
<view class="rank-stream rank-stream--1"></view>
<view class="rank-stream rank-stream--2"></view>
<view class="rank-stream rank-stream--3"></view>
</view>
<!-- 浮动粒子 -->
<view class="floating-particles">
<view class="particle" wx:for="{{12}}" wx:key="index"></view>
</view>
<view class="rank-particle" wx:for="{{12}}" wx:key="index"></view>
</view>
<!-- 页面头部 -->
<view class="page-header">
<view class="rank-page-header">
<!-- 标题 -->
<view class="title-container">
<text class="main-title">商家排行榜</text>
<view class="title-line"></view>
<text class="sub-title">SHANGJIA RANKING</text>
<view class="rank-title-container">
<text class="rank-main-title">商家排行榜</text>
<view class="rank-title-line"></view>
<text class="rank-sub-title">SHANGJIA RANKING</text>
</view>
<!-- 切换按钮 -->
<view class="toggle-container">
<view class="toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view
class="toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
<view class="rank-toggle-container">
<view class="rank-toggle-bg" style="transform: translateX({{paihangLeixing == 1 ? '0' : '100%'}});"></view>
<view
class="rank-toggle-btn {{paihangLeixing == 1 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="1"
bindtap="qiehuangPaihangLeixing"
>
<text class="toggle-text">今日流水</text>
<view class="toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
<text class="rank-toggle-text">今日流水</text>
<view class="rank-toggle-line {{paihangLeixing == 1 ? 'active' : ''}}"></view>
</view>
<view
class="toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
<view
class="rank-toggle-btn {{paihangLeixing == 2 ? 'active' : ''}} {{btnFangdou ? 'disabled' : ''}}"
data-leixing="2"
bindtap="qiehuangPaihangLeixing"
>
<text class="toggle-text">今月流水</text>
<view class="toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
<text class="rank-toggle-text">今月流水</text>
<view class="rank-toggle-line {{paihangLeixing == 2 ? 'active' : ''}}"></view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view wx:if="{{jiazaiZhuangtai}}" class="loading-state">
<view class="loading-content">
<view class="loading-spinner">
<view class="spinner-outer"></view>
<view class="spinner-inner"></view>
<view class="spinner-core"></view>
</view>
<text class="loading-text">数据加载中</text>
<text class="loading-sub">Connecting to server...</text>
<view wx:if="{{jiazaiZhuangtai}}" class="rank-loading-state">
<view class="rank-loading-spinner">
<view class="rank-spinner-outer"></view>
<view class="rank-spinner-inner"></view>
<view class="rank-spinner-core"></view>
</view>
<text class="rank-loading-text">数据加载中</text>
<text class="rank-loading-sub">Connecting to server...</text>
</view>
<!-- 排行榜内容 -->
<view wx:else class="ranking-content">
<view wx:else class="rank-content">
<!-- 虚拟数据提示 -->
<view wx:if="{{!xianshiZhenshiShuju}}" class="demo-notice">
<view class="notice-icon">📡</view>
<view class="notice-content">
<text class="notice-title">排行榜数据准备中</text>
<text class="notice-desc">当前显示为模拟数据,实时排名即将开放</text>
<view wx:if="{{!xianshiZhenshiShuju}}" class="rank-demo-notice">
<view class="rank-notice-icon">📡</view>
<view class="rank-notice-content">
<text class="rank-notice-title">排行榜数据准备中</text>
<text class="rank-notice-desc">当前显示为模拟数据,实时排名即将开放</text>
</view>
<view class="notice-glow"></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 : ''}}"
data-index="1" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-silver">
<view class="podium-glow"></view>
<text class="podium-number">2</text>
<view class="rank-podium-base rank-podium-silver">
<text class="rank-podium-number">2</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-silver">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-silver"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
</view>
<view class="rank-avatar-frame rank-frame-silver">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[1] ? qiansanMing[1].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-silver"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
</view>
<view class="rank-badge badge-silver">2</view>
<view class="rank-badge rank-badge-silver">2</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[1] ? qiansanMing[1].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[1] ? qiansanMing[1].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">流水:</text>
<view class="income-value">
<text class="currency">¥</text>
<text class="amount {{qiansanMing[1] ? qiansanMing[1].jineClass : ''}}">{{qiansanMing[1] ? qiansanMing[1].jine : '0.00'}}</text>
<view class="rank-income-row">
<text class="rank-income-label">流水:</text>
<view class="rank-income-value">
<text class="rank-currency">¥</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 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 : ''}}"
data-index="0" data-type="top3" bindtap="handleCardTap">
<!-- 皇冠 -->
<view class="crown-container" animation="{{crownAnimation}}">
<view class="crown">
<text class="crown-icon">👑</text>
<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 class="rank-crown-container" animation="{{crownAnimation}}">
<text class="rank-crown-icon">👑</text>
<view class="rank-crown-glow"></view>
</view>
<view class="podium-base podium-gold">
<view class="podium-glow"></view>
<text class="podium-number">1</text>
<view class="rank-podium-base rank-podium-gold">
<text class="rank-podium-number">1</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-gold">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-gold"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
<view class="sparkle s4"></view>
</view>
<view class="rank-avatar-frame rank-frame-gold">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[0] ? qiansanMing[0].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-gold"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
<view class="rank-sparkle" style="bottom:15%;right:15%;animation-delay:1.5s"></view>
</view>
<view class="rank-badge badge-gold">1</view>
<view class="rank-badge rank-badge-gold">1</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[0] ? qiansanMing[0].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[0] ? qiansanMing[0].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">流水:</text>
<view class="income-value">
<text class="currency">¥</text>
<text class="amount {{qiansanMing[0] ? qiansanMing[0].jineClass : ''}}">{{qiansanMing[0] ? qiansanMing[0].jine : '0.00'}}</text>
<view class="rank-income-row">
<text class="rank-income-label">流水:</text>
<view class="rank-income-value">
<text class="rank-currency">¥</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 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 : ''}}"
data-index="2" data-type="top3" bindtap="handleCardTap">
<view class="podium-base podium-bronze">
<view class="podium-glow"></view>
<text class="podium-number">3</text>
<view class="rank-podium-base rank-podium-bronze">
<text class="rank-podium-number">3</text>
</view>
<view class="rank-content">
<!-- 头像容器 -->
<view class="avatar-frame frame-bronze">
<view class="avatar-container">
<image class="avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="avatar-halo halo-bronze"></view>
<view class="avatar-sparkles">
<view class="sparkle s1"></view>
<view class="sparkle s2"></view>
<view class="sparkle s3"></view>
</view>
<view class="rank-avatar-frame rank-frame-bronze">
<view class="rank-avatar-container">
<image class="rank-avatar-image" src="{{qiansanMing[2] ? qiansanMing[2].full_touxiang : ''}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo rank-halo-bronze"></view>
<view class="rank-sparkle" style="top:15%;left:15%;animation-delay:0s"></view>
<view class="rank-sparkle" style="top:25%;right:25%;animation-delay:0.5s"></view>
<view class="rank-sparkle" style="bottom:35%;left:35%;animation-delay:1s"></view>
</view>
<view class="rank-badge badge-bronze">3</view>
<view class="rank-badge rank-badge-bronze">3</view>
</view>
<!-- 用户信息 -->
<view class="user-info-card">
<view class="info-header">
<text class="user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
</view>
<view class="info-body">
<view class="uid-row">
<text class="uid-label">ID:</text>
<text class="uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
<view class="rank-user-info-card">
<text class="rank-user-name">{{qiansanMing[2] ? qiansanMing[2].nicheng : '等待上榜'}}</text>
<view class="rank-info-body">
<view class="rank-uid-row">
<text class="rank-uid-label">ID:</text>
<text class="rank-uid-value">{{qiansanMing[2] ? qiansanMing[2].uid : '----'}}</text>
</view>
<view class="income-row">
<text class="income-label">流水:</text>
<view class="income-value">
<text class="currency">¥</text>
<text class="amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0.00'}}</text>
<view class="rank-income-row">
<text class="rank-income-label">流水:</text>
<view class="rank-income-value">
<text class="rank-currency">¥</text>
<text class="rank-amount {{qiansanMing[2] ? qiansanMing[2].jineClass : ''}}">{{qiansanMing[2] ? qiansanMing[2].jine : '0.00'}}</text>
</view>
</view>
</view>
@@ -237,54 +207,50 @@
</view>
</view>
</view>
<!-- ========== 其他排名列表 ========== -->
<view class="other-ranks-section" wx:if="{{paihangShuju.length > 0}}">
<view class="section-title">
<view class="title-line"></view>
<text class="title-text">其他排名</text>
<view class="title-line"></view>
<view wx:if="{{paihangShuju.length > 0}}">
<view class="rank-section-title">
<view class="rank-section-line"></view>
<text class="rank-section-text">其他排名</text>
<view class="rank-section-line"></view>
</view>
<view class="rank-list">
<view class="rank-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
wx:for="{{paihangShuju}}" wx:key="index"
animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}"
data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
<view class="rank-number">
<text class="number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<view class="number-glow"></view>
</view>
<view class="rank-avatar">
<image class="rank-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-avatar-halo"></view>
</view>
<view class="rank-info">
<text class="rank-name">{{item.nicheng}}</text>
<text class="rank-uid">UID: {{item.uid}}</text>
</view>
<view class="rank-income">
<text class="income-currency">¥</text>
<text class="income-amount {{item.jineClass}}">{{item.jine}}</text>
<view class="income-glow"></view>
</view>
<view class="rank-hover"></view>
<view class="rank-list-item {{flashCardIndex == index && flashCardType == 'normal' ? 'flash' : ''}}"
wx:for="{{paihangShuju}}" wx:key="index"
animation="{{flashCardIndex == index && flashCardType == 'normal' ? flashAnimation : ''}}"
data-index="{{index}}" data-type="normal" bindtap="handleCardTap">
<view class="rank-number">
<text class="rank-number-text">{{xianshiZhenshiShuju ? index + 4 : index + 1}}</text>
<view class="rank-number-glow"></view>
</view>
<view class="rank-list-avatar">
<image class="rank-list-avatar-img" src="{{item.full_touxiang}}" mode="aspectFill" binderror="tupianJiazaiShibai"></image>
<view class="rank-list-avatar-halo"></view>
</view>
<view class="rank-list-info">
<text class="rank-list-name">{{item.nicheng}}</text>
<text class="rank-list-uid">UID: {{item.uid}}</text>
</view>
<view class="rank-list-income">
<text class="rank-income-currency">¥</text>
<text class="rank-income-amount {{item.jineClass}}">{{item.jine}}</text>
<view class="rank-income-glow"></view>
</view>
</view>
</view>
<!-- 空状态 -->
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="empty-state">
<view class="empty-icon">🏆</view>
<text class="empty-title">暂无排名数据</text>
<text class="empty-desc">成为第一个登榜的商家吧</text>
<view wx:if="{{xianshiZhenshiShuju && paihangShuju.length === 0 && qiansanMing.length === 0}}" class="rank-empty-state">
<view class="rank-empty-icon">🏆</view>
<text class="rank-empty-title">暂无排名数据</text>
<text class="rank-empty-desc">成为第一个登榜的商家吧</text>
</view>
</view>
<global-notification id="global-notification" />
</view>
</view>

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,11 @@
// pages/shangjiazhongxin/shangjiazhongxin.js
// pages/shangjiazhongxin/shangjiiazhongxin.js
import request from '../../utils/request.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 { createPage, ensureRoleOnCenterPage, lockPrimaryRole, parseSceneOptions } from '../../utils/base-page.js';
import { ensureLogin } from '../../utils/login.js';
const app = getApp();
Page({
Page(createPage({
data: {
// 图片资源
imgUrls: {
@@ -35,14 +25,13 @@ Page({
iconRecharge: '',
iconWithdraw: '',
iconRefresh: '',
iconCopy: '',
avatarFrame: '',
iconClear: '',
},
iconCopy: '',
avatarFrame: '',
iconClear: '',
},
// 商家状态
isShangjia: false,
isLoading: false,
isAutoRegistering: false,
// 用户基础信息
@@ -62,52 +51,57 @@ Page({
// 交互控制
inviteCode: '',
lastRefreshTime: 0,
canRefresh: true,
},
onLoad(options) {
this.initImageUrls();
this.checkShangjiaStatus();
const parsed = parseSceneOptions(options);
this.setupImageUrls();
this.checkRoleStatus();
const { inviteCode } = options || {};
const inviteCode = parsed.inviteCode;
if (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) {
app.globalData.hasShownPopupOnColdStart = true;
setTimeout(() => popupService.checkAndShow(this, 'shangjiaduan'), 300);
}
},
onReady() {
if (isCenterPageActive(this, 'isShangjia', 'shangjiastatus')) {
ensureRoleOnCenterPage(this, 'shangjia');
}
this.checkColdStartPopup('shangjiaduan');
},
onShow() {
this.registerNotificationComponent();
if (isCenterPageActive(this, 'isShangjia', 'shangjiastatus')) {
if (!this.data.isShangjia) this.setData({ isShangjia: true });
if (this.data.isShangjia) {
ensureRoleOnCenterPage(this, 'shangjia');
this.refreshShangjiaInfo();
}
},
onHide() {
const popupComp = this.selectComponent('#popupNotice');
popupComp?.cleanup?.();
},
// 图片路径初始化
initImageUrls() {
setupImageUrls() {
const ossBase = app.globalData.ossImageUrl || '';
const imgDir = `${ossBase}beijing/shangjiaduan/`;
this.setData({
imgUrls: {
imgUrls: this.initImageUrls({
pageBg: `${imgDir}page_bg.png`,
avatarCardBg: `${imgDir}avatar_card_bg.png`,
assetCardBg: `${imgDir}asset_card_bg.png`,
@@ -128,16 +122,16 @@ Page({
avatarFrame: `${imgDir}avatar_frame.png`,
iconClear: `${ossBase}beijing/tubiao/grzx_qingchu.jpg`,
iconSwitch: '/images/_exit.png',
}
})
});
},
// 商家状态检查
checkShangjiaStatus() {
// 商家状态检查(覆盖基类 checkRoleStatus增加 getShangjiaInfo 调用)
checkRoleStatus() {
try {
const shangjiastatus = wx.getStorageSync('shangjiastatus');
const uid = wx.getStorageSync('uid');
if (isRoleStatusActive(shangjiastatus)) {
if (shangjiastatus) {
this.setData({ isShangjia: true, uid: uid || '' });
this.loadAvatar();
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() {
this.setData({ isLoading: true });
try {
const res = await request({ url: '/yonghu/shangjiaxinxi', method: 'POST' });
if (res?.data?.code === 200) {
const data = res.data.data;
await this.loadData({
url: '/yonghu/shangjiaxinxi',
method: 'POST',
successCode: 200,
errorMsg: '获取商家信息失败',
onSuccess: (data) => {
// 更新全局缓存
app.globalData.shangjia = data;
wx.setStorageSync('shangjiastatus', 1);
const chenghaoList = data.chenghao_list || [];
// 字段映射
this.setData({
nicheng: data.nicheng || '',
sjyue: data.sjyue || '0.00',
@@ -189,133 +171,46 @@ Page({
chenghaoList: chenghaoList,
isLoading: false,
});
if (this.data.isAutoRegistering) {
this.setData({ isAutoRegistering: false });
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() {
const now = Date.now();
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');
}
}
this.throttledRefresh(() => this.getShangjiaInfo());
},
async onRegister() {
const { inviteCode } = this.data;
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: '/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 || [];
await this.registerWithInviteCode({
inviteCode,
apiUrl: '/yonghu/shangjiahuce',
role: 'shangjia',
statusKey: 'shangjiastatus',
successMsg: '商家注册成功',
onSuccess: (userData) => {
app.globalData.shangjia = userData;
const chenghaoList = userData.chenghao_list || [];
this.setData({
isShangjia: true,
nicheng: data.nicheng || '',
sjyue: data.sjyue || '0.00',
fabu: data.fabu || 0,
tuikuan: data.tuikuan || 0,
jinriliushui: data.jinriliushui || '0.00',
jinyueliushui: data.jinyueliushui || '0.00',
jinridingdan: data.jinridingdan || 0,
nicheng: userData.nicheng || '',
sjyue: userData.sjyue || '0.00',
fabu: userData.fabu || 0,
tuikuan: userData.tuikuan || 0,
jinriliushui: userData.jinriliushui || '0.00',
jinyueliushui: userData.jinyueliushui || '0.00',
jinridingdan: userData.jinridingdan || 0,
chenghaoList: chenghaoList,
isLoading: false,
isAutoRegistering: false,
});
this.loadAvatar();
ensureRoleOnCenterPage(this, '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' }); },
goToJiSuPaiDan() {
goToJiSuPaiDan() {
wx.vibrateShort({ type: 'medium' });
wx.navigateTo({ url: '/pages/express-order/express-order' });
},
@@ -336,33 +231,12 @@ Page({
goToWithdraw() { wx.navigateTo({ url: '/pages/withdraw/withdraw' }); },
goToRanking() { wx.navigateTo({ url: '/pages/fighter-rank/fighter-rank?type=shangjia' }); },
goToMessages() { wx.navigateTo({ url: '/pages/merchant-msg/merchant-msg' }); },
goToKefu() {
const { kefuConfig } = app.globalData;
if (kefuConfig?.link && kefuConfig?.enterpriseId) {
wx.openCustomerServiceChat({
extInfo: { url: kefuConfig.link },
corpId: kefuConfig.enterpriseId,
fail: () => wx.showToast({ title: '客服暂不可用', icon: 'none' })
});
} else {
wx.showToast({ title: '客服配置未加载', icon: 'none' });
}
}, {
roleConfig: {
role: 'shangjia',
statusKey: 'shangjiastatus',
dataFlag: 'isShangjia',
pageId: 'shangjiaduan',
apiUrl: '/yonghu/shangjiaxinxi',
},
clearCache() {
wx.showModal({
title: '清除缓存',
content: '将清除所有登录与身份数据,并回到点单端,确定继续?',
confirmText: '确定',
cancelText: '取消',
success: (r) => {
if (r.confirm) clearCacheAndEnterNormal(this);
},
});
},
switchToNormal() {
lockPrimaryRole('normal');
wx.reLaunch({ url: '/pages/mine/mine' });
},
});
}));

View File

@@ -1,403 +1,394 @@
// pages/messages/messages.js
const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js';
import { getLocalImUserId } from '../../utils/im-user.js';
import { normalizeConversationsList } from '../../utils/conversation-display.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getPrimaryRole } from '../../utils/primary-role.js';
const { jianquanxian } = require('../../utils/imAuth/jianquanxian');
Page({
data: {
allConversations: [],
filteredConversations: [],
activeTab: 0,
tabUnread: [0, 0, 0],
displayCount: 10,
searchKeyword: '',
actionPopup: { visible: false, conversation: null },
currentUser: null,
notificationMuted: false,
scrollHeight: 0
},
_permissionSeq: 0,
_peerProfileCache: {},
onLoad() {
this.calculateScrollHeight();
const muted = wx.getStorageSync('notificationMuted') || false;
this.setData({ notificationMuted: muted });
},
onShow() {
if (!this.checkLoginStatus()) return;
this.registerNotificationComponent();
this.checkPermissionAndAutoConnect();
},
onHide() {},
onUnload() {},
// ========== ==== ================ ==== ================ 鉴权检查 ==========
async checkPermissionAndAutoConnect() {
const seq = ++this._permissionSeq;
let quanxian;
try {
quanxian = await jianquanxian(app);
} catch (e) {
return;
}
if (seq !== this._permissionSeq) return;
if (!quanxian.allowed) {
const currentRole = app.globalData.currentRole || 'normal';
let content = '您没有权限使用消息功能';
if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能';
wx.showModal({
title: '权限不足',
content,
showCancel: false,
confirmText: '我知道了'
});
return;
}
this.autoConnect();
},
// 登录状态检查
checkLoginStatus() {
const uid = wx.getStorageSync('uid');
if (!uid) {
wx.showToast({ title: '请先登录', icon: 'none' });
setTimeout(() => { wx.redirectTo({ url: '/pages/mine/mine' }); }, 1500);
return false;
}
let currentUser = app.globalData.currentUser;
if (!currentUser) {
currentUser = {
id: getLocalImUserId(app) || uid,
name: wx.getStorageSync('nicheng') || app.globalData.dashouNicheng || app.globalData.nicheng || ('用户' + uid.substring(0, 6)),
avatar: resolveAvatarFromStorage(app),
};
app.globalData.currentUser = currentUser;
}
this.setData({ currentUser });
return true;
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification(),
};
}
},
autoConnect() {
const targetUserId = getLocalImUserId(app);
if (!targetUserId) return;
if (app.ensureConnection) app.ensureConnection();
reconnectForRole(getPrimaryRole(app));
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
if (currentUserId === targetUserId) {
this.setupConversationListener();
this.loadConversations();
return;
}
try {
wx.goEasy.disconnect();
} catch (e) {}
}
this.connectGoEasy(targetUserId);
},
connectGoEasy(userId) {
const { currentUser } = this.data;
if (!currentUser) return;
wx.goEasy.connect({
id: userId,
data: {
name: currentUser.name,
avatar: resolveAvatarUrl(currentUser.avatar, app),
},
onSuccess: () => {
try {
wx.setStorageSync('savedGoEasyConnection', JSON.stringify({
userId,
identityType: app.globalData.currentRole,
lastConnectTime: Date.now(),
status: 'connected',
}));
} catch (e) {}
this.setupConversationListener();
this.loadConversations();
if (app.registerNotification) {
}
},
onFailed: (error) => {
if (error.code === 408) {
this.setupConversationListener();
this.loadConversations();
} else {
wx.showToast({ title: '连接失败,请重试', icon: 'none' });
}
},
});
},
setupConversationListener() {
if (!wx.goEasy || !wx.goEasy.im) return;
if (this.conversationsUpdatedListener) {
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
}
this.conversationsUpdatedListener = (result) => {
const content = result?.content || result;
this.renderConversations(content);
};
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
},
loadConversations() {
if (!wx.goEasy || !wx.goEasy.im) return;
wx.goEasy.im.latestConversations({
onSuccess: (result) => {
const content = result?.content || result;
this.renderConversations(content);
},
onFailed: () => {
wx.showToast({ title: '获取会话失败', icon: 'none' });
},
});
},
// 会话渲染与排序
renderConversations(content) {
const conversations = (content && content.conversations) ? content.conversations : [];
if (!conversations.length) {
this.setData({ allConversations: [] }, () => this.applyFilter());
return;
}
let list = normalizeConversationsList(conversations, app, this._peerProfileCache);
list.forEach(item => {
if (!item.lastMessage) {
item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 };
}
if (item.lastMessage.timestamp) {
item.lastMessage.date = formatDate(item.lastMessage.timestamp);
}
});
list.sort((a, b) => {
if (a.top && !b.top) return -1;
if (!a.top && b.top) return 1;
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 tb = (b.lastMessage && b.lastMessage.timestamp) || 0;
return tb - ta;
});
this.setData({ allConversations: list, displayCount: 10 }, () => {
this.applyFilter();
});
const unreadTotal = content.unreadTotal !== undefined
? content.unreadTotal
: 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) : '' });
}
// 重新订阅群组
this.subscribeGroupsIfNeeded(list);
},
/**
* 群组订阅,每次获取群组列表时直接订阅保证消息不丢失
*/
subscribeGroupsIfNeeded(conversations) {
if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return;
const groupIds = conversations
.filter(c => c.type === 'group' && c.groupId)
.map(c => c.groupId);
if (groupIds.length === 0) return;
// 不再判断缓存,直接订阅当前所有群组
wx.goEasy.im.subscribeGroup({
groupIds: groupIds,
onSuccess: () => {
// 可选的日志输出,不影响功能
// console.log('群组订阅成功', groupIds);
},
onFailed: (error) => {
console.error('群组订阅失败:', error);
}
});
},
applyFilter() {
const { allConversations, activeTab, searchKeyword } = this.data;
let filtered = [];
if (activeTab === 0) {
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');
}
if (searchKeyword.trim()) {
const kw = searchKeyword.trim().toLowerCase();
filtered = filtered.filter(c => {
const name = (c.data && c.data.name || '').toLowerCase();
const orderId = (c.data && c.data.orderId || '').toLowerCase();
const id = (c.userId || c.groupId || '').toLowerCase();
return name.includes(kw) || id.includes(kw) || orderId.includes(kw);
});
}
const tabUnread = [0, 0, 0];
allConversations.forEach(c => {
if (c.type === 'group' && c.data && c.data.orderId) tabUnread[0] += c.unread || 0;
else if (c.type === 'private') tabUnread[1] += c.unread || 0;
else if (c.type === 'cs') tabUnread[2] += c.unread || 0;
});
this.setData({ filteredConversations: filtered, tabUnread });
},
switchTab(e) {
const index = parseInt(e.currentTarget.dataset.index);
this.setData({ activeTab: index, displayCount: 10, searchKeyword: '' }, () => this.applyFilter());
},
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter());
},
onScrollToLower() {
const { displayCount, filteredConversations } = this.data;
if (displayCount < filteredConversations.length) {
this.setData({ displayCount: Math.min(displayCount + 10, filteredConversations.length) });
}
},
onRefresh() {
this.loadConversations();
},
chat(e) {
const conversation = e.currentTarget.dataset.conversation;
if (!conversation) return;
if (conversation.type === 'private') {
const param = {
toUserId: conversation.userId,
toName: conversation.data.name || '用户',
toAvatar: resolveAvatarUrl(conversation.data.avatar, app),
};
wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'group') {
const param = {
groupId: conversation.groupId,
orderId: conversation.data.orderId || '',
groupName: conversation.data.name,
groupAvatar: conversation.data.avatar,
isCross: conversation.data.isCross || 0
};
wx.navigateTo({ url: '/pages/group-chat/group-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)) });
}
},
chatKefu() {
const param = { teamId: 'support_team' };
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 });
},
closeMask() {
this.setData({ ['actionPopup.visible']: false });
},
topConversation() {
let conversation = this.data.actionPopup.conversation;
if (!conversation) return;
const top = !conversation.top;
wx.showLoading({ title: '处理中...' });
if (conversation.type === 'private') {
wx.goEasy.im.topPrivateConversation({
userId: conversation.userId, top,
onSuccess: () => {
wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
},
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
});
} else {
wx.goEasy.im.topGroupConversation({
groupId: conversation.groupId, top,
onSuccess: () => {
wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
},
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
});
}
this.closeMask();
},
removeConversation() {
let conversation = this.data.actionPopup.conversation;
if (!conversation) return;
wx.showLoading({ title: '删除中...' });
wx.goEasy.im.removeConversation({
conversation: conversation,
onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: '已删除' }); },
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); }
});
this.closeMask();
},
toggleNotification() {
const newMuted = !this.data.notificationMuted;
this.setData({ notificationMuted: newMuted });
wx.setStorageSync('notificationMuted', newMuted);
wx.showToast({ title: newMuted ? '已静音' : '已开启通知', icon: 'none' });
},
calculateScrollHeight() {
const systemInfo = wx.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const windowWidth = systemInfo.windowWidth;
const rpxRatio = 750 / windowWidth;
const headerHeight = 150 / rpxRatio;
const tabBarHeight = 130 / rpxRatio;
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 });
}
});
// pages/messages/messages.js
const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js';
import { getLocalImUserId } from '../../utils/im-user.js';
import { normalizeConversationsList } from '../../utils/conversation-display.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getPrimaryRole } from '../../utils/primary-role.js';
import { createPage } from '../../utils/base-page.js';
const { jianquanxian } = require('../../utils/imAuth/jianquanxian');
Page(createPage({
data: {
allConversations: [],
filteredConversations: [],
activeTab: 0,
tabUnread: [0, 0, 0],
displayCount: 10,
searchKeyword: '',
actionPopup: { visible: false, conversation: null },
currentUser: null,
notificationMuted: false,
scrollHeight: 0
},
_permissionSeq: 0,
_peerProfileCache: {},
onLoad() {
this.calculateScrollHeight();
const muted = wx.getStorageSync('notificationMuted') || false;
this.setData({ notificationMuted: muted });
},
onShow() {
if (!this.checkLoginStatus()) return;
this.registerNotificationComponent();
this.checkPermissionAndAutoConnect();
},
onHide() {},
onUnload() {},
// ========== ==== ================ ==== ================ 鉴权检查 ==========
async checkPermissionAndAutoConnect() {
const seq = ++this._permissionSeq;
let quanxian;
try {
quanxian = await jianquanxian(app);
} catch (e) {
return;
}
if (seq !== this._permissionSeq) return;
if (!quanxian.allowed) {
const currentRole = app.globalData.currentRole || 'normal';
let content = '您没有权限使用消息功能';
if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能';
wx.showModal({
title: '权限不足',
content,
showCancel: false,
confirmText: '我知道了'
});
return;
}
this.autoConnect();
},
// 登录状态检查
checkLoginStatus() {
const uid = wx.getStorageSync('uid');
if (!uid) {
wx.showToast({ title: '请先登录', icon: 'none' });
setTimeout(() => { wx.redirectTo({ url: '/pages/mine/mine' }); }, 1500);
return false;
}
let currentUser = app.globalData.currentUser;
if (!currentUser) {
currentUser = {
id: getLocalImUserId(app) || uid,
name: wx.getStorageSync('nicheng') || app.globalData.dashouNicheng || app.globalData.nicheng || ('用户' + uid.substring(0, 6)),
avatar: resolveAvatarFromStorage(app),
};
app.globalData.currentUser = currentUser;
}
this.setData({ currentUser });
return true;
},
autoConnect() {
const targetUserId = getLocalImUserId(app);
if (!targetUserId) return;
if (app.ensureConnection) app.ensureConnection();
reconnectForRole(getPrimaryRole(app));
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
if (currentUserId === targetUserId) {
this.setupConversationListener();
this.loadConversations();
return;
}
try {
wx.goEasy.disconnect();
} catch (e) {}
}
this.connectGoEasy(targetUserId);
},
connectGoEasy(userId) {
const { currentUser } = this.data;
if (!currentUser) return;
wx.goEasy.connect({
id: userId,
data: {
name: currentUser.name,
avatar: resolveAvatarUrl(currentUser.avatar, app),
},
onSuccess: () => {
try {
wx.setStorageSync('savedGoEasyConnection', JSON.stringify({
userId,
identityType: app.globalData.currentRole,
lastConnectTime: Date.now(),
status: 'connected',
}));
} catch (e) {}
this.setupConversationListener();
this.loadConversations();
if (app.registerNotification) {
}
},
onFailed: (error) => {
if (error.code === 408) {
this.setupConversationListener();
this.loadConversations();
} else {
wx.showToast({ title: '连接失败,请重试', icon: 'none' });
}
},
});
},
setupConversationListener() {
if (!wx.goEasy || !wx.goEasy.im) return;
if (this.conversationsUpdatedListener) {
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
}
this.conversationsUpdatedListener = (result) => {
const content = result?.content || result;
this.renderConversations(content);
};
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
},
loadConversations() {
if (!wx.goEasy || !wx.goEasy.im) return;
wx.goEasy.im.latestConversations({
onSuccess: (result) => {
const content = result?.content || result;
this.renderConversations(content);
},
onFailed: () => {
wx.showToast({ title: '获取会话失败', icon: 'none' });
},
});
},
// 会话渲染与排序
renderConversations(content) {
const conversations = (content && content.conversations) ? content.conversations : [];
if (!conversations.length) {
this.setData({ allConversations: [] }, () => this.applyFilter());
return;
}
let list = normalizeConversationsList(conversations, app, this._peerProfileCache);
list.forEach(item => {
if (!item.lastMessage) {
item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 };
}
if (item.lastMessage.timestamp) {
item.lastMessage.date = formatDate(item.lastMessage.timestamp);
}
});
list.sort((a, b) => {
if (a.top && !b.top) return -1;
if (!a.top && b.top) return 1;
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 tb = (b.lastMessage && b.lastMessage.timestamp) || 0;
return tb - ta;
});
this.setData({ allConversations: list, displayCount: 10 }, () => {
this.applyFilter();
});
const unreadTotal = content.unreadTotal !== undefined
? content.unreadTotal
: 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) : '' });
}
// 重新订阅群组
this.subscribeGroupsIfNeeded(list);
},
/**
* 群组订阅,每次获取群组列表时直接订阅保证消息不丢失
*/
subscribeGroupsIfNeeded(conversations) {
if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return;
const groupIds = conversations
.filter(c => c.type === 'group' && c.groupId)
.map(c => c.groupId);
if (groupIds.length === 0) return;
// 不再判断缓存,直接订阅当前所有群组
wx.goEasy.im.subscribeGroup({
groupIds: groupIds,
onSuccess: () => {
// 可选的日志输出,不影响功能
// console.log('群组订阅成功', groupIds);
},
onFailed: (error) => {
console.error('群组订阅失败:', error);
}
});
},
applyFilter() {
const { allConversations, activeTab, searchKeyword } = this.data;
let filtered = [];
if (activeTab === 0) {
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');
}
if (searchKeyword.trim()) {
const kw = searchKeyword.trim().toLowerCase();
filtered = filtered.filter(c => {
const name = (c.data && c.data.name || '').toLowerCase();
const orderId = (c.data && c.data.orderId || '').toLowerCase();
const id = (c.userId || c.groupId || '').toLowerCase();
return name.includes(kw) || id.includes(kw) || orderId.includes(kw);
});
}
const tabUnread = [0, 0, 0];
allConversations.forEach(c => {
if (c.type === 'group' && c.data && c.data.orderId) tabUnread[0] += c.unread || 0;
else if (c.type === 'private') tabUnread[1] += c.unread || 0;
else if (c.type === 'cs') tabUnread[2] += c.unread || 0;
});
this.setData({ filteredConversations: filtered, tabUnread });
},
switchTab(e) {
const index = parseInt(e.currentTarget.dataset.index);
this.setData({ activeTab: index, displayCount: 10, searchKeyword: '' }, () => this.applyFilter());
},
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter());
},
onScrollToLower() {
const { displayCount, filteredConversations } = this.data;
if (displayCount < filteredConversations.length) {
this.setData({ displayCount: Math.min(displayCount + 10, filteredConversations.length) });
}
},
onRefresh() {
this.loadConversations();
},
chat(e) {
const conversation = e.currentTarget.dataset.conversation;
if (!conversation) return;
if (conversation.type === 'private') {
const param = {
toUserId: conversation.userId,
toName: conversation.data.name || '用户',
toAvatar: resolveAvatarUrl(conversation.data.avatar, app),
};
wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'group') {
const param = {
groupId: conversation.groupId,
orderId: conversation.data.orderId || '',
groupName: conversation.data.name,
groupAvatar: conversation.data.avatar,
isCross: conversation.data.isCross || 0
};
wx.navigateTo({ url: '/pages/group-chat/group-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)) });
}
},
chatKefu() {
const param = { teamId: 'support_team' };
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 });
},
closeMask() {
this.setData({ ['actionPopup.visible']: false });
},
topConversation() {
let conversation = this.data.actionPopup.conversation;
if (!conversation) return;
const top = !conversation.top;
wx.showLoading({ title: '处理中...' });
if (conversation.type === 'private') {
wx.goEasy.im.topPrivateConversation({
userId: conversation.userId, top,
onSuccess: () => {
wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
},
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
});
} else {
wx.goEasy.im.topGroupConversation({
groupId: conversation.groupId, top,
onSuccess: () => {
wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
},
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
});
}
this.closeMask();
},
removeConversation() {
let conversation = this.data.actionPopup.conversation;
if (!conversation) return;
wx.showLoading({ title: '删除中...' });
wx.goEasy.im.removeConversation({
conversation: conversation,
onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: '已删除' }); },
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); }
});
this.closeMask();
},
toggleNotification() {
const newMuted = !this.data.notificationMuted;
this.setData({ notificationMuted: newMuted });
wx.setStorageSync('notificationMuted', newMuted);
wx.showToast({ title: newMuted ? '已静音' : '已开启通知', icon: 'none' });
},
calculateScrollHeight() {
const systemInfo = wx.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const windowWidth = systemInfo.windowWidth;
const rpxRatio = 750 / windowWidth;
const headerHeight = 150 / rpxRatio;
const tabBarHeight = 130 / rpxRatio;
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 });
}
}))

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; }
.search-bar { flex: 1; }
.search-input { height: 68rpx; }
.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; }
.msg-page {
background: #f5f5f5;
min-height: 100vh;
}
.tab-row { border-bottom: 1rpx solid #e8e8e8; }
/* .tab-item 已由全局 .tab-item 覆盖 */
.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-badge { border-radius: 20rpx; padding: 2rpx 12rpx; margin-left: 8rpx; }
/* 顶部搜索 + 通知 */
.header {
display: flex;
align-items: center;
padding: 20rpx 24rpx;
background: #fff;
}
.conversation-list { background: #fff; }
.conversation-item { display: flex; align-items: center; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; }
.has-unread { background: #fafafa; }
.avatar { width: 96rpx; height: 96rpx; border-radius: 12rpx; margin-right: 20rpx; background: #eee; }
.info { flex: 1; }
.top-line { display: flex; justify-content: space-between; margin-bottom: 8rpx; }
.name { font-size: 30rpx; color: #1a1a1a; font-weight: 500; }
.time { font-size: 22rpx; color: #999; }
.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; }
.load-tip { text-align: center; padding: 24rpx; color: #999; font-size: 24rpx; }
.empty { text-align: center; padding: 200rpx 0; color: #bbb; font-size: 28rpx; }
.search-bar {
flex: 1;
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 36rpx;
padding: 0 24rpx;
height: 68rpx;
}
.search-input {
flex: 1;
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-btn { width: 300rpx; background: #07c160; color: #fff; border-radius: 40rpx; font-size: 28rpx; }
.kefu-entry {
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-sheet { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 24rpx 24rpx 0 0; padding: 20rpx 20rpx 150rpx 20rpx; }
.action-btn { text-align: center; padding: 28rpx 0; font-size: 32rpx; border-bottom: 1rpx solid #eee; }
.cancel { color: #999; margin-top: 12rpx; border-bottom: none; }
.action-mask {
position: fixed;
top: 0;
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;
}

View File

@@ -1,169 +1,140 @@
// pages/mine/mine.js
const app = getApp();
import { ensureLogin } from '../../utils/login';
import {
backfillUserProfileCache,
getUserProfileForDisplay,
clearCacheAndEnterNormal,
isRoleStatusActive,
ensureRoleOnCenterPage,
} from '../../utils/role-tab-bar';
import { enterLockedRole } from '../../utils/primary-role.js';
Page({
data: {
avatarUrl: '',
userNicheng: '',
userUid: '',
showLoginBtn: false,
orderCounts: { daifuwu:0, fuwuzhong:0, yiwancheng:0, yituikuan:0 },
// 图标
settingIcon: '', daifuwuIcon: '', fuwuzhongIcon: '', yiwanchengIcon: '', yituikuanIcon: '',
dashouIcon: '', shangjiaIcon: '', guanshiIcon: '', kefuIcon: '',
zuzhangIcon: '', guanzhualongIcon: '', arrowRightIcon: '', qingchuIcon: '',
dashouCertified: false,
shangjiaCertified: false,
bottomSafePadding: 0
},
onLoad() {
this.initIconUrls()
this.loadUserData()
this.setBottomSafe()
},
onShow() {
this.initIconUrls();
this.registerNotification();
this.checkAvatarPrompt();
backfillUserProfileCache(app);
this.loadUserData();
this.updateOrderCounts();
},
setBottomSafe() {
const sys = wx.getSystemInfoSync()
const safeBottom = sys.screenHeight - sys.safeArea.bottom // 底部安全区高度(px)
const tabBarHeight = (sys.windowWidth / 750) * 100 // 100rpx 转为 px
this.setData({
bottomSafePadding: safeBottom + tabBarHeight + 4 // 4px 微调,确保不留缝隙
})
},
registerNotification() {
const comp = this.selectComponent('#global-notification')
if (comp?.showNotification) {
app.globalData.globalNotification = {
show: d => comp.showNotification(d),
hide: () => comp.hideNotification()
}
}
},
checkAvatarPrompt() {
const ds = wx.getStorageSync('dashoustatus')
const tx = wx.getStorageSync('touxiang') || ''
if (ds === 1 && (!tx || tx === 'a_long/morentouxiang.jpg')) {
wx.showModal({
title: '提示', content: '请先设置头像',
confirmText: '去设置', confirmColor: '#C9A962',
success: r => r.confirm && wx.navigateTo({ url: '/pages/edit/edit' })
})
}
},
initIconUrls() {
const base = (app.globalData.ossImageUrl || '') + '/beijing/tubiao/'
this.setData({
settingIcon: base + 'grzx_shezhi.jpg',
daifuwuIcon: base + 'grzx_daifuwu.jpg',
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',
guanshiIcon: base + 'grzx_guanshi.jpg',
kefuIcon: base + 'grzx_kefu.jpg',
zuzhangIcon: base + 'grzx_zuzhang.jpg',
guanzhualongIcon: base + 'grzx_guanzhualong.jpg',
arrowRightIcon: base + 'grzx_arrow_right.jpg',
qingchuIcon: base + 'grzx_qingchu.jpg',
kaoheguanIcon: base + 'kaoheguan.png',
})
},
loadUserData() {
const profile = getUserProfileForDisplay(app);
this.setData({
avatarUrl: profile.avatarUrl,
userNicheng: profile.nick,
userUid: profile.uid,
showLoginBtn: !profile.isLoggedIn,
dashouCertified: isRoleStatusActive(wx.getStorageSync('dashoustatus')),
shangjiaCertified: isRoleStatusActive(wx.getStorageSync('shangjiastatus')),
});
},
updateOrderCounts() {
const c = app.globalData.dingdanTiaoshu || {}
this.setData({ orderCounts: {
daifuwu: c.daifuwu||0, fuwuzhong: c.fuwuzhong||0,
yiwancheng: c.yiwancheng||0, yituikuan: c.yituikuan||0
}})
},
goToSetting() { wx.navigateTo({ url: '/pages/edit/edit' }) },
handleWechatLogin() {
wx.showLoading({ title: '登录中...', mask: true });
ensureLogin({ silent: false, page: this })
.then(() => {
this.loadUserData();
this.updateOrderCounts();
wx.showToast({ title: '登录成功', icon: 'success' });
})
.catch(() => {})
.finally(() => wx.hideLoading());
},
goToOrder(e) { wx.navigateTo({ url: `/pages/orders/orders?type=${e.currentTarget.dataset.type}` }) },
goToAuth(e) {
const type = e.currentTarget.dataset.type;
if (!type || !['dashou', 'shangjia'].includes(type)) return;
ensureLogin({ silent: false, page: this })
.then(() => {
this.loadUserData();
const statusKey = type === 'dashou' ? 'dashoustatus' : 'shangjiastatus';
if (isRoleStatusActive(wx.getStorageSync(statusKey))) {
ensureRoleOnCenterPage(this, type);
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);
}
},
});
},
})
// pages/mine/mine.js
const app = getApp();
import { ensureLogin } from '../../utils/login';
import {
backfillUserProfileCache,
getUserProfileForDisplay,
isRoleStatusActive,
ensureRoleOnCenterPage,
} from '../../utils/role-tab-bar';
import { enterLockedRole } from '../../utils/primary-role.js';
import { createPage } from '../../utils/base-page.js';
Page(createPage({
data: {
avatarUrl: '',
userNicheng: '',
userUid: '',
showLoginBtn: false,
orderCounts: { daifuwu:0, fuwuzhong:0, yiwancheng:0, yituikuan:0 },
// 图标
settingIcon: '', daifuwuIcon: '', fuwuzhongIcon: '', yiwanchengIcon: '', yituikuanIcon: '',
dashouIcon: '', shangjiaIcon: '', guanshiIcon: '', kefuIcon: '',
zuzhangIcon: '', guanzhualongIcon: '', arrowRightIcon: '', qingchuIcon: '',
dashouCertified: false,
shangjiaCertified: false,
bottomSafePadding: 0
},
onLoad() {
this.initIconUrls()
this.loadUserData()
this.setBottomSafe()
},
onShow() {
this.initIconUrls();
this.registerNotificationComponent();
this.checkAvatarPrompt();
backfillUserProfileCache(app);
this.loadUserData();
this.updateOrderCounts();
},
setBottomSafe() {
const sys = wx.getSystemInfoSync()
const safeBottom = sys.screenHeight - sys.safeArea.bottom // 底部安全区高度(px)
const tabBarHeight = (sys.windowWidth / 750) * 100 // 100rpx 转为 px
this.setData({
bottomSafePadding: safeBottom + tabBarHeight + 4 // 4px 微调,确保不留缝隙
})
},
checkAvatarPrompt() {
const ds = wx.getStorageSync('dashoustatus')
const tx = wx.getStorageSync('touxiang') || ''
if (ds === 1 && (!tx || tx === 'a_long/morentouxiang.jpg')) {
wx.showModal({
title: '提示', content: '请先设置头像',
confirmText: '去设置', confirmColor: '#C9A962',
success: r => r.confirm && wx.navigateTo({ url: '/pages/edit/edit' })
})
}
},
initIconUrls() {
const base = (app.globalData.ossImageUrl || '') + '/beijing/tubiao/'
this.setData({
settingIcon: base + 'grzx_shezhi.jpg',
daifuwuIcon: base + 'grzx_daifuwu.jpg',
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',
guanshiIcon: base + 'grzx_guanshi.jpg',
kefuIcon: base + 'grzx_kefu.jpg',
zuzhangIcon: base + 'grzx_zuzhang.jpg',
guanzhualongIcon: base + 'grzx_guanzhualong.jpg',
arrowRightIcon: base + 'grzx_arrow_right.jpg',
qingchuIcon: base + 'grzx_qingchu.jpg',
kaoheguanIcon: base + 'kaoheguan.png',
})
},
loadUserData() {
const profile = getUserProfileForDisplay(app);
this.setData({
avatarUrl: profile.avatarUrl,
userNicheng: profile.nick,
userUid: profile.uid,
showLoginBtn: !profile.isLoggedIn,
dashouCertified: isRoleStatusActive(wx.getStorageSync('dashoustatus')),
shangjiaCertified: isRoleStatusActive(wx.getStorageSync('shangjiastatus')),
});
},
updateOrderCounts() {
const c = app.globalData.dingdanTiaoshu || {}
this.setData({ orderCounts: {
daifuwu: c.daifuwu||0, fuwuzhong: c.fuwuzhong||0,
yiwancheng: c.yiwancheng||0, yituikuan: c.yituikuan||0
}})
},
goToSetting() { wx.navigateTo({ url: '/pages/edit/edit' }) },
handleWechatLogin() {
wx.showLoading({ title: '登录中...', mask: true });
ensureLogin({ silent: false, page: this })
.then(() => {
this.loadUserData();
this.updateOrderCounts();
wx.showToast({ title: '登录成功', icon: 'success' });
})
.catch(() => {})
.finally(() => wx.hideLoading());
},
goToOrder(e) { wx.navigateTo({ url: `/pages/orders/orders?type=${e.currentTarget.dataset.type}` }) },
goToAuth(e) {
const type = e.currentTarget.dataset.type;
if (!type || !['dashou', 'shangjia'].includes(type)) return;
ensureLogin({ silent: false, page: this })
.then(() => {
this.loadUserData();
const statusKey = type === 'dashou' ? 'dashoustatus' : 'shangjiastatus';
if (isRoleStatusActive(wx.getStorageSync(statusKey))) {
ensureRoleOnCenterPage(this, type);
enterLockedRole(type, app);
return;
}
wx.navigateTo({ url: `/pages/verify/verify?type=${type}` });
})
.catch(() => {});
},
goToCustomService() { this.goToKefu() },
goToGuanzhuA() { wx.navigateTo({ url: '/pages/manager-assign/manager-assign' }) },
}))

View File

@@ -1,190 +1,213 @@
/* pages/orders/orders.wxss */
.dingdan-page {
background-color: #f8f9fa;
padding-bottom: 20rpx;
}
.dingdan-tabs-scroll {
width: 100%;
height: 90rpx;
white-space: nowrap;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
}
.dingdan-tabs-container {
display: flex;
align-items: center;
height: 100%;
padding: 0 20rpx;
}
.dingdan-tab-item {
position: relative;
}
.dingdan-tab-item:last-child {
margin-right: 0;
}
/* .dingdan-tab-item.tab-active 已由全局 .tab-capsule--active 覆盖 */
.tab-active-line {
width: 40rpx;
height: 4rpx;
background-color: #4caf50;
border-radius: 2rpx;
margin-top: 6rpx;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}
.tab-text {
font-weight: 400;
}
.dingdan-list-container {
padding: 20rpx;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400rpx;
}
.loading-spinner {
border: 6rpx solid #e0e0e0;
border-top-color: #4caf50;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 28rpx;
color: #999999;
}
.empty-container {
height: 500rpx;
padding-top: 80rpx;
}
.empty-image {
opacity: 0.6;
}
.empty-text {
font-size: 32rpx;
color: #999999;
}
.dingdan-card {
display: flex;
align-items: center;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.dingdan-card:active {
transform: translateY(-2rpx);
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08);
}
.dingdan-card-left {
flex-shrink: 0;
margin-right: 24rpx;
}
.dingdan-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
background-color: #f5f5f5;
}
.dingdan-card-center {
flex: 1;
height: 160rpx;
display: flex;
align-items: flex-start;
}
.dingdan-jieshao {
font-size: 28rpx;
line-height: 40rpx;
color: #333333;
font-weight: 400;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
.dingdan-card-right {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
height: 160rpx;
margin-left: 20rpx;
}
/* 价格样式 */
.dingdan-jine {
display: flex;
align-items: center; /* 垂直居中 */
}
.jine-symbol {
font-size: 28rpx;
color: #333333;
font-weight: 500;
margin-right: 4rpx; /* 添加右边距,让符号和数字有间隔 */
}
.jine-number {
font-size: 32rpx; /* 整数和小数一样大 */
color: #333333;
font-weight: 600;
}
.load-more-line {
width: 80rpx;
height: 1rpx;
background-color: #e0e0e0;
margin: 0 20rpx;
}
.load-more-text {
white-space: nowrap;
}
@media (max-width: 375px) {
.dingdan-image {
width: 140rpx;
height: 140rpx;
}
.dingdan-card-center {
height: 140rpx;
}
.dingdan-card-right {
height: 140rpx;
}
.jine-number {
font-size: 30rpx;
}
}
/* pages/orders/orders.wxss */
.dingdan-page {
background: #f2f2f2;
min-height: 100vh;
}
/* TAB栏 */
.dingdan-tabs-scroll {
width: 100%;
background: #fff;
white-space: nowrap;
border-bottom: 1rpx solid #f0f0f0;
}
.dingdan-tabs-container {
display: flex;
align-items: center;
height: 88rpx;
padding: 0 8rpx;
}
.dingdan-tab-item {
position: relative;
padding: 0 28rpx;
height: 88rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.tab-text {
font-size: 28rpx;
color: #666;
font-weight: 400;
line-height: 1;
}
.dingdan-tab-item.tab-active .tab-text {
color: #e02e24;
font-weight: 700;
}
.tab-active-line {
width: 40rpx;
height: 6rpx;
background: #e02e24;
border-radius: 3rpx;
margin-top: 10rpx;
}
/* 列表区 */
.dingdan-list-container {
padding: 20rpx 24rpx;
}
/* 加载 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400rpx;
}
.loading-spinner {
width: 56rpx;
height: 56rpx;
border: 4rpx solid #f0f0f0;
border-top-color: #e02e24;
border-radius: 50%;
animation: spin 0.7s linear infinite;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 26rpx;
color: #999;
}
/* 空状态 */
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 500rpx;
}
.empty-image {
width: 200rpx;
height: 200rpx;
opacity: 0.5;
margin-bottom: 24rpx;
}
.empty-text {
font-size: 30rpx;
color: #999;
margin-bottom: 8rpx;
}
.empty-subtext {
font-size: 24rpx;
color: #bbb;
}
/* 订单卡片 */
.dingdan-card {
display: flex;
align-items: center;
background: #fff;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 16rpx;
}
.dingdan-card-left {
flex-shrink: 0;
margin-right: 20rpx;
}
.dingdan-image {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
background: #f5f5f5;
}
.dingdan-card-center {
flex: 1;
height: 160rpx;
display: flex;
align-items: flex-start;
}
.dingdan-jieshao {
font-size: 28rpx;
line-height: 40rpx;
color: #333;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.dingdan-card-right {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
height: 160rpx;
margin-left: 16rpx;
flex-shrink: 0;
}
.dingdan-zhuangtai {
font-size: 24rpx;
color: #e02e24;
font-weight: 600;
}
.dingdan-jine {
display: flex;
align-items: baseline;
}
.jine-symbol {
font-size: 24rpx;
color: #333;
font-weight: 500;
}
.jine-number {
font-size: 32rpx;
color: #333;
font-weight: 700;
}
/* 加载更多 */
.load-more-container {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
}
.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;
}

View File

@@ -62,9 +62,9 @@
</view>
</scroll-view>
<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.zhuangtai === 3 }}" class="btn primary" bindtap="startPay">立即缴纳</view>
<view class="btn default" bindtap="closeDetail">关闭</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="pbtn primary" bindtap="startPay">立即缴纳</view>
<view class="pbtn default" bindtap="closeDetail">关闭</view>
</view>
</view>
</view>
@@ -100,8 +100,8 @@
</view>
</scroll-view>
<view class="modal-foot">
<view class="btn default" bindtap="closeShensu">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
<view class="pbtn default" bindtap="closeShensu">取消</view>
<view class="pbtn primary" bindtap="submitShensu">提交申诉</view>
</view>
</view>
</view>

View File

@@ -50,10 +50,10 @@ page { background: #f5f5f5; }
.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; }
.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; }
.default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; }
.pbtn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */

View File

@@ -48,8 +48,8 @@
<text class="confirm-tip">确认后将从您的佣金/分红中扣除</text>
</view>
<view class="pay-foot">
<view class="btn default" bindtap="backToMethod">取消</view>
<view class="btn primary" bindtap="confirmBalance">确认</view>
<view class="pbtn default" bindtap="backToMethod">取消</view>
<view class="pbtn primary" bindtap="confirmBalance">确认</view>
</view>
</view>

View File

@@ -53,7 +53,7 @@
@keyframes spin { to { transform: rotate(360deg); } }
.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; }
.default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; }
.pbtn:active { opacity: 0.8; }

View File

@@ -60,8 +60,8 @@
</view>
</scroll-view>
<view class="modal-foot">
<view wx:if="{{ xuanzhongChufa.sqzhuangtai === 0 }}" class="btn primary" bindtap="openShensuModal">申诉</view>
<view class="btn default" bindtap="guanbiXiangqing">关闭</view>
<view wx:if="{{ xuanzhongChufa.sqzhuangtai === 0 }}" class="pbtn primary" bindtap="openShensuModal">申诉</view>
<view class="pbtn default" bindtap="guanbiXiangqing">关闭</view>
</view>
</view>
</view>
@@ -97,8 +97,8 @@
</view>
</scroll-view>
<view class="modal-foot">
<view class="btn default" bindtap="closeShensuModal">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
<view class="pbtn default" bindtap="closeShensuModal">取消</view>
<view class="pbtn primary" bindtap="submitShensu">提交申诉</view>
</view>
</view>
</view>

View File

@@ -59,10 +59,10 @@ page { background: #f5f5f5; }
/* 🔥 底部按钮区左右 padding 严格均等 */
.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; }
.default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; }
.pbtn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */

View File

@@ -25,38 +25,38 @@
<!-- 按钮区域(第一行:保存/刷新/生成海报) -->
<view class="button-section">
<view class="btn-group">
<view class="btn save-btn" bindtap="saveToAlbum" wx:if="{{qrcodeUrl}}">
<text class="btn-icon">📥</text>
<text class="btn-text">保存二维码</text>
<view class="pbtn-group">
<view class="pbtn pbtn-save" bindtap="saveToAlbum" wx:if="{{qrcodeUrl}}">
<text class="pbtn-icon">📥</text>
<text class="pbtn-text">保存二维码</text>
</view>
<view class="btn refresh-btn" bindtap="refreshQRCode" wx:if="{{!isLoading}}">
<text class="btn-icon">⟳</text>
<text class="btn-text">{{qrcodeUrl ? '重新获取' : '获取二维码'}}</text>
<view class="pbtn pbtn-refresh" bindtap="refreshQRCode" wx:if="{{!isLoading}}">
<text class="pbtn-icon">⟳</text>
<text class="pbtn-text">{{qrcodeUrl ? '重新获取' : '获取二维码'}}</text>
</view>
<view class="btn loading-btn" wx:else>
<view class="pbtn pbtn-loading" wx:else>
<view class="loading-spinner-small"></view>
<text class="btn-text">生成中...</text>
<text class="pbtn-text">生成中...</text>
</view>
<!-- 🔥【新增】生成我的专属海报按钮 -->
<view class="btn poster-btn" bindtap="generateMyPoster" wx:if="{{qrcodeUrl && !showPosterCanvas}}">
<text class="btn-icon">🎨</text>
<text class="btn-text">生成我的专属海报</text>
<!-- 生成我的专属海报按钮 -->
<view class="pbtn pbtn-poster" bindtap="generateMyPoster" wx:if="{{qrcodeUrl && !showPosterCanvas}}">
<text class="pbtn-icon">🎨</text>
<text class="pbtn-text">生成我的专属海报</text>
</view>
</view>
</view>
<!-- 🔥【新增】专属海报展示区(当 showPosterCanvas 为 true 时显示) -->
<!-- 专属海报展示区(当 showPosterCanvas 为 true 时显示) -->
<view class="poster-section" wx:if="{{showPosterCanvas}}">
<canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
<view class="poster-actions">
<view class="btn save-poster-btn" bindtap="savePoster">
<text class="btn-icon">📥</text>
<text class="btn-text">保存海报</text>
<view class="pbtn pbtn-save-poster" bindtap="savePoster">
<text class="pbtn-icon">📥</text>
<text class="pbtn-text">保存海报</text>
</view>
<view class="btn back-btn" bindtap="hidePoster">
<text class="btn-icon">↩️</text>
<text class="btn-text">返回</text>
<view class="pbtn pbtn-back" bindtap="hidePoster">
<text class="pbtn-icon">↩️</text>
<text class="pbtn-text">返回</text>
</view>
</view>
</view>
@@ -68,4 +68,4 @@
</view>
<global-notification id="global-notification" />
</view>
</view>

View File

@@ -1,269 +1,237 @@
/* 机甲风格推广海报页面(含新增专属海报样式) */
/* 推广海报页面 */
page {
background: #0a0e17;
min-height: 100vh;
font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
color: #fff;
}
/* 背景网格 */
.grid-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(0, 160, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 160, 255, 0.05) 1px, transparent 1px);
background-size: 50rpx 50rpx;
pointer-events: none;
z-index: 0;
}
.page-container {
position: relative;
z-index: 2;
min-height: 100vh;
padding: 30rpx 25rpx;
box-sizing: border-box;
}
/* 机械装饰线条 */
.mech-line {
position: absolute;
pointer-events: none;
z-index: 1;
}
.line-h {
top: 200rpx;
left: 0;
width: 100%;
height: 2rpx;
background: linear-gradient(90deg, transparent, #00a6ff, transparent);
}
.line-v {
right: 50rpx;
top: 100rpx;
width: 2rpx;
height: 300rpx;
background: linear-gradient(180deg, transparent, #00a6ff, transparent);
}
.content {
position: relative;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 40rpx 0;
}
/* 标题区域 */
.title-section {
text-align: center;
margin-bottom: 60rpx;
}
.title {
font-size: 44rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 0 30rpx #00a6ff;
letter-spacing: 2rpx;
display: block;
margin-bottom: 20rpx;
}
.subtitle {
font-size: 28rpx;
color: #9cf;
opacity: 0.9;
letter-spacing: 1rpx;
}
/* 二维码区域(方形,无斜切) */
.qrcode-section {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 60rpx;
}
.qrcode-wrapper {
position: relative;
width: 400rpx;
height: 400rpx;
background: rgba(0, 20, 40, 0.8);
border: 4rpx solid #00a6ff;
/* 移除 clip-path保持方形 */
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 80rpx rgba(0, 166, 255, 0.4);
animation: qrcodePulse 2s infinite alternate;
}
.qrcode-img {
width: 90%;
height: 90%;
object-fit: contain;
}
.qrcode-glow {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 30% 30%, rgba(0, 166, 255, 0.2), transparent 70%);
pointer-events: none;
}
.empty-qrcode {
width: 400rpx;
height: 400rpx;
background: rgba(0, 20, 40, 0.8);
border: 4rpx dashed #00a6ff;
/* 方形 */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
font-size: 80rpx;
color: #00a6ff;
margin-bottom: 20rpx;
opacity: 0.5;
}
.empty-text {
font-size: 26rpx;
color: #9cf;
text-align: center;
padding: 0 40rpx;
}
/* 按钮区域(机甲斜切风格) */
.button-section {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 40rpx;
}
.btn-group {
display: flex;
gap: 20rpx;
flex-wrap: wrap;
justify-content: center;
}
.btn {
width: 220rpx;
height: 80rpx;
background: rgba(0, 30, 60, 0.8);
border: 2rpx solid #00a6ff;
border-right: none;
border-bottom: none;
clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: 0.2s;
cursor: pointer;
}
.btn:active {
transform: scale(0.96);
box-shadow: 0 0 40rpx #00a6ff;
}
.save-btn {
border-color: #00a6ff;
}
.refresh-btn {
border-color: #ffaa00;
}
.refresh-btn .btn-icon {
color: #ffaa00;
}
/* 🔥【新增】生成海报按钮样式 */
.poster-btn {
border-color: #ffaa00;
}
.poster-btn .btn-icon {
color: #ffaa00;
}
.loading-btn {
opacity: 0.7;
pointer-events: none;
}
.btn-icon {
font-size: 36rpx;
margin-right: 8rpx;
}
.btn-text {
font-size: 26rpx;
font-weight: 500;
color: #fff;
}
.loading-spinner-small {
width: 30rpx;
height: 30rpx;
border: 4rpx solid rgba(255,255,255,0.2);
border-top: 4rpx solid #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10rpx;
}
/* 🔥【新增】专属海报展示区 */
.poster-section {
margin-top: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.poster-canvas {
width: 600rpx;
height: 900rpx;
background: #1a1f2e; /* 占位色 */
border: 4rpx solid #00a6ff;
box-shadow: 0 0 60rpx #00a6ff;
/* 保持方形,无斜切 */
}
.poster-actions {
display: flex;
gap: 30rpx;
margin-top: 30rpx;
justify-content: center;
}
.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); }
}
background: #0a0e17;
min-height: 100vh;
font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
color: #fff;
}
/* 背景网格 */
.grid-bg {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background-image:
linear-gradient(rgba(0, 160, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 160, 255, 0.05) 1px, transparent 1px);
background-size: 50rpx 50rpx;
pointer-events: none;
z-index: 0;
}
.page-container {
position: relative;
z-index: 2;
min-height: 100vh;
padding: 30rpx 25rpx;
box-sizing: border-box;
}
/* 机械装饰线条 */
.mech-line {
position: absolute;
pointer-events: none;
z-index: 1;
}
.line-h {
top: 200rpx; left: 0;
width: 100%; height: 2rpx;
background: linear-gradient(90deg, transparent, #00a6ff, transparent);
}
.line-v {
right: 50rpx; top: 100rpx;
width: 2rpx; height: 300rpx;
background: linear-gradient(180deg, transparent, #00a6ff, transparent);
}
.content {
position: relative;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 40rpx 0;
}
/* 标题区域 */
.title-section {
text-align: center;
margin-bottom: 60rpx;
}
.title {
font-size: 44rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 0 30rpx #00a6ff;
letter-spacing: 2rpx;
display: block;
margin-bottom: 20rpx;
}
.subtitle {
font-size: 28rpx;
color: #9cf;
opacity: 0.9;
letter-spacing: 1rpx;
}
/* 二维码区域 */
.qrcode-section {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 60rpx;
}
.qrcode-wrapper {
position: relative;
width: 400rpx; height: 400rpx;
background: rgba(0, 20, 40, 0.8);
border: 4rpx solid #00a6ff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 80rpx rgba(0, 166, 255, 0.4);
animation: qrcodePulse 2s infinite alternate;
}
.qrcode-img {
width: 90%; height: 90%;
object-fit: contain;
}
.qrcode-glow {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background: radial-gradient(circle at 30% 30%, rgba(0, 166, 255, 0.2), transparent 70%);
pointer-events: none;
}
.empty-qrcode {
width: 400rpx; height: 400rpx;
background: rgba(0, 20, 40, 0.8);
border: 4rpx dashed #00a6ff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
font-size: 80rpx;
color: #00a6ff;
margin-bottom: 20rpx;
opacity: 0.5;
}
.empty-text {
font-size: 26rpx;
color: #9cf;
text-align: center;
padding: 0 40rpx;
}
/* 按钮区域 */
.button-section {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 40rpx;
}
.pbtn-group {
display: flex;
gap: 20rpx;
flex-wrap: wrap;
justify-content: center;
}
.pbtn {
width: 220rpx;
height: 80rpx;
background: rgba(0, 30, 60, 0.8);
border: 2rpx solid #00a6ff;
border-right: none;
border-bottom: none;
clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: 0.2s;
}
.pbtn:active {
transform: scale(0.96);
box-shadow: 0 0 40rpx #00a6ff;
}
.pbtn-save { border-color: #00a6ff; }
.pbtn-refresh { border-color: #ffaa00; }
.pbtn-refresh .pbtn-icon { color: #ffaa00; }
.pbtn-poster { border-color: #ffaa00; }
.pbtn-poster .pbtn-icon { color: #ffaa00; }
.pbtn-loading {
opacity: 0.7;
pointer-events: none;
}
.pbtn-icon {
font-size: 36rpx;
margin-right: 8rpx;
}
.pbtn-text {
font-size: 26rpx;
font-weight: 500;
color: #fff;
}
.loading-spinner-small {
width: 30rpx; height: 30rpx;
border: 4rpx solid rgba(255,255,255,0.2);
border-top: 4rpx solid #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10rpx;
}
/* 专属海报展示区 */
.poster-section {
margin-top: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.poster-canvas {
width: 600rpx; height: 900rpx;
background: #1a1f2e;
border: 4rpx solid #00a6ff;
box-shadow: 0 0 60rpx #00a6ff;
}
.poster-actions {
display: flex;
gap: 30rpx;
margin-top: 30rpx;
justify-content: center;
}
.pbtn-save-poster {
background: linear-gradient(45deg, #0066cc, #00a6ff);
border-color: #00a6ff;
}
.pbtn-back {
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); }
}

View File

@@ -140,8 +140,7 @@ Page({
that.handleLoadError(res.data?.msg || '加载失败')
}
},
fail(error) {
console.error('商品详情加载失败:', error)
fail() {
that.handleLoadError('网络错误,请重试')
}
})
@@ -234,6 +233,10 @@ Page({
this.loadShangpinDetail()
},
goToKefu() {
wx.navigateTo({ url: '/pages/cs-chat/cs-chat' })
},
onShareAppMessage() {
const { shangpinData, shangpinId } = this.data

View File

@@ -1,112 +1,123 @@
<!--pages/product-detail/product-detail.wxml-->
<view class="detail-page page-container">
<!-- 加载状态 -->
<view class="loading-container loading-mask loading-mask--light" wx:if="{{isLoading}}">
<view class="loading-content">
<view class="loading-spinner">
<view class="spinner-ring"></view>
<view class="spinner-dot"></view>
</view>
<text class="loading-text">加载中...</text>
</view>
</view>
<!-- 错误状态 -->
<view class="error-container empty-state" wx:if="{{!isLoading && loadError}}">
<image src="/images/error.png" class="error-icon" />
<text class="error-text">加载失败</text>
<button class="error-btn btn btn-sm btn-sm--green" bindtap="onRefresh">重新加载</button>
</view>
<!-- 商品详情内容 -->
<view class="detail-content" wx:if="{{!isLoading && !loadError && shangpinData}}">
<!-- 商品图片区域(占满屏幕) -->
<view class="image-section">
<view class="image-container" bindtap="previewImage">
<image
src="{{shangpinData.tupian}}"
mode="aspectFill"
class="product-image"
/>
<!-- 库存标签 -->
<view class="kucun-tag {{shangpinData.kucunClass || 'kucun-normal'}}">
<text class="kucun-text">{{shangpinData.kucunText || '库存充足'}}</text>
</view>
</view>
</view>
<!-- 下方内容区域(顶部有圆角) -->
<view class="content-area">
<!-- 商品标题和价格区域 -->
<view class="title-price-section">
<view class="title-row">
<text class="product-title">{{shangpinData.biaoti || ''}}</text>
</view>
<view class="price-row">
<!-- 价格 -->
<view class="price-section">
<text class="price-icon">¥</text>
<text class="price-integer">{{shangpinData.priceInteger || '0'}}</text>
<text class="price-decimal">.{{shangpinData.priceDecimal || '00'}}</text>
</view>
<!-- 销量 -->
<view class="sales-section">
<image src="/images/xiaoliang.png" class="sales-icon" />
<text class="sales-text">{{shangpinData.xiaoliangText || '0'}}次购买</text>
</view>
</view>
</view>
<!-- 商品介绍区域 -->
<view class="info-section" wx:if="{{shangpinData.xiangqing}}">
<view class="section-header">
<image src="/images/jieshao.png" class="section-icon" />
<text class="section-title">商品介绍:</text>
</view>
<view class="section-content">
<text class="content-text">{{shangpinData.xiangqing}}</text>
</view>
</view>
<!-- 购买须知区域 -->
<view class="info-section" wx:if="{{shangpinData.xiadanxuzhi}}">
<view class="section-header">
<image src="/images/jieshao.png" class="section-icon" />
<text class="section-title">购买须知:</text>
</view>
<view class="section-content">
<text class="content-text">{{shangpinData.xiadanxuzhi}}</text>
</view>
</view>
<!-- 规则图片区域(只有有图片时才显示) -->
<view class="rule-section" wx:if="{{shangpinData.guize}}">
<image
src="{{shangpinData.guize}}"
mode="widthFix"
class="rule-image"
binderror="onImageError"
/>
</view>
<!-- 占位区域(给底部按钮留空间) -->
<view class="placeholder"></view>
</view>
</view>
<!-- 立即下单按钮(固定在底部,无价格标签) -->
<view class="order-btn-container fixed-bottom" wx:if="{{!isLoading && !loadError && shangpinData}}">
<view class="order-btn btn btn-primary btn-primary--green" bindtap="onOrderClick">
<!-- 按钮发光特效 -->
<view class="btn-glow"></view>
<!-- 按钮流光特效 -->
<view class="btn-streamer"></view>
<!-- 按钮文字 -->
<text class="btn-text">立即预约</text>
</view>
</view>
<global-notification id="global-notification" />
</view>
<!--pages/product-detail/product-detail.wxml-->
<view class="page">
<!-- 加载 -->
<view class="loading-box" wx:if="{{isLoading}}">
<view class="spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 错误 -->
<view class="error-box" wx:if="{{!isLoading && loadError}}">
<text class="error-text">加载失败</text>
<view class="error-btn" bindtap="onRefresh">重新加载</view>
</view>
<!-- 商品详情 -->
<block wx:if="{{!isLoading && !loadError && shangpinData}}">
<!-- 商品大图 -->
<view class="hero" bindtap="previewImage">
<image src="{{shangpinData.tupian}}" mode="aspectFill" class="hero-img" />
<!-- 图片角标 -->
<view class="hero-badge">
<text class="hero-badge-text">{{shangpinData.kucun <= 0 ? '已售罄' : '现货'}}</text>
</view>
</view>
<!-- 价格区 - 红底渐变 -->
<view class="price-block">
<view class="price-left">
<text class="price-sym">¥</text>
<text class="price-int">{{shangpinData.priceInteger || '0'}}</text>
<text class="price-dec">.{{shangpinData.priceDecimal || '00'}}</text>
</view>
<view class="price-right">
<text class="sold-text">已拼{{shangpinData.xiaoliangText || '0'}}件</text>
</view>
</view>
<!-- 标题区 -->
<view class="title-block">
<view class="title-tags">
<text class="tag-hot">热卖</text>
</view>
<text class="title-text">{{shangpinData.biaoti || ''}}</text>
</view>
<!-- 服务保障条 -->
<view class="service-strip">
<view class="service-item">
<text class="service-check"></text>
<text class="service-label">极速接单</text>
</view>
<view class="service-item">
<text class="service-check">✓</text>
<text class="service-label">资金担保</text>
</view>
<view class="service-item">
<text class="service-check">✓</text>
<text class="service-label">售后保障</text>
</view>
<view class="service-item">
<text class="service-check">✓</text>
<text class="service-label">隐私保护</text>
</view>
</view>
<!-- 选择SKU入口 -->
<view class="sku-trigger" bindtap="onOrderClick">
<view class="sku-trigger-left">
<text class="sku-label">选择</text>
<text class="sku-value">服务规格、购买数量</text>
</view>
<text class="sku-arrow">></text>
</view>
<!-- 商品介绍 -->
<view class="detail-card" wx:if="{{shangpinData.xiangqing}}">
<view class="detail-card-head">
<text class="detail-card-title">商品介绍</text>
</view>
<text class="detail-card-body">{{shangpinData.xiangqing}}</text>
</view>
<!-- 购买须知 -->
<view class="detail-card" wx:if="{{shangpinData.xiadanxuzhi}}">
<view class="detail-card-head">
<text class="detail-card-title">购买须知</text>
</view>
<text class="detail-card-body">{{shangpinData.xiadanxuzhi}}</text>
</view>
<!-- 规则图片 -->
<view class="detail-card" wx:if="{{shangpinData.guize}}">
<view class="detail-card-head">
<text class="detail-card-title">服务规则</text>
</view>
<image src="{{shangpinData.guize}}" mode="widthFix" class="rule-img" binderror="onImageError" />
</view>
<!-- 底部留白 -->
<view class="bottom-space"></view>
</block>
<!-- 底部操作栏 -->
<view class="bottom-bar" wx:if="{{!isLoading && !loadError && shangpinData}}">
<view class="bottom-bar-inner">
<!-- 客服 -->
<view class="bar-icon-btn" bindtap="goToKefu">
<text class="bar-icon-text">客服</text>
</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>

View File

@@ -1,349 +1,347 @@
/* pages/product-detail/product-detail.wxss */
.detail-page {
width: 100%;
background: #f5f5f5;
}
/* 加载状态 */
.loading-container {
position: absolute;
background: #f5f5f5;
z-index: 100;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
}
.loading-spinner {
width: 80rpx;
height: 80rpx;
position: relative;
margin-bottom: 30rpx;
}
.spinner-ring {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 4rpx solid rgba(82, 196, 26, 0.2);
border-radius: 50%;
}
.spinner-dot {
position: absolute;
top: 0;
left: 50%;
width: 12rpx;
height: 12rpx;
background: #52c41a;
border-radius: 50%;
transform: translateX(-50%);
animation: spin 1s linear infinite;
}
.loading-text {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
/* 错误状态 */
.error-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #f5f5f5;
z-index: 100;
padding: 0;
}
.error-icon {
width: 180rpx;
height: 180rpx;
opacity: 0.3;
margin-bottom: 30rpx;
}
.error-text {
font-size: 30rpx;
color: #999;
margin-bottom: 40rpx;
}
.error-btn {
font-weight: 500;
padding: 20rpx 50rpx;
border-radius: 50rpx;
box-shadow: 0 8rpx 24rpx rgba(82, 196, 26, 0.3);
}
/* 商品详情内容 */
.detail-content {
padding-bottom: 140rpx; /* 给底部按钮留出空间 */
}
/* 商品图片区域(占满屏幕) */
.image-section {
width: 100%;
background: white;
}
.image-container {
width: 100%;
height: 750rpx; /* 正方形,占满屏幕宽度 */
position: relative;
overflow: hidden;
background: #f5f5f5;
}
.product-image {
width: 100%;
height: 100%;
}
/* 库存标签 */
.kucun-tag {
position: absolute;
bottom: 30rpx;
right: 30rpx;
padding: 12rpx 24rpx;
border-radius: 30rpx;
backdrop-filter: blur(10rpx);
z-index: 10;
}
.kucun-normal {
background: rgba(0, 0, 0, 0.6);
color: white;
}
.kucun-none {
background: rgba(255, 77, 79, 0.8);
color: white;
}
.kucun-text {
font-size: 24rpx;
font-weight: 500;
}
/* 下方内容区域(顶部有圆角,与图片重叠一部分) */
.content-area {
position: relative;
background: white;
border-radius: 40rpx 40rpx 0 0;
margin-top: -40rpx; /* 与图片重叠一部分,实现平滑过渡 */
padding: 40rpx 30rpx 0;
min-height: 400rpx;
}
/* 标题和价格区域 */
.title-price-section {
margin-bottom: 40rpx;
}
.title-row {
margin-bottom: 30rpx;
}
.product-title {
font-size: 38rpx;
font-weight: 700;
color: #222;
line-height: 1.5;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
/* 价格和销量行 */
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
/* 价格部分 */
.price-section {
display: flex;
align-items: baseline;
}
.price-icon {
font-size: 34rpx;
color: #ff4d4f;
font-weight: 600;
}
.price-integer {
font-size: 58rpx;
color: #ff4d4f;
font-weight: 700;
margin-left: 4rpx;
}
.price-decimal {
font-size: 34rpx;
color: #ff4d4f;
font-weight: 600;
}
/* 销量部分 */
.sales-section {
display: flex;
align-items: center;
}
.sales-icon {
width: 30rpx;
height: 30rpx;
margin-right: 12rpx;
opacity: 0.6;
}
.sales-text {
font-size: 28rpx;
color: #999;
font-weight: 400;
}
/* 信息区域(商品介绍、购买须知) */
.info-section {
margin-bottom: 40rpx;
padding-top: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.section-icon {
width: 36rpx;
height: 36rpx;
margin-right: 16rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #52c41a; /* 绿色 */
}
.section-content {
line-height: 1.8;
padding-left: 52rpx; /* 与图标对齐 */
}
.content-text {
font-size: 28rpx;
color: #333;
font-weight: 400;
white-space: pre-line;
line-height: 1.8;
}
/* 规则图片区域 */
.rule-section {
margin: 40rpx 0;
padding-top: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.rule-image {
width: 100%;
border-radius: 16rpx;
display: block;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
}
/* 占位区域 */
.placeholder {
height: 140rpx;
}
/* 立即下单按钮容器(固定底部) */
.order-btn-container {
background: linear-gradient(transparent, rgba(255, 255, 255, 0.9) 70%);
}
/* 立即下单按钮 */
.order-btn {
border-radius: 50rpx;
height: 100rpx;
box-shadow:
0 12rpx 36rpx rgba(82, 196, 26, 0.4),
0 4rpx 12rpx rgba(82, 196, 26, 0.3);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.order-btn:active {
box-shadow:
0 6rpx 24rpx rgba(82, 196, 26, 0.5),
0 2rpx 8rpx rgba(82, 196, 26, 0.4);
}
/* 按钮发光特效 */
.btn-glow {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.1);
animation: glowPulse 2s infinite;
}
/* 按钮流光特效 */
.btn-streamer {
position: absolute;
top: 0;
left: 0;
width: 100rpx;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transform: translateX(-100%);
animation: streamerFlow 3s infinite;
}
/* 按钮文字 */
.btn-text {
font-size: 36rpx;
color: white;
font-weight: 600;
letter-spacing: 2rpx;
position: relative;
z-index: 2;
}
/* 动画定义 */
@keyframes glowPulse {
0%, 100% {
opacity: 0.5;
}
50% {
opacity: 0.8;
}
}
@keyframes streamerFlow {
0% {
transform: translateX(-100%);
}
50% {
transform: translateX(100%);
}
100% {
transform: translateX(100%);
}
}
/* pages/product-detail/product-detail.wxss */
page {
background: #f2f2f2;
}
.page {
min-height: 100vh;
padding-bottom: 140rpx;
box-sizing: border-box;
}
/* 加载 */
.loading-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
}
.spinner {
width: 56rpx;
height: 56rpx;
border: 4rpx solid #f0f0f0;
border-top-color: #e02e24;
border-radius: 50%;
animation: spin 0.7s linear infinite;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 26rpx;
color: #999;
}
/* 错误 */
.error-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
}
.error-text {
font-size: 30rpx;
color: #999;
margin-bottom: 32rpx;
}
.error-btn {
padding: 16rpx 48rpx;
background: #e02e24;
color: #fff;
font-size: 28rpx;
font-weight: 600;
border-radius: 40rpx;
}
/* 商品大图 */
.hero {
position: relative;
width: 100%;
height: 750rpx;
background: #fff;
}
.hero-img {
width: 100%;
height: 100%;
}
.hero-badge {
position: absolute;
top: 24rpx;
left: 24rpx;
padding: 8rpx 16rpx;
border-radius: 6rpx;
background: rgba(224, 46, 36, 0.85);
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.hero-badge-text {
font-size: 20rpx;
color: #fff;
font-weight: 600;
line-height: 1;
}
/* 价格区 - 拼多多红底 */
.price-block {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 24rpx 28rpx;
background: linear-gradient(135deg, #e02e24, #f05545);
}
.price-left {
display: flex;
align-items: baseline;
}
.price-sym {
font-size: 28rpx;
color: #fff;
font-weight: 700;
}
.price-int {
font-size: 60rpx;
color: #fff;
font-weight: 800;
line-height: 1;
margin-left: 2rpx;
}
.price-dec {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
font-weight: 700;
}
.price-right {
display: flex;
align-items: center;
}
.sold-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
background: rgba(0, 0, 0, 0.15);
padding: 6rpx 16rpx;
border-radius: 20rpx;
}
/* 标题区 */
.title-block {
background: #fff;
padding: 24rpx 28rpx 20rpx;
}
.title-tags {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.tag-hot {
font-size: 20rpx;
color: #fff;
background: #e02e24;
padding: 4rpx 12rpx;
border-radius: 4rpx;
font-weight: 600;
margin-right: 12rpx;
}
.title-text {
font-size: 32rpx;
font-weight: 600;
color: #222;
line-height: 1.6;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
/* 服务保障条 */
.service-strip {
display: flex;
flex-wrap: wrap;
gap: 0;
padding: 16rpx 28rpx;
background: #fff;
margin-bottom: 16rpx;
}
.service-item {
display: flex;
align-items: center;
margin-right: 24rpx;
margin-bottom: 8rpx;
}
.service-check {
font-size: 20rpx;
color: #e02e24;
font-weight: 700;
margin-right: 4rpx;
}
.service-label {
font-size: 22rpx;
color: #666;
}
/* 选择SKU入口 */
.sku-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 24rpx 28rpx;
margin-bottom: 16rpx;
}
.sku-trigger-left {
display: flex;
align-items: center;
}
.sku-label {
font-size: 26rpx;
color: #999;
margin-right: 16rpx;
}
.sku-value {
font-size: 26rpx;
color: #333;
}
.sku-arrow {
font-size: 28rpx;
color: #ccc;
margin-left: 12rpx;
}
/* 详情卡片 */
.detail-card {
background: #fff;
margin-bottom: 16rpx;
padding: 28rpx;
}
.detail-card-head {
margin-bottom: 20rpx;
}
.detail-card-title {
font-size: 30rpx;
font-weight: 700;
color: #222;
position: relative;
padding-left: 16rpx;
}
.detail-card-title::before {
content: '';
position: absolute;
left: 0;
top: 4rpx;
width: 6rpx;
height: 28rpx;
border-radius: 3rpx;
background: #e02e24;
}
.detail-card-body {
font-size: 26rpx;
color: #555;
line-height: 1.8;
white-space: pre-line;
}
.rule-img {
width: 100%;
border-radius: 8rpx;
display: block;
}
/* 底部留白 */
.bottom-space {
height: 40rpx;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
background: #fff;
padding-bottom: env(safe-area-inset-bottom);
border-top: 1rpx solid #f0f0f0;
}
.bottom-bar-inner {
display: flex;
align-items: stretch;
height: 100rpx;
}
.bar-icon-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 28rpx;
height: 96rpx;
}
.bar-icon-text {
font-size: 22rpx;
color: #666;
margin-top: 4rpx;
}
.bar-buy-btn {
width: 50%;
height: 100%;
border-radius: 0;
display: flex;
align-items: center;
justify-content: center;
background: #e02e24;
margin-left: auto;
}
.bar-buy-btn--disabled {
background: #ccc;
}
.bar-buy-btn--hover {
opacity: 0.9;
transform: scale(0.98);
}
.bar-buy-text {
font-size: 30rpx;
font-weight: 700;
color: #fff;
margin-right: 8rpx;
}
.bar-buy-price {
font-size: 26rpx;
font-weight: 700;
color: rgba(255, 255, 255, 0.9);
}

View File

@@ -1,547 +1,332 @@
// pages/submit/submit.js
const app = getApp()
import request from '../../utils/request.js'
// 引入弹窗服务(路径请根据项目实际调整)
import PopupService from '../../services/popupService.js'
Page({
data: {
// 全局变量
ossImageUrl: '',
apiBaseUrl: '',
// 从商品详情页传入的数据
shangpinData: null,
shangpinId: '',
// 表单数据
nicheng: '', // 游戏昵称/ID
beizhu: '', // 备注信息
zhiding: '', // 指定打手ID新增
jine: 0, // 总金额
// 页面状态
isSubmitting: false,
canSubmit: false,
isPaying: false,
// 支付相关
orderId: '',
payParams: null,
checkCount: 0,
maxCheckCount: 3
},
onLoad(options) {
// 获取全局变量
this.setData({
ossImageUrl: app.globalData.ossImageUrl || '',
apiBaseUrl: app.globalData.apiBaseUrl || ''
})
// 设置页面标题
wx.setNavigationBarTitle({
title: '提交订单'
})
// 解析传递的商品数据
try {
if (options.data) {
const shangpinData = JSON.parse(decodeURIComponent(options.data))
// 处理图片URL拼接完整路径
if (shangpinData.image && !shangpinData.image.startsWith('http')) {
shangpinData.image = this.data.ossImageUrl + shangpinData.image
}
// 处理价格显示
if (shangpinData.price) {
const price = parseFloat(shangpinData.price) || 0
const priceInteger = Math.floor(price)
let priceDecimal = '00'
const decimalPart = (price - priceInteger).toFixed(2)
if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1]
}
shangpinData.priceInteger = priceInteger
shangpinData.priceDecimal = priceDecimal
}
// 设置商品数据
this.setData({
shangpinData: shangpinData,
shangpinId: shangpinData.id || '',
jine: parseFloat(shangpinData.price) || 0,
})
} else {
this.handleDataError('商品数据不完整')
}
} catch (error) {
this.handleDataError('解析商品数据失败')
}
},
onShow() {
// 原有代码:注册通知组件
this.registerNotificationComponent();
// 🆕 进入页面时触发弹窗检查PopupService 会自动判断是否展示)
PopupService.checkAndShow(this, 'tijiao');
},
// pages/submit/submit.js 中添加 onHide 方法(如果已有则合并内容)
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
},
// 新增:注册通知组件
registerNotificationComponent() {
const app = getApp();
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
//console.log('🏪 商城页面注册通知组件');
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
/**
* 处理数据错误
*/
handleDataError(errorMsg) {
wx.showToast({
title: errorMsg || '数据错误',
icon: 'none'
})
setTimeout(() => {
wx.navigateBack()
}, 1500)
},
/**
* 游戏昵称输入事件
*/
onNicknameInput(e) {
const value = e.detail.value.trim()
this.setData({
nicheng: value
}, () => {
this.checkFormValid()
})
},
/**
* 备注信息输入事件
*/
onRemarkInput(e) {
const value = e.detail.value.trim()
this.setData({
beizhu: value
})
},
/**
* 指定打手ID输入事件
*/
onZhidingInput(e) {
const value = e.detail.value.trim()
this.setData({
zhiding: value
})
},
/**
* 检查表单是否有效
*/
checkFormValid() {
const { nicheng } = this.data
const isValid = nicheng && nicheng.length > 0 && nicheng.length <= 15
this.setData({
canSubmit: isValid
})
},
/**
* 提交订单
*/
onSubmitOrder() {
const { nicheng, beizhu, zhiding, jine, shangpinId, canSubmit, isSubmitting } = this.data
// 验证表单
if (!canSubmit) {
wx.showToast({
title: '请填写游戏昵称',
icon: 'none'
})
return
}
// 防止重复提交
if (isSubmitting) {
return
}
this.setData({
isSubmitting: true,
isPaying: false
})
// 显示loading超时设为10秒
wx.showLoading({
title: '创建订单中...',
mask: true
})
const timeoutTimer = setTimeout(() => {
wx.hideLoading()
wx.showToast({
title: '网络超时,请检查您是否已登录',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
}, 10000)
// 构建请求数据添加zhiding字段
const orderData = {
shangpin_id: shangpinId, // 商品ID
nicheng: nicheng, // 游戏昵称
beizhu: beizhu || '', // 备注(可为空)
zhiding: zhiding || '', // 指定打手ID可为空
jine: jine // 金额
}
// 调用后端创建订单接口
request({
url: '/dingdan/xiadan',
method: 'POST',
data: orderData
})
.then(res => {
clearTimeout(timeoutTimer)
wx.hideLoading()
if (res.statusCode === 200 && res.data) {
const response = res.data
if (response.code === 0 && response.data) {
// 保存订单ID和支付参数
const orderId = response.data.dingdanid
const payParams = response.data.payParams || {}
this.setData({
orderId: orderId,
payParams: payParams,
isSubmitting: false,
isPaying: true,
checkCount: 0
})
// 调起微信支付
this.triggerWxPayment(payParams)
} else {
wx.showToast({
title: response.msg || '创建订单失败',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
}
} else {
wx.showToast({
title: '网络错误,请重试',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
}
})
.catch(error => {
clearTimeout(timeoutTimer)
wx.hideLoading()
wx.showToast({
title: '创建订单失败',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
})
},
/**
* 调起微信支付
*/
triggerWxPayment(payParams) {
// 验证支付参数是否完整
if (!payParams || !payParams.timeStamp || !payParams.nonceStr ||
!payParams.package || !payParams.signType || !payParams.paySign) {
wx.showToast({
title: '支付参数不完整',
icon: 'none'
})
this.setData({
isSubmitting: false,
isPaying: false
})
return
}
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success: (res) => {
// 支付成功,开始双重确认
this.onPaymentSuccess()
},
fail: (err) => {
// 根据错误类型处理
if (err.errMsg === 'requestPayment:fail cancel') {
// 用户取消支付
this.handleUserCancel()
} else {
// 其他支付错误
this.handlePaymentFail(err)
}
}
})
},
/**
* 处理用户取消支付
*/
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
})
}
}
})
// pages/submit/submit.js
const app = getApp()
import request from '../../utils/request.js'
import PopupService from '../../services/popupService.js'
Page({
data: {
ossImageUrl: '',
apiBaseUrl: '',
shangpinData: null,
shangpinId: '',
nicheng: '',
beizhu: '',
zhiding: '',
quantity: 1,
jine: 0,
totalPriceInteger: '0',
totalPriceDecimal: '00',
isSubmitting: false,
canSubmit: false,
isPaying: false,
orderId: '',
payParams: null,
checkCount: 0,
maxCheckCount: 3
},
onLoad(options) {
this.setData({
ossImageUrl: app.globalData.ossImageUrl || '',
apiBaseUrl: app.globalData.apiBaseUrl || ''
})
wx.setNavigationBarTitle({ title: '提交订单' })
try {
if (options.data) {
const shangpinData = JSON.parse(decodeURIComponent(options.data))
if (shangpinData.image && !shangpinData.image.startsWith('http')) {
shangpinData.image = this.data.ossImageUrl + shangpinData.image
}
if (shangpinData.price) {
const price = parseFloat(shangpinData.price) || 0
const priceInteger = Math.floor(price)
let priceDecimal = '00'
const decimalPart = (price - priceInteger).toFixed(2)
if (decimalPart > 0) {
priceDecimal = decimalPart.toString().split('.')[1]
}
shangpinData.priceInteger = priceInteger
shangpinData.priceDecimal = priceDecimal
}
this.setData({
shangpinData: shangpinData,
shangpinId: shangpinData.id || '',
jine: parseFloat(shangpinData.price) || 0,
}, () => this.updateTotalPrice())
} else {
this.handleDataError('商品数据不完整')
}
} catch (error) {
this.handleDataError('解析商品数据失败')
}
},
onShow() {
this.registerNotificationComponent()
PopupService.checkAndShow(this, 'tijiao')
},
onHide() {
const popupComp = this.selectComponent('#popupNotice')
if (popupComp && popupComp.cleanup) popupComp.cleanup()
},
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()
}
}
},
updateTotalPrice() {
const total = this.data.jine * this.data.quantity
const intPart = Math.floor(total)
const decPart = (total - intPart).toFixed(2).split('.')[1]
this.setData({
totalPriceInteger: intPart,
totalPriceDecimal: decPart
})
},
onQtyMinus() {
if (this.data.quantity <= 1) return
this.setData({ quantity: this.data.quantity - 1 }, () => this.updateTotalPrice())
},
onQtyPlus() {
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)
},
goBack() {
wx.navigateBack()
},
onNicknameInput(e) {
this.setData({ nicheng: e.detail.value.trim() }, () => this.checkFormValid())
},
onRemarkInput(e) {
this.setData({ beizhu: e.detail.value.trim() })
},
onZhidingInput(e) {
this.setData({ zhiding: e.detail.value.trim() })
},
checkFormValid() {
const { nicheng } = this.data
this.setData({ canSubmit: nicheng && nicheng.length > 0 && nicheng.length <= 15 })
},
onSubmitOrder() {
const { nicheng, beizhu, zhiding, jine, quantity, shangpinId, canSubmit, isSubmitting } = this.data
if (!canSubmit) {
wx.showToast({ title: '请填写游戏昵称', icon: 'none' })
return
}
if (isSubmitting) return
this.setData({ isSubmitting: true, isPaying: false })
wx.showLoading({ title: '创建订单中...', mask: true })
const timeoutTimer = setTimeout(() => {
wx.hideLoading()
wx.showToast({ title: '网络超时,请检查您是否已登录', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
}, 10000)
const orderData = {
shangpin_id: shangpinId,
nicheng: nicheng,
beizhu: beizhu || '',
zhiding: zhiding || '',
jine: jine * quantity,
shuliang: quantity
}
request({
url: '/dingdan/xiadan',
method: 'POST',
data: orderData
})
.then(res => {
clearTimeout(timeoutTimer)
wx.hideLoading()
if (res.statusCode === 200 && res.data) {
const response = res.data
if (response.code === 0 && response.data) {
const orderId = response.data.dingdanid
const payParams = response.data.payParams || {}
this.setData({
orderId: orderId,
payParams: payParams,
isSubmitting: false,
isPaying: true,
checkCount: 0
})
this.triggerWxPayment(payParams)
} else {
wx.showToast({ title: response.msg || '创建订单失败', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
}
} else {
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
}
})
.catch(() => {
clearTimeout(timeoutTimer)
wx.hideLoading()
wx.showToast({ title: '创建订单失败', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
})
},
triggerWxPayment(payParams) {
if (!payParams || !payParams.timeStamp || !payParams.nonceStr ||
!payParams.package || !payParams.signType || !payParams.paySign) {
wx.showToast({ title: '支付参数不完整', icon: 'none' })
this.setData({ isSubmitting: false, isPaying: false })
return
}
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success: () => this.onPaymentSuccess(),
fail: (err) => {
if (err.errMsg === 'requestPayment:fail cancel') {
this.handleUserCancel()
} else {
this.handlePaymentFail(err)
}
}
})
},
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 })
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 })
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) {
if (res.data.code === 0) {
this.handleConfirmSuccess()
} else {
this.handleConfirmError(res.data.msg || '订单状态异常')
}
} else {
this.handleConfirmError('网络错误')
}
})
.catch(() => {
clearTimeout(timeoutTimer)
wx.hideLoading()
this.handleConfirmError('检查失败')
})
},
handleConfirmSuccess() {
wx.showToast({ title: '购买成功!', icon: 'success', duration: 1500 })
setTimeout(() => wx.switchTab({ url: '/pages/index/index' }), 1500)
this.setData({ isSubmitting: false, isPaying: false })
},
handleConfirmError(errorMsg) {
const newCheckCount = this.data.checkCount + 1
if (newCheckCount < this.data.maxCheckCount) {
this.setData({ checkCount: newCheckCount })
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 })
}
}
})

View File

@@ -1,124 +1,132 @@
<!--pages/submit/submit.wxml-->
<view class="submit-page">
<!-- 商品信息区域 -->
<view class="product-section">
<view class="product-card">
<!-- 商品图片 -->
<view class="product-image-box">
<image
src="{{shangpinData && shangpinData.image ? shangpinData.image : ''}}"
mode="aspectFill"
class="product-image"
/>
</view>
<!-- 商品信息 -->
<view class="product-info">
<view class="product-title">
<text class="title-text">{{shangpinData && shangpinData.title ? shangpinData.title : '商品标题'}}</text>
</view>
<!-- 价格信息 -->
<view class="price-info">
<text class="price-label">价格:</text>
<view class="price-value">
<text class="price-icon">¥</text>
<text class="price-integer">{{shangpinData && shangpinData.priceInteger ? shangpinData.priceInteger : '0'}}</text>
<text class="price-decimal">.{{shangpinData && shangpinData.priceDecimal ? shangpinData.priceDecimal : '00'}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 表单区域(左右布局) -->
<view class="form-section">
<view class="form-card">
<!-- 游戏昵称 - 左右布局 -->
<view class="form-item-row">
<view class="item-label-row">
<text class="label-text-bold">昵称</text>
<text class="label-required">*</text>
</view>
<input
class="item-input-row"
type="text"
placeholder="请输入您的昵称"
placeholder-class="input-placeholder-gray"
maxlength="15"
value="{{nicheng}}"
bindinput="onNicknameInput"
/>
</view>
<!-- 备注信息 - 左右布局 -->
<view class="form-item-row">
<view class="item-label-row">
<text class="label-text-bold">备注</text>
</view>
<input
class="item-input-row"
type="text"
placeholder="请补充您要补充的其他联系信息"
placeholder-class="input-placeholder-gray"
maxlength="30"
value="{{beizhu}}"
bindinput="onRemarkInput"
/>
</view>
<!-- 指定打手ID - 左右布局 -->
<view class="form-item-row">
<view class="item-label-row">
<text class="label-text-bold">指定服务者ID(选填)</text>
</view>
<input
class="item-input-row"
type="text"
placeholder="填写您需要指定的服务者"
placeholder-class="input-placeholder-gray"
maxlength="20"
value="{{zhiding}}"
bindinput="onZhidingInput"
/>
</view>
<!-- 支付方式 -->
<view class="payment-item">
<view class="payment-method">
<image src="/images/wechat-pay.png" class="payment-icon" />
<text class="payment-text">微信支付</text>
<view class="payment-checkbox">
<text class="checkbox-text">✓</text>
</view>
</view>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="footer-section">
<view class="total-price">
<text class="total-label">总价:</text>
<view class="total-value">
<text class="price-icon">¥</text>
<text class="price-integer">{{shangpinData && shangpinData.priceInteger ? shangpinData.priceInteger : '0'}}</text>
<text class="price-decimal">.{{shangpinData && shangpinData.priceDecimal ? shangpinData.priceDecimal : '00'}}</text>
</view>
</view>
<view
class="submit-btn {{canSubmit && !isSubmitting ? 'submit-btn-active' : 'submit-btn-disabled'}}"
bindtap="onSubmitOrder"
>
<text class="btn-text">{{isSubmitting ? '提交中...' : '提交订单'}}</text>
<view class="btn-glow"></view>
</view>
</view>
<!-- 全局通知组件 -->
<global-notification id="global-notification" />
<!-- 弹窗公告组件(供 PopupService 调用) -->
<popup-notice id="popupNotice" />
</view>
<!--pages/submit/submit.wxml-->
<view class="page">
<!-- 商品摘要 - 拼多多SKU弹窗顶部风格 -->
<view class="sku-header">
<image class="sku-thumb" src="{{shangpinData.image || ''}}" mode="aspectFill" />
<view class="sku-header-info">
<view class="sku-price-row">
<text class="sku-price-sym">¥</text>
<text class="sku-price-int">{{totalPriceInteger}}</text>
<text class="sku-price-dec">.{{totalPriceDecimal}}</text>
</view>
<text class="sku-stock">库存{{shangpinData.kucun || 0}}件</text>
<text class="sku-selected">已选:{{quantity}}份</text>
</view>
<view class="sku-close" bindtap="goBack">×</view>
</view>
<!-- 购买数量 -->
<view class="sku-section">
<text class="sku-section-title">购买数量</text>
<view class="qty-row">
<view class="qty-btn {{quantity <= 1 ? 'qty-btn--disabled' : ''}}" bindtap="onQtyMinus">-</view>
<text class="qty-val">{{quantity}}</text>
<view class="qty-btn {{quantity >= 99 ? 'qty-btn--disabled' : ''}}" bindtap="onQtyPlus">+</view>
</view>
</view>
<!-- 服务信息 -->
<view class="sku-section">
<text class="sku-section-title">服务信息</text>
<view class="form-field">
<view class="field-top">
<text class="field-name">游戏昵称</text>
<text class="field-required">必填</text>
</view>
<input
class="field-input"
type="text"
placeholder="请输入您的游戏昵称"
placeholder-class="ph"
maxlength="15"
value="{{nicheng}}"
bindinput="onNicknameInput"
/>
</view>
<view class="form-field">
<view class="field-top">
<text class="field-name">备注</text>
<text class="field-optional">选填</text>
</view>
<input
class="field-input"
type="text"
placeholder="补充其他联系信息"
placeholder-class="ph"
maxlength="30"
value="{{beizhu}}"
bindinput="onRemarkInput"
/>
</view>
<view class="form-field">
<view class="field-top">
<text class="field-name">指定服务者</text>
<text class="field-optional">选填</text>
</view>
<input
class="field-input"
type="text"
placeholder="填写指定服务者ID"
placeholder-class="ph"
maxlength="20"
value="{{zhiding}}"
bindinput="onZhidingInput"
/>
</view>
</view>
<!-- 服务保障 -->
<view class="sku-section">
<text class="sku-section-title">服务保障</text>
<view class="guarantee-row">
<view class="guarantee-item">
<text class="g-check">✓</text>
<text class="g-text">极速接单</text>
</view>
<view class="guarantee-item">
<text class="g-check">✓</text>
<text class="g-text">资金担保</text>
</view>
<view class="guarantee-item">
<text class="g-check">✓</text>
<text class="g-text">售后保障</text>
</view>
<view class="guarantee-item">
<text class="g-check">✓</text>
<text class="g-text">隐私保护</text>
</view>
</view>
</view>
<!-- 支付方式 -->
<view class="sku-section">
<text class="sku-section-title">支付方式</text>
<view class="pay-row">
<image src="/images/wechat-pay.png" class="pay-icon" />
<text class="pay-label">微信支付</text>
<view class="pay-check">
<text class="pay-check-icon">✓</text>
</view>
</view>
</view>
<!-- 底部留白 -->
<view class="bottom-space"></view>
<!-- 底部确认按钮 -->
<view class="confirm-bar">
<view
class="confirm-btn {{canSubmit && !isSubmitting ? 'confirm-btn--active' : 'confirm-btn--disabled'}}"
bindtap="onSubmitOrder"
hover-class="confirm-btn--hover"
>
{{isSubmitting ? '提交中...' : '确定'}}
</view>
</view>
<global-notification id="global-notification" />
<popup-notice id="popupNotice" />
</view>

View File

@@ -1,280 +1,296 @@
/* pages/submit/submit.wxss */
.submit-page {
display: flex;
flex-direction: column;
min-height: 100vh;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f3ff 100%);
box-sizing: border-box;
}
/* 商品信息区域 */
.product-section {
padding: 30rpx 30rpx 20rpx 30rpx;
flex-shrink: 0;
}
.product-card {
background: white;
border-radius: 20rpx;
padding: 30rpx;
display: flex;
align-items: center;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06);
}
.product-image-box {
width: 160rpx;
height: 160rpx;
border-radius: 16rpx;
overflow: hidden;
background: #f5f5f5;
flex-shrink: 0;
margin-right: 24rpx;
}
.product-image {
width: 100%;
height: 100%;
}
.product-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 160rpx;
}
.product-title {
margin-bottom: 20rpx;
}
.title-text {
font-size: 30rpx;
font-weight: 600;
color: #222;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.price-info {
display: flex;
align-items: baseline;
}
.price-label {
font-size: 26rpx;
color: #666;
font-weight: 500;
}
.price-value {
display: flex;
align-items: baseline;
}
.price-icon {
font-size: 28rpx;
color: #ff4d4f;
font-weight: 600;
}
.price-integer {
font-size: 38rpx;
color: #ff4d4f;
font-weight: 700;
margin-left: 4rpx;
}
.price-decimal {
font-size: 28rpx;
color: #ff4d4f;
font-weight: 600;
}
/* 表单区域 */
.form-section {
padding: 0 30rpx 20rpx 30rpx;
flex-shrink: 0;
}
.form-card {
background: white;
border-radius: 20rpx;
padding: 30rpx;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.05);
}
.form-item-row {
display: flex;
align-items: center;
padding: 25rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.form-item-row:last-child {
border-bottom: none;
}
.item-label-row {
width: 220rpx;
flex-shrink: 0;
display: flex;
align-items: center;
}
.label-text-bold {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.label-required {
font-size: 30rpx;
color: #ff4d4f;
margin-left: 8rpx;
}
.item-input-row {
flex: 1;
font-size: 28rpx;
color: #222;
height: 60rpx;
line-height: 60rpx;
padding: 0 20rpx;
text-align: right;
background: #fafafa;
border-radius: 10rpx;
}
.input-placeholder-gray {
color: #999;
font-size: 28rpx;
}
/* 支付方式 */
.payment-item {
margin-top: 30rpx;
padding-top: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.payment-method {
display: flex;
align-items: center;
padding: 20rpx 0;
}
.payment-icon {
width: 48rpx;
height: 48rpx;
margin-right: 20rpx;
}
.payment-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
flex: 1;
}
.payment-checkbox {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #52c41a;
display: flex;
align-items: center;
justify-content: center;
}
.checkbox-text {
color: white;
font-size: 28rpx;
font-weight: 600;
}
/* 底部操作栏 */
.footer-section {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: white;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
margin-top: auto;
flex-shrink: 0;
}
.total-price {
display: flex;
align-items: baseline;
flex: 1;
}
.total-label {
font-size: 30rpx;
color: #333;
font-weight: 600;
}
.total-value {
display: flex;
align-items: baseline;
}
.submit-btn {
width: 240rpx;
height: 88rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transition: all 0.3s;
}
.submit-btn-active {
background: linear-gradient(135deg, #52c41a, #389e0d);
box-shadow: 0 8rpx 32rpx rgba(255, 77, 79, 0.4);
}
.submit-btn-disabled {
background: #ccc;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
.submit-btn-active:active {
transform: scale(0.98);
box-shadow: 0 4rpx 20rpx rgba(255, 77, 79, 0.5);
}
.btn-text {
font-size: 32rpx;
color: white;
font-weight: 600;
position: relative;
z-index: 2;
}
.btn-glow {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transform: translateX(-100%);
animation: btnGlow 3s infinite;
}
@keyframes btnGlow {
0% { transform: translateX(-100%); }
50% { transform: translateX(100%); }
100% { transform: translateX(100%); }
}
/* pages/submit/submit.wxss */
page {
background: #f2f2f2;
}
.page {
min-height: 100vh;
padding-bottom: 140rpx;
box-sizing: border-box;
}
/* SKU头部 - 拼多多弹窗风格 */
.sku-header {
display: flex;
background: #fff;
padding: 24rpx;
position: relative;
margin-bottom: 16rpx;
}
.sku-thumb {
width: 180rpx;
height: 180rpx;
border-radius: 12rpx;
flex-shrink: 0;
margin-right: 20rpx;
background: #f5f5f5;
}
.sku-header-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 4rpx 0;
}
.sku-price-row {
display: flex;
align-items: baseline;
}
.sku-price-sym {
font-size: 28rpx;
color: #e02e24;
font-weight: 700;
}
.sku-price-int {
font-size: 48rpx;
color: #e02e24;
font-weight: 800;
line-height: 1;
margin-left: 2rpx;
}
.sku-price-dec {
font-size: 28rpx;
color: #e02e24;
font-weight: 700;
}
.sku-stock {
font-size: 24rpx;
color: #999;
}
.sku-selected {
font-size: 24rpx;
color: #666;
}
.sku-close {
position: absolute;
top: 16rpx;
right: 16rpx;
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;
}
/* SKU区块 */
.sku-section {
background: #fff;
padding: 24rpx 28rpx;
margin-bottom: 16rpx;
}
.sku-section-title {
font-size: 28rpx;
font-weight: 700;
color: #222;
margin-bottom: 20rpx;
display: block;
}
/* 数量选择器 */
.qty-row {
display: flex;
align-items: center;
}
.qty-btn {
width: 64rpx;
height: 64rpx;
border-radius: 8rpx;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 700;
color: #333;
border: 1rpx solid #e8e8e8;
}
.qty-btn--disabled {
color: #ccc;
border-color: #f0f0f0;
background: #fafafa;
}
.qty-val {
min-width: 80rpx;
text-align: center;
font-size: 32rpx;
font-weight: 700;
color: #222;
margin: 0 20rpx;
}
/* 表单字段 */
.form-field {
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.form-field:last-child {
border-bottom: none;
padding-bottom: 0;
}
.field-top {
display: flex;
align-items: center;
margin-bottom: 10rpx;
}
.field-name {
font-size: 26rpx;
font-weight: 600;
color: #333;
}
.field-required {
font-size: 20rpx;
color: #e02e24;
background: #fff1f0;
padding: 2rpx 10rpx;
border-radius: 4rpx;
margin-left: 10rpx;
}
.field-optional {
font-size: 20rpx;
color: #bbb;
margin-left: 10rpx;
}
.field-input {
width: 100%;
height: 72rpx;
background: #f7f7f7;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 26rpx;
color: #333;
border: 1rpx solid #eee;
box-sizing: border-box;
}
.ph {
color: #bbb;
font-size: 26rpx;
}
/* 服务保障 */
.guarantee-row {
display: flex;
flex-wrap: wrap;
gap: 12rpx 20rpx;
}
.guarantee-item {
display: flex;
align-items: center;
}
.g-check {
font-size: 20rpx;
color: #e02e24;
font-weight: 700;
margin-right: 4rpx;
}
.g-text {
font-size: 24rpx;
color: #666;
}
/* 支付方式 */
.pay-row {
display: flex;
align-items: center;
}
.pay-icon {
width: 44rpx;
height: 44rpx;
margin-right: 16rpx;
}
.pay-label {
flex: 1;
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.pay-check {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
background: #52c41a;
display: flex;
align-items: center;
justify-content: center;
}
.pay-check-icon {
color: #fff;
font-size: 22rpx;
font-weight: 700;
}
/* 底部留白 */
.bottom-space {
height: 20rpx;
}
/* 底部确认按钮 */
.confirm-bar {
position: fixed;
bottom: 0;
right: 0;
width: 50%;
height: 100rpx;
background: #fff;
padding-bottom: env(safe-area-inset-bottom);
z-index: 9999;
display: flex;
}
.confirm-btn {
width: 100%;
height: 100%;
border-radius: 0;
display: flex;
align-items: center;
justify-content: center;
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);
}

View File

@@ -1,169 +1,159 @@
// pages/verify/verify.js
import request from '../../utils/request.js'
import { enterLockedRole } from '../../utils/primary-role.js'
const API_MAP = {
dashou: '/yonghu/dashouzhuce',
shangjia: '/yonghu/shangjiahuce',
guanshi: '/yonghu/guanshizhuce',
zuzhang: '/yonghu/zuzhangzhuce',
kaoheguan: '/dengji/khgzc'
}
const STATUS_KEY_MAP = {
dashou: 'dashoustatus',
shangjia: 'shangjiastatus',
guanshi: 'guanshistatus',
zuzhang: 'zuzhangstatus',
kaoheguan: 'kaoheguanstatus'
}
const NAME_MAP = {
dashou: '接单员',
shangjia: '商家',
guanshi: '管事',
zuzhang: '组长',
kaoheguan: '考核官'
}
Page({
data: {
authType: '',
authName: '',
pageState: 'need_register',
inviteCode: '',
isLoading: false,
},
onLoad(options) {
const type = options.type || ''
if (!API_MAP[type]) {
wx.showToast({ title: '认证类型错误', icon: 'none' })
setTimeout(() => wx.navigateBack(), 1500)
return
}
this.setData({ authType: type, authName: NAME_MAP[type] })
this.checkStatus()
},
onShow() {
if (this.data.authType) this.checkStatus()
},
checkStatus() {
const key = STATUS_KEY_MAP[this.data.authType]
const val = wx.getStorageSync(key)
if (val === 1) {
this.setData({ pageState: 'done' })
} else {
this.setData({ pageState: 'need_register' })
}
},
onInviteCodeInput(e) {
this.setData({ inviteCode: e.detail.value.trim() })
},
async onRegister() {
const { inviteCode, authType } = this.data
if (!inviteCode) {
wx.showToast({ title: '请输入邀请码', icon: 'none' })
return
}
if (inviteCode.length > 100) {
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' })
return
}
this.setData({ isLoading: true })
wx.showLoading({ title: '注册中...', mask: true })
try {
const res = await request({
url: API_MAP[authType],
method: 'POST',
data: { inviteCode }
})
if (res && res.data.code === 200) {
const data = res.data.data || res.data
const app = getApp()
// 缓存身份状态
const key = STATUS_KEY_MAP[authType]
if (data[key] !== undefined) {
wx.setStorageSync(key, data[key])
app.globalData[key] = data[key]
} else {
wx.setStorageSync(key, 1)
app.globalData[key] = 1
}
// 更新其他可能返回的身份状态字段
const allKeys = ['dashoustatus', 'shangjiastatus', 'guanshistatus', 'zuzhangstatus', 'kaoheguanstatus']
allKeys.forEach(k => {
if (data[k] !== undefined && k !== key) {
wx.setStorageSync(k, data[k])
app.globalData[k] = data[k]
}
})
// 保存各身份详细信息
if (authType === 'dashou') {
const fields = ['dashounicheng','zhanghaostatus','yongjin','zonge','yajin','chenghao','jifen','clumber','chengjiaoliang','zaixianzhuangtai']
fields.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] })
} else if (authType === 'shangjia') {
app.globalData.shangjia = app.globalData.shangjia || {}
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] })
} else if (authType === 'guanshi') {
app.globalData.guanshi = app.globalData.guanshi || {}
const gf = ['gszhstatus','yaoqingzongshu','fenyongzonge','fenyongtixian','yichongzhiDashou']
gf.forEach(f => { if (data[f] !== undefined) app.globalData.guanshi[f] = data[f] })
} else if (authType === 'zuzhang') {
const zf = ['ketixian','guanshiCount','fenhongZonge']
zf.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] })
} else if (authType === 'kaoheguan') {
app.globalData.kaoheguan = app.globalData.kaoheguan || {}
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] })
}
wx.hideLoading()
this.setData({ isLoading: false, pageState: 'done' })
wx.showToast({ title: '认证成功', icon: 'success' })
if (authType === 'dashou' || authType === 'shangjia') {
setTimeout(() => enterLockedRole(authType), 1200)
} else if (authType === 'guanshi') {
setTimeout(() => {
const pages = getCurrentPages()
if (pages.length > 1) {
wx.navigateBack()
} else {
wx.redirectTo({ url: '/pages/fighter/fighter' })
}
}, 800)
}
} else {
wx.hideLoading()
this.setData({ isLoading: false })
wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none' })
}
} catch (err) {
wx.hideLoading()
this.setData({ isLoading: false })
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 })
}
})
// pages/verify/verify.js
import { createPage, request } from '../../utils/base-page.js'
import { enterLockedRole } from '../../utils/primary-role.js'
const API_MAP = {
dashou: '/yonghu/dashouzhuce',
shangjia: '/yonghu/shangjiahuce',
guanshi: '/yonghu/guanshizhuce',
zuzhang: '/yonghu/zuzhangzhuce',
kaoheguan: '/dengji/khgzc'
}
const STATUS_KEY_MAP = {
dashou: 'dashoustatus',
shangjia: 'shangjiastatus',
guanshi: 'guanshistatus',
zuzhang: 'zuzhangstatus',
kaoheguan: 'kaoheguanstatus'
}
const NAME_MAP = {
dashou: '接单员',
shangjia: '商家',
guanshi: '管事',
zuzhang: '组长',
kaoheguan: '考核官'
}
Page(createPage({
data: {
authType: '',
authName: '',
pageState: 'need_register',
inviteCode: '',
isLoading: false,
},
onLoad(options) {
const type = options.type || ''
if (!API_MAP[type]) {
wx.showToast({ title: '认证类型错误', icon: 'none' })
setTimeout(() => wx.navigateBack(), 1500)
return
}
this.setData({ authType: type, authName: NAME_MAP[type] })
this.checkStatus()
},
onShow() {
if (this.data.authType) this.checkStatus()
},
checkStatus() {
const key = STATUS_KEY_MAP[this.data.authType]
const val = wx.getStorageSync(key)
if (val === 1) {
this.setData({ pageState: 'done' })
} else {
this.setData({ pageState: 'need_register' })
}
},
onInviteCodeInput(e) {
this.setData({ inviteCode: e.detail.value.trim() })
},
async onRegister() {
const { inviteCode, authType } = this.data
if (!inviteCode) {
wx.showToast({ title: '请输入邀请码', icon: 'none' })
return
}
if (inviteCode.length > 100) {
wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' })
return
}
this.setData({ isLoading: true })
wx.showLoading({ title: '注册中...', mask: true })
try {
const res = await request({
url: API_MAP[authType],
method: 'POST',
data: { inviteCode }
})
if (res && res.data.code === 200) {
const data = res.data.data || res.data
const app = getApp()
// 缓存身份状态
const key = STATUS_KEY_MAP[authType]
if (data[key] !== undefined) {
wx.setStorageSync(key, data[key])
app.globalData[key] = data[key]
} else {
wx.setStorageSync(key, 1)
app.globalData[key] = 1
}
// 更新其他可能返回的身份状态字段
const allKeys = ['dashoustatus', 'shangjiastatus', 'guanshistatus', 'zuzhangstatus', 'kaoheguanstatus']
allKeys.forEach(k => {
if (data[k] !== undefined && k !== key) {
wx.setStorageSync(k, data[k])
app.globalData[k] = data[k]
}
})
// 保存各身份详细信息
if (authType === 'dashou') {
const fields = ['dashounicheng','zhanghaostatus','yongjin','zonge','yajin','chenghao','jifen','clumber','chengjiaoliang','zaixianzhuangtai']
fields.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] })
} else if (authType === 'shangjia') {
app.globalData.shangjia = app.globalData.shangjia || {}
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] })
} else if (authType === 'guanshi') {
app.globalData.guanshi = app.globalData.guanshi || {}
const gf = ['gszhstatus','yaoqingzongshu','fenyongzonge','fenyongtixian','yichongzhiDashou']
gf.forEach(f => { if (data[f] !== undefined) app.globalData.guanshi[f] = data[f] })
} else if (authType === 'zuzhang') {
const zf = ['ketixian','guanshiCount','fenhongZonge']
zf.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] })
} else if (authType === 'kaoheguan') {
app.globalData.kaoheguan = app.globalData.kaoheguan || {}
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] })
}
wx.hideLoading()
this.setData({ isLoading: false, pageState: 'done' })
wx.showToast({ title: '认证成功', icon: 'success' })
if (authType === 'dashou' || authType === 'shangjia') {
setTimeout(() => enterLockedRole(authType), 1200)
} else if (authType === 'guanshi') {
setTimeout(() => {
const pages = getCurrentPages()
if (pages.length > 1) {
wx.navigateBack()
} else {
wx.redirectTo({ url: '/pages/fighter/fighter' })
}
}, 800)
}
} else {
wx.hideLoading()
this.setData({ isLoading: false })
wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none' })
}
} catch (err) {
wx.hideLoading()
this.setData({ isLoading: false })
wx.showToast({ title: err.message || '注册失败', icon: 'none' })
}
},
}))

View File

@@ -1,72 +1,54 @@
// pages/withdraw/withdraw.js
import request from '../../utils/request.js';
import PopupService from '../../services/popupService.js';
const app = getApp();
Page({
data: {
pageMode: 0,
pageOptions: {},
loadError: false,
},
onLoad(options) {
this.setData({
pageOptions: options || {}
});
this.fetchPageMode();
},
onShow() {
this.registerNotificationComponent();
PopupService.checkAndShow(this, 'tixian');
},
onHide() {
const popupComp = this.selectComponent('#popupNotice');
if (popupComp && popupComp.cleanup) {
popupComp.cleanup();
}
},
// 注册通知组件
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
async fetchPageMode() {
try {
const res = await request({
url: '/peizhi/hqtxzsym',
method: 'POST'
});
if (res.statusCode === 200 && res.data && res.data.code === 0) {
const mode = parseInt(res.data.data.mode);
this.setData({
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() {}
});
// pages/withdraw/withdraw.js
import { createPage, request } from '../../utils/base-page.js';
import PopupService from '../../services/popupService.js';
const app = getApp();
Page(createPage({
data: {
pageMode: 0,
pageOptions: {},
loadError: false,
},
onLoad(options) {
this.setData({
pageOptions: options || {}
});
this.fetchPageMode();
},
onShow() {
this.registerNotificationComponent();
PopupService.checkAndShow(this, 'tixian');
},
async fetchPageMode() {
try {
const res = await request({
url: '/peizhi/hqtxzsym',
method: 'POST'
});
if (res.statusCode === 200 && res.data && res.data.code === 0) {
const mode = parseInt(res.data.data.mode);
this.setData({
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() {}
}))