diff --git a/pages/fighter-order-detail/fighter-order-detail.js b/pages/fighter-order-detail/fighter-order-detail.js index 624e051..9eed57b 100644 --- a/pages/fighter-order-detail/fighter-order-detail.js +++ b/pages/fighter-order-detail/fighter-order-detail.js @@ -2,7 +2,7 @@ const app = getApp() import request from '../../utils/request.js' import connectionManager from '../../utils/xiaoxilj.js' -import { attachAutoSettleFields } from '../../utils/auto-settle-display.js' +import { bindDetailAutoSettle, stopAutoSettleTicker } from '../../utils/auto-settle-display.js' const COS = require('../../utils/cos-wx-sdk-v5.min.js') Page({ @@ -23,6 +23,7 @@ Page({ shangjia_nicheng: '', shangjia_liuyan: '', tuikuan_liyou: '', + tuikuan_tupian: [], chufa_liyou: '', chufa_zhuangtai: 0, chufa_jieguo: '', @@ -34,6 +35,8 @@ Page({ }, autoSettleTip: '', autoSettleExpireAt: '', + autoSettleClock: '', + autoSettleRemainText: '', zhuangtaiYanseMap: { 1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#FFF3E0', 5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#FFF8E1' @@ -70,6 +73,8 @@ Page({ team: null, isLeader: false, + canSubmit: false, + canModify: false, teamStatusText: '', teamShareText: '', showTeamForm: false, @@ -89,14 +94,28 @@ Page({ this.jiexiTiaozhuanCanshu(options) }, onHide() { + stopAutoSettleTicker(this); const popupComp = this.selectComponent('#popupNotice'); if (popupComp && popupComp.cleanup) { popupComp.cleanup(); } }, + + onUnload() { + stopAutoSettleTicker(this); + }, onShow() { this.registerNotificationComponent(); + if (this.data._autoSettleEligible) { + bindDetailAutoSettle(this, { + zhuangtai: this.data.jibenShuju.zhuangtai, + auto_settle_eligible: this.data._autoSettleEligible, + auto_expire_at: this.data._autoExpireAt, + auto_expire_at_ts: this.data._autoExpireAtTs, + auto_settle_remain_seconds: this.data._autoSettleRemainBase, + }); + } }, registerNotificationComponent() { @@ -189,6 +208,14 @@ Page({ 'xiangxiShuju.shangjia_nicheng': data.shangjia_nicheng || '', 'xiangxiShuju.shangjia_liuyan': data.shangjia_liuyan || '', 'xiangxiShuju.tuikuan_liyou': data.tuikuan_liyou || '', + 'xiangxiShuju.tuikuan_tupian': (data.tuikuan_tupian || []).map((url) => { + if (url && !String(url).startsWith('http')) { + const oss = app.globalData.ossImageUrl || '' + const path = String(url).startsWith('/') ? String(url).substring(1) : String(url) + return oss + path + } + return url + }), 'xiangxiShuju.chufa_liyou': data.chufa_liyou || '', 'xiangxiShuju.chufa_zhuangtai': data.chufa_zhuangtai || 0, 'xiangxiShuju.chufa_jieguo': data.chufa_jieguo || '', @@ -202,17 +229,14 @@ Page({ 'liuyan': data.dashou_liuyan || '', 'jibenShuju.fadanpingtai': data.fadanpingtai || 0 }) - const settled = attachAutoSettleFields({ + bindDetailAutoSettle(this, { zhuangtai: data.zhuangtai, auto_settle_eligible: data.auto_settle_eligible, auto_expire_at: data.auto_expire_at, + auto_expire_at_ts: data.auto_expire_at_ts, auto_settle_remain_seconds: data.auto_settle_remain_seconds, }) - this.setData({ - autoSettleTip: settled.autoSettleTip || '', - autoSettleExpireAt: settled.autoSettleExpireAt || '', - }) - this.applyTeamData(data.team || null, data.fuwudashou_id || '') + this.applyTeamData(data.team || null, data.fuwudashou_id || '', data) const currentStatus = data.zhuangtai || 0 const showRefund = [4,5,6].includes(currentStatus) this.setData({ showRefundInfo: showRefund }) @@ -257,6 +281,12 @@ Page({ if (!currentUrl) return wx.previewImage({ current: currentUrl, urls: urls.length > 0 ? urls : [currentUrl] }) }, + previewTuikuanTupian(e) { + const currentUrl = e.currentTarget.dataset.url + const urls = this.data.xiangxiShuju.tuikuan_tupian || [] + if (!currentUrl) return + wx.previewImage({ current: currentUrl, urls: urls.length > 0 ? urls : [currentUrl] }) + }, getFileExtension(filePath) { const lastDotIndex = filePath.lastIndexOf('.') if (lastDotIndex === -1) return '.jpg' @@ -348,38 +378,9 @@ Page({ }) }, - // 组队群:固定进 group_{订单号}(入队时已订阅) + // 组队沟通:复用抢单配对群,不另开 group_{订单} goToTeamGroupChat() { - const uid = wx.getStorageSync('uid') - const orderId = this.data.jibenShuju.dingdan_id - if (!uid || !orderId) { - wx.showToast({ title: '信息不完整', icon: 'none' }) - return - } - const groupId = `group_${orderId}` - const currentUser = { - id: 'Ds' + uid, - name: wx.getStorageSync('nicheng') || app.globalData.dashouNicheng || '打手', - avatar: '', - } - const payload = encodeURIComponent(JSON.stringify({ - groupId, - orderId, - groupName: (this.data.jibenShuju.nicheng || '订单') + ' · 组队群', - orderDesc: this.data.jibenShuju.jieshao || '', - orderJine: this.data.jibenShuju.jine || '', - orderZhuangtai: this.data.jibenShuju.zhuangtai, - currentUserId: currentUser.id, - currentUserName: currentUser.name, - isCross: this.data.jibenShuju.fadanpingtai || 0, - })) - wx.navigateTo({ - url: `/pages/group-chat/group-chat?data=${payload}`, - fail: (err) => { - console.error('进入组队群失败', err) - wx.showToast({ title: '无法打开群聊', icon: 'none' }) - }, - }) + this.goToChatWithBoss() }, async getCOSZhengshu() { @@ -485,6 +486,10 @@ Page({ }, tijiaoDingdan() { + if (!this.data.canSubmit && !this.data.isLeader) { + wx.showToast({ title: '仅队长可提交交付', icon: 'none' }) + return + } this.showAnnouncementAndConfirm() }, @@ -616,6 +621,10 @@ Page({ return fullUrl }, enterModify() { + if (!this.data.canModify && !this.data.isLeader) { + wx.showToast({ title: '仅队长可修改交付', icon: 'none' }) + return + } const { dashou_jiaofu, dashou_liuyan } = this.data.xiangxiShuju this.setData({ isModifying: true, @@ -789,19 +798,32 @@ Page({ } }, - applyTeamData(team, playerId) { + applyTeamData(team, playerId, detailData) { const uid = String(wx.getStorageSync('uid') || '') const statusMap = { 1: '招募中', 2: '已锁定', 3: '已结束', 4: '已取消', 5: '已作废' } const pid = String(playerId || this.data.xiangxiShuju.fuwudashou_id || '') + const d = detailData || {} let isLeader = false - if (team) { + if (typeof d.is_team_leader === 'boolean') { + isLeader = d.is_team_leader + } else if (team) { isLeader = String(team.leader_uid || '') === uid } else { isLeader = !!pid && pid === uid } + const zt = Number(d.zhuangtai != null ? d.zhuangtai : this.data.jibenShuju.zhuangtai) + let canSubmit = typeof d.can_submit === 'boolean' ? d.can_submit : (isLeader && zt === 2) + let canModify = typeof d.can_modify === 'boolean' ? d.can_modify : (isLeader && (zt === 4 || zt === 8)) + // 有组队时强制仅队长 + if (team && !isLeader) { + canSubmit = false + canModify = false + } this.setData({ team: team || null, isLeader, + canSubmit, + canModify, teamStatusText: team ? (statusMap[team.status] || '未知') : '', teamShareText: team ? Number(team.display_share_amount || 0).toFixed(2) : '', }) diff --git a/pages/fighter-order-detail/fighter-order-detail.wxml b/pages/fighter-order-detail/fighter-order-detail.wxml index c157587..1935c70 100644 --- a/pages/fighter-order-detail/fighter-order-detail.wxml +++ b/pages/fighter-order-detail/fighter-order-detail.wxml @@ -100,9 +100,13 @@ {{zhuangtaiWenziMap[jibenShuju.zhuangtai] || '未知状态'}} - + 自动结算: - {{autoSettleTip}}({{autoSettleExpireAt}}) + + {{autoSettleClock}} + {{autoSettleTip}} + 到期 {{autoSettleExpireAt}} + @@ -157,7 +161,7 @@ - 进入组队群聊 + 进入订单群聊 关停招募 @@ -205,14 +209,28 @@ 退款信息 - + 退款理由: {{xiangxiShuju.tuikuan_liyou}} + + 相关图片: + + + + + + @@ -254,7 +272,7 @@ - 我的交付 + {{isLeader ? '订单交付' : '订单交付(只读)'}} @@ -286,8 +304,8 @@ - - + + 修改交付 @@ -353,8 +371,8 @@ - - + + 提交订单 @@ -421,8 +439,8 @@ - - + + - - + + - + {{isSubmitting ? '提交中...' : '立即提交'}} + + 组队中仅队长可提交交付,请等待队长操作 + diff --git a/utils/deal-stat-display.js b/utils/deal-stat-display.js new file mode 100644 index 0000000..31dbd38 --- /dev/null +++ b/utils/deal-stat-display.js @@ -0,0 +1,111 @@ +/** + * 成交指标展示:后端有样本就展示(含真实 0%),无样本不造假。 + * - 成交率/罚款率:有 rate / rate_text 即展示 + * - 均时:有 avg_deal_hours_text 才展示 + */ + +function parseRate(textOrNum) { + if (textOrNum == null || textOrNum === '') return null; + if (typeof textOrNum === 'number') { + return Number.isFinite(textOrNum) ? textOrNum : null; + } + const s = String(textOrNum).trim().replace(/%/g, ''); + const n = Number(s); + return Number.isFinite(n) ? n : null; +} + +function formatRateText(rate, fallbackText) { + if (fallbackText) return fallbackText; + const pct = parseRate(rate); + if (pct == null) return ''; + const asPercent = pct > 1 ? pct : pct * 100; + return `${asPercent.toFixed(1)}%`; +} + +function hasRateValue(rate, text) { + if (text != null && String(text).trim() !== '') return true; + return rate != null && rate !== ''; +} + +/** + * 把接口 data 收成列表顶栏可用结构;全无可展示则返回 null + */ +export function sanitizeMyDealStat(raw, role = 'merchant') { + if (!raw || typeof raw !== 'object') return null; + const out = { role: raw.role || role }; + const chips = []; + + const dealRate = raw.deal_rate != null ? raw.deal_rate : raw.deal_rate_text; + if (hasRateValue(raw.deal_rate, raw.deal_rate_text)) { + const pct = parseRate(dealRate); + out.deal_rate = pct == null ? 0 : (pct > 1 ? pct / 100 : pct); + out.deal_rate_text = formatRateText(dealRate, raw.deal_rate_text); + out.deal_label = '成交率'; + chips.push({ k: 'deal', t: `成交率 ${out.deal_rate_text}` }); + } + + const fineKey = role === 'dashou' ? 'fined_rate' : 'fine_rate'; + const fineTextKey = role === 'dashou' ? 'fined_rate_text' : 'fine_rate_text'; + const fineRate = raw[fineKey] != null + ? raw[fineKey] + : (raw[fineTextKey] || raw.fine_rate_text || raw.fined_rate_text); + const fineText = raw[fineTextKey] || raw.fine_rate_text || raw.fined_rate_text; + if (hasRateValue(raw[fineKey] != null ? raw[fineKey] : raw.fine_rate, fineText)) { + const finePct = parseRate(fineRate); + out[fineKey] = finePct == null ? 0 : (finePct > 1 ? finePct / 100 : finePct); + out[fineTextKey] = formatRateText(fineRate, fineText); + out.fine_label = role === 'dashou' ? '被罚款率' : '罚款率'; + chips.push({ + k: 'fine', + t: `${out.fine_label} ${out[fineTextKey]}`, + }); + } + + if (raw.avg_deal_hours_text) { + out.avg_deal_hours_text = raw.avg_deal_hours_text; + out.avg_deal_hours = raw.avg_deal_hours; + chips.push({ k: 'avg', t: `均时 ${raw.avg_deal_hours_text}`, hl: true }); + } + + if (!chips.length) return null; + out.chips = chips; + out.order_count = raw.order_count; + out.completed_count = raw.completed_count; + return out; +} + +/** + * 抢单池/组队卡片芯片:后端给了哪个字段就展示哪个(含 0%) + */ +export function buildDealStatChips(stat, opts = {}) { + if (!stat || typeof stat !== 'object') return []; + const dealLabel = opts.dealLabel || '成交率'; + const fineLabel = opts.fineLabel || '罚款率'; + const chips = []; + + if (hasRateValue(stat.deal_rate, stat.deal_rate_text)) { + const text = formatRateText( + stat.deal_rate != null ? stat.deal_rate : stat.deal_rate_text, + stat.deal_rate_text, + ); + chips.push({ k: 'deal', t: `${dealLabel} ${text}` }); + } + + const fineRaw = stat.fine_rate != null + ? stat.fine_rate + : (stat.fined_rate != null ? stat.fined_rate : (stat.fine_rate_text || stat.fined_rate_text)); + const fineText = stat.fine_rate_text || stat.fined_rate_text; + if (hasRateValue(stat.fine_rate != null ? stat.fine_rate : stat.fined_rate, fineText)) { + chips.push({ k: 'fine', t: `${fineLabel} ${formatRateText(fineRaw, fineText)}` }); + } + + if (stat.avg_deal_hours_text) { + chips.push({ k: 'avg', t: `均时 ${stat.avg_deal_hours_text}`, hl: true }); + } + return chips; +} + +export const DEAL_STAT_HELP = { + merchant: '成交率=已结算÷有效单量;罚款率=已缴纳罚单÷有效单量;均时=平均结算耗时。有样本即展示(含 0%)。', + dashou: '成交率=已结算÷接单量;被罚款率=已缴纳罚单÷接单量。有样本即展示(含 0%)。', +};