From 7e3e999b74e7484f5dfc79590d80a06b32f10d16 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 8 Oct 2026 20:34:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E8=B4=A7=E6=96=B9=E5=BC=8F=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=8F=8A=E6=B6=88=E6=81=AF=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/application/common/model/Messages.php | 7 +- .../application/common/model/order/Ships.php | 11 +- .../common/service/DemandService.php | 31 ++- .../common/service/MessageService.php | 10 +- .../common/service/OrderShipService.php | 16 +- frontend/miniapp/app.js | 17 +- frontend/miniapp/custom-tab-bar/index.js | 10 +- .../miniapp/pages/demand/deliver/index.js | 30 ++- .../miniapp/pages/demand/deliver/index.json | 3 +- .../miniapp/pages/demand/deliver/index.wxml | 32 ++- .../miniapp/pages/demand/deliver/index.wxss | 14 ++ .../miniapp/pages/demand/detail/index.wxml | 7 + .../miniapp/pages/demand/detail/index.wxss | 18 ++ .../miniapp/pages/message/detail/index.js | 226 +++++------------- .../miniapp/pages/message/detail/index.json | 8 +- .../miniapp/pages/message/detail/index.wxml | 50 ++-- .../miniapp/pages/message/detail/index.wxs | 12 + .../miniapp/pages/message/detail/index.wxss | 171 ++++--------- frontend/miniapp/pages/message/list/index.js | 77 +----- .../miniapp/pages/message/list/index.json | 7 +- .../miniapp/pages/message/list/index.wxml | 28 +-- frontend/miniapp/pages/message/list/index.wxs | 15 +- .../miniapp/pages/message/list/index.wxss | 26 +- 23 files changed, 371 insertions(+), 455 deletions(-) create mode 100644 frontend/miniapp/pages/message/detail/index.wxs diff --git a/backend/application/common/model/Messages.php b/backend/application/common/model/Messages.php index f97ff8f..48f7533 100644 --- a/backend/application/common/model/Messages.php +++ b/backend/application/common/model/Messages.php @@ -36,9 +36,14 @@ class Messages extends Model + /** + * 消息类型:0=系统消息,1=公告消息,2=需求消息 + * 此处直接返回中文而非 __() 语言包:该文案会随接口返回给小程序, + * 而 API 模块加载的语言包并不包含这些 key,__() 查不到会原样输出英文 + */ public function getMessageTypeList() { - return ['0' => __('Message_type 0'), '1' => __('Message_type 1'), '2' => __('Message_type 2')]; + return ['0' => '系统消息', '1' => '公告消息', '2' => '需求消息']; } diff --git a/backend/application/common/model/order/Ships.php b/backend/application/common/model/order/Ships.php index cf9dd73..0e2f2b9 100644 --- a/backend/application/common/model/order/Ships.php +++ b/backend/application/common/model/order/Ships.php @@ -26,7 +26,8 @@ class Ships extends Model // 追加属性 protected $append = [ 'images_list', - 'files_list' + 'files_list', + 'deliver_links_list' ]; /** @@ -45,6 +46,14 @@ class Ships extends Model return self::parseUrlList($data['files'] ?? ''); } + /** + * 交付链接列表([{url, name}]),供接口与后台直接使用 + */ + public function getDeliverLinksListAttr($value, $data) + { + return self::parseUrlList($data['deliver_links'] ?? ''); + } + /** * 解析多图/多文件字段:英文逗号分隔的 URL 字符串 -> [{url, name}] * 例:http://a.com/1.jpg,http://a.com/2.pdf => [['url'=>'http://a.com/1.jpg','name'=>'1.jpg'], ...] diff --git a/backend/application/common/service/DemandService.php b/backend/application/common/service/DemandService.php index 753a800..db244db 100644 --- a/backend/application/common/service/DemandService.php +++ b/backend/application/common/service/DemandService.php @@ -438,6 +438,28 @@ class DemandService } } + /** + * 规范化交付链接(只允许 1 个) + * 支持数组或字符串,仅保留第一个 http(s) 开头的合法链接 + * @param array|string $links + * @return array + */ + public static function normalizeDeliverLinks($links): array + { + if (is_string($links)) { + $links = preg_split('/[,,\r\n]+/', $links) ?: []; + } + foreach ((array)$links as $item) { + $url = is_array($item) ? trim($item['url'] ?? '') : trim((string)$item); + if ($url && preg_match('/^https?:\/\//i', $url)) { + //只保留第一个合法链接 + return [$url]; + } + } + + return []; + } + /** * 开发者发货(交付开发成果) * 凭证截图、交付附件与备注写入订单发货表,尾款订单转为已发货(待收货) @@ -452,10 +474,13 @@ class DemandService $demandId = $requestData['demandId'] ?? 0; $images = array_values($requestData['images'] ?? []); $files = array_values($requestData['files'] ?? []); + $links = self::normalizeDeliverLinks($requestData['links'] ?? ''); $remark = trim($requestData['remark'] ?? ''); Db::startTrans(); - if (!$images || !$files) { - throw new \Exception('请上传开发完成的凭证截图和交付附件'); + //源码一般在企业微信沟通群直接交付(无大小限制),平台只登记发货状态, + //故仅凭证截图必填,附件与交付链接均选填 + if (!$images) { + throw new \Exception('请上传开发完成的凭证截图'); } $demand = self::getOne(['id' => $demandId], ['winBidding']); if (!$demand) { @@ -477,7 +502,7 @@ class DemandService } //交付内容写入订单发货表 $spuId = $finalOrder->orderGoods[0]->spu_id ?? 0; - OrderShipService::createDemandDelivery($finalOrder->order_no, $spuId, $demandId, $images, $files, $remark); + OrderShipService::createDemandDelivery($finalOrder->order_no, $spuId, $demandId, $images, $files, $remark, $links); //尾款订单改为已发货(待收货) $upRes = OrderService::where(['id' => $finalOrder->id])->update(['order_status' => OrderService::ORDER_STATUS_SHIPPED]); if (!$upRes) { diff --git a/backend/application/common/service/MessageService.php b/backend/application/common/service/MessageService.php index 6b965d9..e8b9e42 100644 --- a/backend/application/common/service/MessageService.php +++ b/backend/application/common/service/MessageService.php @@ -75,8 +75,14 @@ class MessageService $lines[] = $name . ':' . $values[$key]; } } - if (!empty($template['remark'])) { - $lines[] = '备注:' . $template['remark']; + // 各发送点已把 remark 作为最后一个 value 传入(对应 valueNames 末位的「备注」字段), + // 此处仅在正文尚未包含 remark 时才追加,避免出现两行重复的「备注」 + $remark = $template['remark'] ?? ''; + if ($remark !== '') { + $content = implode("\n", $lines); + if (mb_strpos($content, $remark) === false) { + $lines[] = '备注:' . $remark; + } } $content = implode("\n", $lines); diff --git a/backend/application/common/service/OrderShipService.php b/backend/application/common/service/OrderShipService.php index ce9c8e4..0eb5607 100644 --- a/backend/application/common/service/OrderShipService.php +++ b/backend/application/common/service/OrderShipService.php @@ -37,24 +37,28 @@ class OrderShipService } /** - * 需求交付发货单(开发者发货:凭证截图+交付附件+备注) - * 多图/多文件统一存英文逗号分隔的 URL 字符串: + * 需求交付发货单(开发者发货:凭证截图 + 交付附件/链接 + 备注) + * 多图/多文件/多链接统一存英文逗号分隔的 URL 字符串: * http://xxx/a.jpg,http://xxx/b.pdf + * 说明:源码通常在企业微信沟通群直接交付,平台只登记发货状态, + * 故附件与链接均为选填,仅凭证截图必填 * @param $order_no * @param $order_goods_spu_id * @param $demand_id * @param array $images URL 数组 - * @param array $files URL 数组 + * @param array $files URL 数组(选填) * @param string $remark + * @param array $links 交付链接数组(选填:Git仓库/网盘等持久获取方式) * @return mixed * @throws \Exception */ - public static function createDemandDelivery($order_no, $order_goods_spu_id, $demand_id, array $images, array $files, $remark = '') + public static function createDemandDelivery($order_no, $order_goods_spu_id, $demand_id, array $images, array $files, $remark = '', array $links = []) { // 复用模型的规范化方法,统一为英文逗号分隔字符串 $imagesStr = OrderShips::toUrlString($images); $filesStr = OrderShips::toUrlString($files); - if (mb_strlen($imagesStr) > 1000 || mb_strlen($filesStr) > 1000) { + $linksStr = OrderShips::toUrlString($links); + if (mb_strlen($imagesStr) > 1000 || mb_strlen($filesStr) > 1000 || mb_strlen($linksStr) > 1000) { exception('附件数量过多或链接过长,请精简后重新发货'); } $data = [ @@ -63,6 +67,7 @@ class OrderShipService 'demand_id' => $demand_id, 'images' => $imagesStr, 'files' => $filesStr, + 'deliver_links' => $linksStr, 'remark' => $remark, ]; $res = self::create($data); @@ -99,6 +104,7 @@ class OrderShipService return [ 'images' => $ship->images_list, 'files' => $ship->files_list, + 'links' => $ship->deliver_links_list, 'remark' => $ship->remark, 'delivery_time' => $ship->createtime, ]; diff --git a/frontend/miniapp/app.js b/frontend/miniapp/app.js index d0544b7..859fd4f 100644 --- a/frontend/miniapp/app.js +++ b/frontend/miniapp/app.js @@ -25,12 +25,10 @@ App({ }); }, }); - //启动未读消息轮询(tabbar红点) - this.startUnreadPolling(); }, onShow: function () { updateManager(); - //从后台切回前台,立即刷新未读红点 + //从后台切回前台时刷新一次未读红点(不使用定时轮询,避免请求堆积导致页面卡死) this.refreshUnread(); }, onHide: function () {}, @@ -38,19 +36,6 @@ App({ unread: 0, //未读消息数(tabbar红点共享缓存) }, - /** - * 未读消息轮询:小程序前台每10s刷新一次底部tabbar红点 - * (切后台后JS执行会被系统挂起,定时器自然暂停,回前台由onShow立即补一次) - */ - startUnreadPolling: function () { - if (this.unreadTimer) { - return; - } - this.unreadTimer = setInterval(() => { - this.refreshUnread(); - }, 10000); - }, - /** * 刷新当前 tab 页 tabbar 实例的红点(非tab页无tabbar实例,自动跳过) */ diff --git a/frontend/miniapp/custom-tab-bar/index.js b/frontend/miniapp/custom-tab-bar/index.js index 37fc7a5..fab95c9 100644 --- a/frontend/miniapp/custom-tab-bar/index.js +++ b/frontend/miniapp/custom-tab-bar/index.js @@ -62,7 +62,15 @@ Component({ if (!force && now - (this._lastFetchAt || 0) < 5000) { return; } + //上一次请求尚未结束时不再发起,避免请求堆积占满并发导致页面假死 + if (this._fetching) { + return; + } this._lastFetchAt = now; + this._fetching = true; + const done = () => { + this._fetching = false; + }; fetchMessageUnreadCount().then((res) => { if (res.code == 1) { const unread = Number(res.data?.unread_count) || 0; @@ -70,7 +78,7 @@ Component({ const app = getApp(); if (app) app.globalData.unread = unread; } - }).catch(() => {}); + }).catch(() => {}).then(done, done); }, /** diff --git a/frontend/miniapp/pages/demand/deliver/index.js b/frontend/miniapp/pages/demand/deliver/index.js index 691c3d1..02dec91 100644 --- a/frontend/miniapp/pages/demand/deliver/index.js +++ b/frontend/miniapp/pages/demand/deliver/index.js @@ -8,6 +8,8 @@ Page({ data: { demandId: 0, remark: '', + // 交付链接(Git仓库/网盘等持久获取方式),选填 + deliverLinks: '', // 开发完成凭证截图(软件成品展示图等) imageFiles: [], imageMax: 9, @@ -43,6 +45,19 @@ Page({ this.setData({ remark: e.detail.value }); }, + // 交付链接变更 + onDeliverLinksChange(e) { + this.setData({ deliverLinks: e.detail.value }); + }, + + // 交付链接(单个):只取第一行,避免粘贴时带入多余内容 + getDeliverLinks() { + const link = String(this.data.deliverLinks || '') + .split(/[\r\n]+/)[0] + .trim(); + return link ? [link] : []; + }, + // 凭证截图选择成功 // 注意:t-upload 绑定了 files,success 事件返回的是完整文件列表(含已选的旧文件), // 必须整体覆盖,不能往 imageFiles 里追加,否则已选图片会重复 @@ -209,7 +224,7 @@ Page({ } }, - // 表单验证 + // 表单验证:源码一般在沟通群交付,平台仅凭证截图必填,附件与链接选填 validateForm() { const { imageFiles, attachFiles } = this.data; if (!imageFiles.length) { @@ -219,9 +234,17 @@ Page({ }); return false; } - if (!attachFiles.length) { + const links = this.getDeliverLinks(); + if (links.length > 1) { wx.showToast({ - title: '请上传源码或软件附件', + title: '只能填写1个交付链接', + icon: 'none' + }); + return false; + } + if (links.some(item => !/^https?:\/\//i.test(item))) { + wx.showToast({ + title: '交付链接需以 http:// 或 https:// 开头', icon: 'none' }); return false; @@ -251,6 +274,7 @@ Page({ opType: 'deliverGoods', images: imageFiles.map(file => file.fullUrl || file.url), files: attachFiles.map(file => file.fullUrl || file.url), + links: this.getDeliverLinks(), remark }; demandOperate(params).then( diff --git a/frontend/miniapp/pages/demand/deliver/index.json b/frontend/miniapp/pages/demand/deliver/index.json index 3227b1d..9fcad4c 100644 --- a/frontend/miniapp/pages/demand/deliver/index.json +++ b/frontend/miniapp/pages/demand/deliver/index.json @@ -3,6 +3,7 @@ "usingComponents": { "t-upload": "tdesign-miniprogram/upload/upload", "t-tag": "tdesign-miniprogram/tag/tag", - "t-textarea": "tdesign-miniprogram/textarea/textarea" + "t-textarea": "tdesign-miniprogram/textarea/textarea", + "t-input": "tdesign-miniprogram/input/input" } } diff --git a/frontend/miniapp/pages/demand/deliver/index.wxml b/frontend/miniapp/pages/demand/deliver/index.wxml index be42d43..6a85ec2 100644 --- a/frontend/miniapp/pages/demand/deliver/index.wxml +++ b/frontend/miniapp/pages/demand/deliver/index.wxml @@ -20,14 +20,23 @@ /> + + + + 源码交付方式 + + + 建议在企业微信沟通群中直接发送源码文件,或使用网盘地址。下方的附件与链接为选填补充。 + + + 交付附件(源码或软件成品) - * - {{attachFiles.length}}/{{attachMax}} + 选填 {{attachFiles.length}}/{{attachMax}} - 上传源码或软件成品文件,客户确认收货后可下载,从聊天记录选择文件 + 小体积源码或软件成品可直接上传,大文件请在沟通群交付;从聊天记录选择文件 + + + + 交付链接 + 选填 + + 填写一个网盘下载地址 + + + @@ -74,7 +98,7 @@ - + 确认发货 diff --git a/frontend/miniapp/pages/demand/deliver/index.wxss b/frontend/miniapp/pages/demand/deliver/index.wxss index d7181ca..a1b9a60 100644 --- a/frontend/miniapp/pages/demand/deliver/index.wxss +++ b/frontend/miniapp/pages/demand/deliver/index.wxss @@ -47,6 +47,20 @@ color: var(--text-tertiary); } +/* 交付方式提示卡片 */ +.form-card--tip { + background: var(--brand-color-light, #fff3e8); +} + +.form-card--tip .form-card__desc { + margin-bottom: 0; +} + +.tip-strong { + color: var(--brand-color, #ff5f15); + font-weight: 500; +} + /* ================= 附件标签 ================= */ .attach-list { display: flex; diff --git a/frontend/miniapp/pages/demand/detail/index.wxml b/frontend/miniapp/pages/demand/detail/index.wxml index 6c80469..2633d8d 100644 --- a/frontend/miniapp/pages/demand/detail/index.wxml +++ b/frontend/miniapp/pages/demand/detail/index.wxml @@ -73,6 +73,13 @@ + + 交付链接 + + {{item.url}} + + + 发货备注 {{_calc.getDelivery(demandInfo).remark}} diff --git a/frontend/miniapp/pages/demand/detail/index.wxss b/frontend/miniapp/pages/demand/detail/index.wxss index de9da7e..4bd30ef 100644 --- a/frontend/miniapp/pages/demand/detail/index.wxss +++ b/frontend/miniapp/pages/demand/detail/index.wxss @@ -94,6 +94,24 @@ page { background: var(--bg-grey); } +/* 交付链接:长链接换行展示 + 复制按钮 */ +.delivery-link { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12rpx 0; +} + +.delivery-link__url { + flex: 1; + min-width: 0; + margin-right: 16rpx; + font-size: 26rpx; + line-height: 38rpx; + color: var(--brand-color, #ff5f15); + word-break: break-all; +} + .delivery-remark { font-size: 26rpx; line-height: 40rpx; diff --git a/frontend/miniapp/pages/message/detail/index.js b/frontend/miniapp/pages/message/detail/index.js index 6f3a562..62e4a33 100644 --- a/frontend/miniapp/pages/message/detail/index.js +++ b/frontend/miniapp/pages/message/detail/index.js @@ -1,176 +1,82 @@ // pages/message/detail/index.js +import { fetchMessageDetail, messageSetRead } from '../../../services/message/fetchMessage'; + +// tabBar 页面(与 app.json 的 tabBar.list 保持一致): +// navigateTo 无法跳转 tab 页,必须用 switchTab;而 switchTab 不支持带参数 +const TAB_PAGES = [ + 'pages/home/home', + 'pages/goods/list/index', + 'pages/message/list/index', + 'pages/usercenter/index', +]; + Page({ - - /** - * 页面的初始数据 - */ data: { - // 客服信息 - serviceAvatar: '/assets/images/contact.png', - serviceNickname: '在线客服', - inputContent: '', - // 消息列表 - messageList: [{ - id: 1, - type: 'text', - content: '您好,请问有什么可以帮您?', - time: '12:30', - avatar: '/assets/image/contact.png', - isSelf: false - }, - { - id: 2, - type: 'text', - content: '我想咨询一下订单问题', - time: '12:31', - avatar: '/assets/image/default_avatar_big.png', - isSelf: true - }, - { - id: 3, - type: 'text', - content: '好的,请问是哪个订单呢?', - time: '12:31', - avatar: '/assets/image/contact.png', - isSelf: false - } - ], - scrollToMessage: '', // 用于消息定位 - - // 输入相关 - inputContent: '', - showEmoji: false, + id: 0, + detail: null, + jumpPage: '', + loading: true, }, - // 监听输入框内容变化 - onInput(e) { - this.setData({ - inputContent: e.detail.value + onLoad(options) { + const id = options?.id || 0; + this.setData({ id }); + // 页面标题用消息标题,onLoad 阶段先给默认值 + wx.setNavigationBarTitle({ + title: '消息详情' }); + this.loadDetail(); }, - // 发送消息 - sendMessage() { - if (!this.data.inputContent.trim()) return; - - const newMessage = { - id: Date.now(), // 使用时间戳作为临时ID - type: 'text', - content: this.data.inputContent, - isSelf: true, - avatar: '你的默认头像URL', - time: this.formatTime(new Date()) - }; - - this.setData({ - messageList: [...this.data.messageList, newMessage], - inputContent: '', // 清空输入框 - scrollToMessage: `msg-${newMessage.id}` // 滚动到最新消息 - }); - }, - - // 选择图片 - chooseImage() { - wx.chooseMedia({ - count: 1, - mediaType: ['image'], - sourceType: ['album', 'camera'], - success: (res) => { - const tempFilePath = res.tempFiles[0].tempFilePath; - const newMessage = { - id: Date.now(), - type: 'image', - content: tempFilePath, - isSelf: true, - avatar: '你的默认头像URL', - time: this.formatTime(new Date()) - }; - - this.setData({ - messageList: [...this.data.messageList, newMessage], - scrollToMessage: `msg-${newMessage.id}` + loadDetail() { + fetchMessageDetail(this.data.id).then((res) => { + const detail = res.data || null; + const jumpPage = String(detail?.extra_json?.page || ''); + this.setData({ + detail, + jumpPage, + loading: false + }); + if (detail?.title) { + wx.setNavigationBarTitle({ + title: detail.title }); } + // 进入即标记已读 + if (detail && !detail.is_read) { + messageSetRead({ id: this.data.id }).then(() => { + this.setData({ 'detail.is_read': 1 }); + }).catch(() => {}); + } + }).catch(() => { + this.setData({ loading: false }); }); }, - // 预览图片 - previewImage(e) { - const current = e.currentTarget.dataset.src; - const urls = this.data.messageList - .filter(msg => msg.type === 'image') - .map(msg => msg.content); - - wx.previewImage({ - current, - urls + // 前往消息关联的业务页面 + onJumpTap() { + const jumpPage = String(this.data.jumpPage || '').replace(/^\//, ''); + if (!jumpPage) { + return; + } + const pathname = jumpPage.split('?')[0]; + //tabBar 页只能 switchTab(不支持带参数),其余走 navigateTo + if (TAB_PAGES.indexOf(pathname) !== -1) { + wx.switchTab({ + url: '/' + pathname, + fail: () => { + wx.showToast({ title: '页面跳转失败', icon: 'none' }); + } + }); + return; + } + wx.navigateTo({ + url: '/' + jumpPage, + fail: () => { + wx.showToast({ title: '页面跳转失败', icon: 'none' }); + } }); }, - // 格式化时间 - formatTime(date) { - const hour = date.getHours().toString().padStart(2, '0'); - const minute = date.getMinutes().toString().padStart(2, '0'); - return `${hour}:${minute}`; - }, - - /** - * 生命周期函数--监听页面加载 - */ - onLoad(options) { - // 设置页面标题为客服名称 - wx.setNavigationBarTitle({ - title: this.serviceNickname - }); - - }, - - /** - * 生命周期函数--监听页面初次渲染完成 - */ - onReady() { - - }, - - /** - * 生命周期函数--监听页面显示 - */ - onShow() { - - }, - - /** - * 生命周期函数--监听页面隐藏 - */ - onHide() { - - }, - - /** - * 生命周期函数--监听页面卸载 - */ - onUnload() { - - }, - - /** - * 页面相关事件处理函数--监听用户下拉动作 - */ - onPullDownRefresh() { - - }, - - /** - * 页面上拉触底事件的处理函数 - */ - onReachBottom() { - - }, - - /** - * 用户点击右上角分享 - */ - onShareAppMessage() { - - } -}) \ No newline at end of file + onShareAppMessage() { } +}); diff --git a/frontend/miniapp/pages/message/detail/index.json b/frontend/miniapp/pages/message/detail/index.json index b99150b..cf21abf 100644 --- a/frontend/miniapp/pages/message/detail/index.json +++ b/frontend/miniapp/pages/message/detail/index.json @@ -1,5 +1,9 @@ { + "navigationBarTitleText": "消息详情", "usingComponents": { - "t-icon": "tdesign-miniprogram/icon/icon" + "t-tag": "tdesign-miniprogram/tag/tag", + "t-button": "tdesign-miniprogram/button/button", + "t-empty": "tdesign-miniprogram/empty/empty", + "t-loading": "tdesign-miniprogram/loading/loading" } -} \ No newline at end of file +} diff --git a/frontend/miniapp/pages/message/detail/index.wxml b/frontend/miniapp/pages/message/detail/index.wxml index fc5f449..723b403 100644 --- a/frontend/miniapp/pages/message/detail/index.wxml +++ b/frontend/miniapp/pages/message/detail/index.wxml @@ -1,29 +1,29 @@ + - - - - - - - {{item.content}} - - - {{item.time}} - - - - - - - - - -