// pages/dsddxq/dsddxq.js - 【最终版:提交前公告弹窗 + 修改图片至少保留一张】 const app = getApp() import request from '../../utils/request.js' const COS = require('../../utils/cos-wx-sdk-v5.js') import connectionManager from '../../utils/xiaoxilj.js' Page({ data: { jibenShuju: { dingdan_id: '', tupian: '', jieshao: '', create_time: '', jine: '', nicheng: '', beizhu: '', zhuangtai: 0, fadanpingtai: 0, is_cross: 0 }, xiangxiShuju: { shangjia_id: '', shangjia_nicheng: '', shangjia_liuyan: '', tuikuan_liyou: '', chufa_liyou: '', chufa_zhuangtai: 0, chufa_jieguo: '', bohui_liyou: '', dashou_jiaofu: [], dashou_liuyan: '', laoban_id: '' }, zhuangtaiYanseMap: { 1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#FFF3E0', 5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#FFF8E1' }, zhuangtaiWenziMap: { 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中', 5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中' }, isLoading: true, isSubmitting: false, liuyan: '', xuanzhongTupian: [], shangchuanJindu: 0, shangchuanZongshu: 0, jinduWidth: '0%', showWanzhengJieshao: false, wanzhengJieshao: '', showWanzhengBeizhu: false, wanzhengBeizhu: '', ossImageUrl: '', morentouxiang: '', showRefundInfo: false, // 修改功能所需字段 isModifying: false, originalDashouJiaofu: [], originalDashouLiuyan: '', remainingOriginals: [], modifiedLiuyan: '', deletedImages: [], newImages: [], isConfirming: false, modifyUploadProgress: { total: 0, current: 0, width: '0%' } }, onLoad(options) { wx.setNavigationBarTitle({ title: '订单详情' }) this.initGlobalData() this.jiexiTiaozhuanCanshu(options) }, onHide() { const popupComp = this.selectComponent('#popupNotice'); if (popupComp && popupComp.cleanup) { popupComp.cleanup(); } }, 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() }; } }, initGlobalData() { const app = getApp() const ossImageUrl = app.globalData.ossImageUrl || 'https://your-oss-domain.com/' const morentouxiang = app.globalData.morentouxiang || '/images/default-avatar.png' this.setData({ ossImageUrl, morentouxiang }) }, jiexiTiaozhuanCanshu(options) { try { const dingdanDataStr = options.dingdanData || '' if (!dingdanDataStr) { wx.showToast({ title: '订单数据错误', icon: 'none' }) setTimeout(() => wx.navigateBack(), 1500) return } const dingdanData = JSON.parse(decodeURIComponent(dingdanDataStr)) let tupianUrl = dingdanData.tupian || '' if (!tupianUrl) tupianUrl = this.getTupianUrl() let jine = parseFloat(dingdanData.jine || 0) if (isNaN(jine)) jine = 0 const formattedJine = jine.toFixed(2) this.setData({ 'jibenShuju.dingdan_id': dingdanData.dingdan_id || '', 'jibenShuju.tupian': tupianUrl, 'jibenShuju.jieshao': dingdanData.jieshao || '', 'jibenShuju.create_time': dingdanData.create_time || '', 'jibenShuju.jine': formattedJine, 'jibenShuju.nicheng': dingdanData.nicheng || '', 'jibenShuju.beizhu': dingdanData.beizhu || '', 'jibenShuju.zhuangtai': dingdanData.zhuangtai || 0, 'jibenShuju.fadanpingtai': dingdanData.fadanpingtai || 0, isLoading: false }) this.jiazaiXiangxiShuju(dingdanData.dingdan_id) } catch (error) { console.error('解析订单数据失败:', error) wx.showToast({ title: '数据解析失败', icon: 'none' }) setTimeout(() => wx.navigateBack(), 1500) } }, getTupianUrl() { const cacheTouxiang = wx.getStorageSync('touxiang') || '' let tupianPath = cacheTouxiang || app.globalData.morentouxiang || '' const ossImageUrl = app.globalData.ossImageUrl || '' if (tupianPath && ossImageUrl) { if (tupianPath.startsWith('/')) tupianPath = tupianPath.substring(1) return ossImageUrl + tupianPath } return '' }, async jiazaiXiangxiShuju(dingdanId) { if (!dingdanId) return wx.showLoading({ title: '加载中...', mask: true }) try { const res = await request({ url: '/dingdan/dsddxq', method: 'POST', data: { dingdan_id: dingdanId }, header: { 'content-type': 'application/json' } }) wx.hideLoading() if (res && res.data.code === 0 && res.data.data) { const data = res.data.data let dashouJiaofuList = data.dashou_jiaofu || [] dashouJiaofuList = dashouJiaofuList.map(imgUrl => { if (imgUrl && !imgUrl.startsWith('http')) { const ossImageUrl = app.globalData.ossImageUrl || '' if (imgUrl.startsWith('/')) imgUrl = imgUrl.substring(1) return ossImageUrl + imgUrl } return imgUrl }) this.setData({ 'xiangxiShuju.shangjia_id': data.shangjia_id || '', 'xiangxiShuju.shangjia_nicheng': data.shangjia_nicheng || '', 'xiangxiShuju.shangjia_liuyan': data.shangjia_liuyan || '', 'xiangxiShuju.tuikuan_liyou': data.tuikuan_liyou || '', 'xiangxiShuju.chufa_liyou': data.chufa_liyou || '', 'xiangxiShuju.chufa_zhuangtai': data.chufa_zhuangtai || 0, 'xiangxiShuju.chufa_jieguo': data.chufa_jieguo || '', 'xiangxiShuju.bohui_liyou': data.bohui_liyou || '', 'xiangxiShuju.dashou_jiaofu': dashouJiaofuList, 'xiangxiShuju.dashou_liuyan': data.dashou_liuyan || '', 'xiangxiShuju.laoban_id': data.laoban_id || '', 'jibenShuju.zhuangtai': data.zhuangtai || 0, 'jibenShuju.beizhu': data.beizhu || '', 'liuyan': data.dashou_liuyan || '', 'jibenShuju.fadanpingtai': data.fadanpingtai || 0, 'jibenShuju.is_cross': data.is_cross || 0, }) const currentStatus = data.zhuangtai || 0 const showRefund = [4,5,6].includes(currentStatus) this.setData({ showRefundInfo: showRefund }) } else { const errorMsg = res?.data?.msg || '获取订单详情失败' wx.showToast({ title: errorMsg, icon: 'none', duration: 2000 }) } } catch (error) { console.error('加载详细数据失败:', error) wx.hideLoading() wx.showToast({ title: '加载数据失败,请重试', icon: 'none', duration: 2000 }) } }, // ========== 原有提交功能(部分修改) ========== xuanzeTupian() { if (this.data.isSubmitting) return const shengyu = 9 - this.data.xuanzhongTupian.length if (shengyu <= 0) { wx.showToast({ title: '最多只能选择9张图片', icon: 'none' }) return } wx.chooseImage({ count: shengyu, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: (res) => { const newTupian = [...this.data.xuanzhongTupian, ...res.tempFilePaths] this.setData({ xuanzhongTupian: newTupian.slice(0, 9) }) } }) }, shanchuTupian(e) { const index = e.currentTarget.dataset.index const newTupian = [...this.data.xuanzhongTupian] newTupian.splice(index, 1) this.setData({ xuanzhongTupian: newTupian }) }, yulanTupian(e) { const currentUrl = e.currentTarget.dataset.url const urls = e.currentTarget.dataset.urls || [] if (!currentUrl) return wx.previewImage({ current: currentUrl, urls: urls.length > 0 ? urls : [currentUrl] }) }, getFileExtension(filePath) { const lastDotIndex = filePath.lastIndexOf('.') if (lastDotIndex === -1) return '.jpg' const ext = filePath.substring(lastDotIndex).toLowerCase() const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'] return allowedExts.includes(ext) ? ext : '.jpg' }, shuruLiuyan(e) { const liuyan = e.detail.value.slice(0, 50) this.setData({ liuyan }) }, chakanWanzhengJieshao() { this.setData({ showWanzhengJieshao: true, wanzhengJieshao: this.data.jibenShuju.jieshao || '无' }) }, guanbiWanzhengJieshao() { this.setData({ showWanzhengJieshao: false }) }, chakanWanzhengBeizhu() { this.setData({ showWanzhengBeizhu: true, wanzhengBeizhu: this.data.jibenShuju.beizhu || '无' }) }, guanbiWanzhengBeizhu() { this.setData({ showWanzhengBeizhu: false }) }, async fuzhiWenben(e) { const text = e.currentTarget.dataset.text if (!text) return try { await wx.setClipboardData({ data: text }) wx.showToast({ title: '复制成功', icon: 'success', duration: 1500 }) } catch (error) { console.error('复制失败:', error) wx.showToast({ title: '复制失败', icon: 'none' }) } }, goToKefu() { const kefuLink = app.globalData.kefuConfig?.link || ''; const corpId = app.globalData.kefuConfig?.enterpriseId || ''; if (!kefuLink || !corpId) { wx.showToast({ title: '客服配置未加载', icon: 'none' }); return; } wx.openCustomerServiceChat({ extInfo: { url: kefuLink }, corpId: corpId, success: (res) => { console.log('跳转客服成功', res); }, fail: (err) => { console.error('跳转客服失败', err); wx.showToast({ title: '暂时无法连接客服', icon: 'none' }); } }); }, // ===== 联系老板按钮(改为跳转订单群聊) ===== // ===== 联系老板按钮(智能查找订单群聊) ===== // ===== 联系老板按钮(智能查找订单群聊) ===== goToChatWithBoss() { const uid = wx.getStorageSync('uid') if (!uid) return const orderId = this.data.jibenShuju.dingdan_id if (!orderId) { wx.showToast({ title: '订单ID缺失', icon: 'none' }) return } const identityType = 'dashou' const userId = 'Ds' + uid const fadanPingtai = this.data.jibenShuju.fadanpingtai || 0 const partnerUid = fadanPingtai === 2 ? (this.data.xiangxiShuju.shangjia_id || '') : (this.data.xiangxiShuju.laoban_id || '') connectionManager.connectToGroupChat({ identityType, userId, orderId, partnerUid, fadanPingtai, groupName: this.data.jibenShuju.nicheng + '的订单群', isCross: this.data.jibenShuju.is_cross || 0 }).catch(err => { console.error('跳转群聊失败', err) }) }, async getCOSZhengshu() { try { const res = await request({ url: '/dingdan/dsscpz', method: 'POST', data: { dingdan_id: this.data.jibenShuju.dingdan_id } }) if (res && res.data.code === 0 && res.data.data) return res.data.data else throw new Error(res?.data?.msg || '获取上传凭证失败') } catch (error) { console.error('获取COS凭证失败:', error) throw error } }, initCOSClient(tokenData) { const credentials = tokenData.credentials || tokenData const bucket = tokenData.bucket || 'julebu-1361527063' const region = tokenData.region || 'ap-shanghai' const cos = new COS({ SimpleUploadMethod: 'putObject', getAuthorization: async (options, callback) => { const authParams = { TmpSecretId: credentials.tmpSecretId, TmpSecretKey: credentials.tmpSecretKey, SecurityToken: credentials.sessionToken || '', StartTime: tokenData.startTime || Math.floor(Date.now() / 1000), ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200) } callback(authParams) } }) return { cos, bucket, region } }, async shangchuanDanZhangTupian(filePath, cosKey, index, cosInstance, bucket, region) { return new Promise((resolve) => { const timeoutId = setTimeout(() => resolve({ status: 'optimistic_success', key: cosKey }), 2000) cosInstance.uploadFile({ Bucket: bucket, Region: region, Key: cosKey, FilePath: filePath, onProgress: (progressInfo) => { const percent = Math.round(progressInfo.percent * 100) if (percent === 100) { clearTimeout(timeoutId) resolve({ status: 'optimistic_success', key: cosKey }) } } }) }) }, async piliangShangchuanTupian() { const total = this.data.xuanzhongTupian.length this.setData({ shangchuanZongshu: total, shangchuanJindu: 0, jinduWidth: '0%' }) wx.showLoading({ title: '正在准备...', mask: true }) const preGeneratedUrls = [] for (let i = 0; i < total; i++) { const timestamp = Date.now() + i const random = Math.floor(Math.random() * 10000) const fileExt = this.getFileExtension(this.data.xuanzhongTupian[i]) const fileName = `${timestamp}_${random}${fileExt}` const cosKey = `order/${this.data.jibenShuju.dingdan_id}/${fileName}` preGeneratedUrls.push(cosKey) } let cosClient, bucket, region try { const tokenData = await this.getCOSZhengshu() const { cos, bucket: cosBucket, region: cosRegion } = this.initCOSClient(tokenData) cosClient = cos bucket = cosBucket region = cosRegion } catch (error) { wx.hideLoading() wx.showToast({ title: '上传初始化失败', icon: 'none' }) return [] } const uploadTasks = [] for (let i = 0; i < total; i++) { const task = this.shangchuanDanZhangTupian( this.data.xuanzhongTupian[i], preGeneratedUrls[i], i, cosClient, bucket, region ).then((result) => { const currentDone = i + 1 const jinduPercent = ((currentDone / total) * 100).toFixed(0) this.setData({ shangchuanJindu: currentDone, jinduWidth: `${jinduPercent}%` }) return result }) uploadTasks.push(task) } wx.showLoading({ title: `上传中...`, mask: true }) await Promise.all(uploadTasks) wx.hideLoading() return preGeneratedUrls }, tijiaoDingdan() { this.showAnnouncementAndConfirm() }, async showAnnouncementAndConfirm() { try { const res = await request({ url: '/peizhi/tanchuang', method: 'POST', data: { pageKey: 'dsddxq' } }) if (res.statusCode !== 200 || res.data.code !== 0) { this.showOriginalConfirmAndSubmit() return } const { popups = [], serverTime } = res.data.data if (!popups.length) { this.showOriginalConfirmAndSubmit() return } const popup = popups[0] const popupComp = this.selectComponent('#popupNotice') if (!popupComp) { console.error('未找到弹窗组件 #popupNotice') this.showOriginalConfirmAndSubmit() return } const showData = { popupId: popup.popup_id, title: popup.title || '', duration: popup.duration || 0, images: popup.images || [], showMuteCheckbox: false, serverTime: serverTime } popupComp.show(showData, (result) => { this.showOriginalConfirmAndSubmit() }) } catch (error) { console.error('获取公告配置失败', error) this.showOriginalConfirmAndSubmit() } }, showOriginalConfirmAndSubmit() { if (this.data.isSubmitting) { wx.showToast({ title: '正在提交中', icon: 'none' }) return } if (this.data.jibenShuju.zhuangtai !== 2) { wx.showToast({ title: '订单状态不可提交', icon: 'none' }) return } if (this.data.xuanzhongTupian.length === 0) { wx.showToast({ title: '请至少上传一张图片', icon: 'none' }) return } wx.showModal({ title: '⚠️ 提交确认', content: '在提交订单之前,请确保您已在打手端页面认真阅读打手规则。如有漏交图片,订单被退款,概不申诉。', confirmText: '我已阅读', cancelText: '我再想想', success: (res) => { if (res.confirm) this._doSubmit() }, fail: (err) => { console.error('弹窗失败', err) wx.showToast({ title: '系统异常,请重试', icon: 'none' }) } }) }, async _doSubmit() { this.setData({ isSubmitting: true }) wx.showLoading({ title: '开始提交...', mask: true }) try { const tupianUrls = await this.piliangShangchuanTupian() if (!tupianUrls || tupianUrls.length === 0) throw new Error('无法生成上传路径') wx.showLoading({ title: '提交订单数据...', mask: true }) const res = await request({ url: '/dingdan/dstijiao', method: 'POST', data: { dingdan_id: this.data.jibenShuju.dingdan_id, liuyan: this.data.liuyan || '', jiaofu_tupian_urls: tupianUrls }, header: { 'content-type': 'application/json' } }) wx.hideLoading() this.setData({ isSubmitting: false }) if (res && res.data.code === 0) { const ossImageUrl = app.globalData.ossImageUrl || '' const fullUrls = tupianUrls.map(url => { if (url && !url.startsWith('http')) { if (url.startsWith('/')) url = url.substring(1) return ossImageUrl + url } return url }) this.setData({ 'jibenShuju.zhuangtai': 8, 'xiangxiShuju.dashou_liuyan': this.data.liuyan || '', 'xiangxiShuju.dashou_jiaofu': fullUrls, xuanzhongTupian: [], liuyan: '', shangchuanJindu: 0, shangchuanZongshu: 0, jinduWidth: '0%', }) if (app.globalData.dashouzhuangtai != 1) app.globalData.dashouzhuangtai = 1 wx.showToast({ title: '提交成功!', icon: 'success', duration: 2000 }) setTimeout(() => wx.navigateBack(), 1500) } else { const errorMsg = res?.data?.msg || '提交失败' wx.showToast({ title: errorMsg, icon: 'none' }) } } catch (error) { console.error('提交失败:', error) wx.hideLoading() this.setData({ isSubmitting: false }) wx.showToast({ title: error.message || '提交失败', icon: 'none' }) } }, // ========== 修改功能(增加最少一张图片校验) ========== extractRelativePath(fullUrl) { const ossImageUrl = this.data.ossImageUrl if (fullUrl.startsWith(ossImageUrl)) return fullUrl.substring(ossImageUrl.length) return fullUrl }, enterModify() { const { dashou_jiaofu, dashou_liuyan } = this.data.xiangxiShuju this.setData({ isModifying: true, originalDashouJiaofu: dashou_jiaofu.slice(), remainingOriginals: dashou_jiaofu.slice(), originalDashouLiuyan: dashou_liuyan || '', modifiedLiuyan: dashou_liuyan || '', deletedImages: [], newImages: [] }) }, cancelModify() { this.setData({ isModifying: false, originalDashouJiaofu: [], remainingOriginals: [], originalDashouLiuyan: '', modifiedLiuyan: '', deletedImages: [], newImages: [] }) }, handleDeleteImage(e) { const fullUrl = e.currentTarget.dataset.url const { originalDashouJiaofu, remainingOriginals, deletedImages, newImages } = this.data if (originalDashouJiaofu.includes(fullUrl)) { const newRemaining = remainingOriginals.filter(url => url !== fullUrl) this.setData({ remainingOriginals: newRemaining, deletedImages: [...deletedImages, fullUrl] }) } else { const index = newImages.indexOf(fullUrl) if (index > -1) { const newImagesCopy = [...newImages] newImagesCopy.splice(index, 1) this.setData({ newImages: newImagesCopy }) } } }, handleAddImage() { if (this.data.isConfirming) return const currentCount = this.getCurrentImageCount() const maxCount = 9 - currentCount if (maxCount <= 0) { wx.showToast({ title: '最多9张图片', icon: 'none' }) return } wx.chooseImage({ count: maxCount, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: (res) => { const newImages = [...this.data.newImages, ...res.tempFilePaths] this.setData({ newImages: newImages.slice(0, 9) }) } }) }, getCurrentImageCount() { const { remainingOriginals, newImages } = this.data return remainingOriginals.length + newImages.length }, onModifyLiuyanInput(e) { this.setData({ modifiedLiuyan: e.detail.value.slice(0, 50) }) }, async confirmModify() { if (this.data.isConfirming) return const { dingdan_id } = this.data.jibenShuju const { remainingOriginals, deletedImages, newImages, modifiedLiuyan, originalDashouLiuyan } = this.data const hasLiuyanChange = modifiedLiuyan !== originalDashouLiuyan const hasDelete = deletedImages.length > 0 const hasAdd = newImages.length > 0 if (!hasLiuyanChange && !hasDelete && !hasAdd) { wx.showToast({ title: '没有修改内容', icon: 'none' }) return } const finalImageCount = remainingOriginals.length + newImages.length if (finalImageCount === 0) { wx.showToast({ title: '至少保留一张图片', icon: 'none' }) return } this.setData({ isConfirming: true }) wx.showLoading({ title: '提交修改...', mask: true }) try { const tokenData = await this.getCOSZhengshu() const { cos, bucket, region } = this.initCOSClient(tokenData) let newImageRelativePaths = [] if (hasAdd) { const total = newImages.length this.setData({ 'modifyUploadProgress.total': total, 'modifyUploadProgress.current': 0, 'modifyUploadProgress.width': '0%' }) const preGeneratedKeys = [] for (let i = 0; i < total; i++) { const timestamp = Date.now() + i const random = Math.floor(Math.random() * 10000) const fileExt = this.getFileExtension(newImages[i]) const fileName = `${timestamp}_${random}${fileExt}` const cosKey = `order/${this.data.jibenShuju.dingdan_id}/${fileName}` preGeneratedKeys.push(cosKey) } const uploadTasks = [] for (let i = 0; i < total; i++) { const task = this.shangchuanDanZhangTupian( newImages[i], preGeneratedKeys[i], i, cos, bucket, region ).then((result) => { const currentDone = i + 1 const jinduPercent = ((currentDone / total) * 100).toFixed(0) this.setData({ 'modifyUploadProgress.current': currentDone, 'modifyUploadProgress.width': `${jinduPercent}%` }) return result.key }) uploadTasks.push(task) } wx.showLoading({ title: `上传中...`, mask: true }) newImageRelativePaths = await Promise.all(uploadTasks) wx.hideLoading() this.setData({ 'modifyUploadProgress.total': 0, 'modifyUploadProgress.current': 0, 'modifyUploadProgress.width': '0%' }) } const deletedRelativePaths = deletedImages.map(fullUrl => this.extractRelativePath(fullUrl)) const res = await request({ url: '/dingdan/dsxiugaidd', method: 'POST', data: { dingdan_id, liuyan: modifiedLiuyan, deleted_images: deletedRelativePaths, new_images: newImageRelativePaths }, header: { 'content-type': 'application/json' } }) wx.hideLoading() this.setData({ isConfirming: false }) if (res && res.data.code === 0) { const ossImageUrl = this.data.ossImageUrl const newFullUrls = newImageRelativePaths.map(rel => ossImageUrl + rel) const newDashouJiaofu = [...remainingOriginals, ...newFullUrls] this.setData({ 'xiangxiShuju.dashou_jiaofu': newDashouJiaofu, 'xiangxiShuju.dashou_liuyan': modifiedLiuyan, isModifying: false, originalDashouJiaofu: [], remainingOriginals: [], originalDashouLiuyan: '', deletedImages: [], newImages: [], modifiedLiuyan: '' }) wx.showToast({ title: '修改成功', icon: 'success' }) } else { const errorMsg = res?.data?.msg || '修改失败' wx.showToast({ title: errorMsg, icon: 'none' }) } } catch (error) { console.error('修改失败:', error) wx.hideLoading() this.setData({ isConfirming: false }) wx.showToast({ title: error.message || '修改失败', icon: 'none' }) } } })