m0_70156489 1 month ago
parent
commit
684ed71ed2

+ 82 - 10
application/admin/controller/Procuremen.php

@@ -285,10 +285,12 @@ class Procuremen extends Backend
     /**
      * 手机端协助明细首页直达链接(登录后打开对应明细,免搜索)
      * 配置 application/extra/mproc.php:mobile_base_url、mobile_index_path
+     * 列表 Tab 写入 hash(mproc_tab),避免邮件客户端拆坏多 query 参数;浏览器端再补全 tab=
      *
-     * @param int $purchaseOrderDetailId purchase_order_detail 主键
+     * @param int    $purchaseOrderDetailId purchase_order_detail 主键
+     * @param string $listTab               rfq|draft|submitted|done|rfq_submitted,空则仅靠服务端按状态推断
      */
-    protected function buildMprocMobileOrderUrl($purchaseOrderDetailId)
+    protected function buildMprocMobileOrderUrl($purchaseOrderDetailId, $listTab = '')
     {
         static $mprocCfgLoaded = false;
         if (!$mprocCfgLoaded) {
@@ -308,12 +310,62 @@ class Procuremen extends Backend
         } else {
             $path = '/' . ltrim($path, '/');
         }
-        // 邮件 HTML 中多参数链接常被邮箱客户端错误解析(& 后参数丢失),仅保留 focus_eid 单参数 + hash 备份
+        $tab = trim((string)$listTab);
+        if (!in_array($tab, ['rfq', 'rfq_submitted', 'draft', 'submitted', 'done'], true)) {
+            $tab = '';
+        }
+        // 邮件 HTML 中多参数链接常被邮箱客户端错误解析(& 后参数丢失),仅保留 focus_eid 单参数 + hash 备份 Tab
         if ($eid > 0) {
-            return $base . $path . '?focus_eid=' . $eid . '#mproc_fe=' . $eid;
+            $hash = 'mproc_fe=' . $eid;
+            if ($tab !== '') {
+                $hash .= '&mproc_tab=' . $tab;
+            }
+
+            return $base . $path . '?focus_eid=' . $eid . '#' . $hash;
         }
 
-        return $base . $path . '?main_tab=orders';
+        return $base . $path . '?main_tab=orders' . ($tab !== '' ? ('&tab=' . $tab) : '');
+    }
+
+    /**
+     * 业务员手机端「订单询价」直达链接(默认待询价)
+     *
+     * @param int    $scydgyId 手工询价 scydgy_id(负数)
+     * @param string $status   pending|quoted
+     */
+    protected function buildMprocMobileRfqAddUrl($scydgyId = 0, $status = 'pending')
+    {
+        static $mprocCfgLoaded = false;
+        if (!$mprocCfgLoaded) {
+            $mprocCfgLoaded = true;
+            if (is_file(APP_PATH . 'extra/mproc.php')) {
+                Config::load(APP_PATH . 'extra/mproc.php', 'mproc');
+            }
+        }
+        $base = $this->resolveMprocMobilePublicBaseUrl();
+        if ($base === '') {
+            throw new \Exception('无法生成手机端链接,请检查站点访问地址或 application/extra/mproc.php 中的 mobile_base_url');
+        }
+        $indexPath = trim((string)Config::get('mproc.mobile_index_path'));
+        if ($indexPath === '') {
+            $indexPath = '/index.php/index/index/index';
+        } else {
+            $indexPath = '/' . ltrim($indexPath, '/');
+        }
+        $path = preg_replace('#/index$#', '/rfqadd', rtrim($indexPath, '/'));
+        if ($path === null || $path === '' || substr($path, -6) === '/index') {
+            $path = '/index.php/index/index/rfqadd';
+        }
+        $status = trim((string)$status) === 'quoted' ? 'quoted' : 'pending';
+        $sid = (int)$scydgyId;
+        $q = 'status=' . $status;
+        $hash = '';
+        if ($sid < 0) {
+            $q .= '&focus_sid=' . $sid;
+            $hash = '#mproc_fs=' . $sid;
+        }
+
+        return $base . $path . '?' . $q . $hash;
     }
 
     /**
@@ -8022,7 +8074,9 @@ class Procuremen extends Backend
             }
             $mprocUrl = '';
             if ($buildMobileLinks && $detailId > 0) {
-                $mprocUrl = $this->buildMprocMobileOrderUrl($detailId);
+                // 手工询价 → 待报价;协助下发 → 未提交
+                $listTab = ($sid < 0) ? 'rfq' : 'draft';
+                $mprocUrl = $this->buildMprocMobileOrderUrl($detailId, $listTab);
             }
             $detailLinks[] = [
                 'detail_id' => $detailId,
@@ -8112,6 +8166,9 @@ class Procuremen extends Backend
             throw new \Exception($companyName . ' 未填写手机号,无法发送短信');
         }
 
+        $emailTplScene = trim($emailTplScene) !== '' ? trim($emailTplScene) : 'review_email';
+        $linkListTab = (strpos($emailTplScene, 'rfq_') === 0) ? 'rfq' : 'draft';
+
         $sids = [];
         foreach ($mergeRows as $mr) {
             if (!is_array($mr)) {
@@ -8181,14 +8238,17 @@ class Procuremen extends Backend
             $detailLinks[] = [
                 'detail_id' => $detailId,
                 'cgymc'     => $gymc,
-                'url'       => $this->buildMprocMobileOrderUrl($detailId),
+                // 询价邮件 → 待报价;下发报价 → 未提交
+                'url'       => $this->buildMprocMobileOrderUrl(
+                    $detailId,
+                    ($linkListTab === 'rfq' || $sid < 0) ? 'rfq' : 'draft'
+                ),
             ];
         }
         if ($detailLinks === []) {
             throw new \Exception('未找到有效的手机端链接,无法发送通知');
         }
 
-        $emailTplScene = trim($emailTplScene) !== '' ? trim($emailTplScene) : 'review_email';
         $ctx = $this->buildIssueNotifyContext($mergeRows, $sysRqNotify);
         $orderTypeCtx = $this->resolveIssueNotifyOrderTypeFromBundle($bundle);
         $ctx['order_type'] = (string)($orderTypeCtx['order_type'] ?? '');
@@ -8966,6 +9026,7 @@ class Procuremen extends Backend
             . "工序名称:{cgymc}\n"
             . "本次数量:{this_quantity}\n"
             . "供应商报价:\n{supplier_quotes}\n"
+            . "平台链接:{platform_url}\n"
             . "需求发起人:{cywyxm}\n";
         try {
             $row = Db::table('purchase_sms_template')
@@ -8977,9 +9038,14 @@ class Procuremen extends Backend
         $now = date('Y-m-d H:i:s');
         if (is_array($row) && $row !== []) {
             $old = (string)($row['content'] ?? '');
-            // 仍用旧的「供应商:{supplier_name}」或没有报价占位符时,补上单价表格
+            // 无报价表格 / 无平台链接 / 链接不在「需求发起人」上方时,同步默认文案
+            $urlPos = strpos($old, '{platform_url}');
+            $cywyPos = strpos($old, '需求发起人');
+            $linkNotAboveStarter = ($urlPos === false)
+                || ($cywyPos !== false && $urlPos > $cywyPos);
             $needMigrate = (strpos($old, '{supplier_quotes}') === false)
-                || (strpos($old, '供应商:{supplier_name}') !== false);
+                || (strpos($old, '供应商:{supplier_name}') !== false)
+                || $linkNotAboveStarter;
             if ($needMigrate) {
                 $data = ['content' => $content];
                 if (trim((string)($row['title'] ?? '')) === '' || trim((string)($row['title'] ?? '')) === '询价通知') {
@@ -9970,6 +10036,11 @@ class Procuremen extends Backend
             $supplier = trim((string)($po['pick_company_name'] ?? ''));
         }
         $supplierQuotesHtml = $this->buildRfqSalesmanSupplierQuotesHtml($sid, $supplier);
+        try {
+            $platformUrl = $this->buildMprocMobileRfqAddUrl($sid, 'pending');
+        } catch (\Throwable $e) {
+            $platformUrl = '';
+        }
         try {
             $mailSubjectTpl = $this->resolveProcuremenEmailSubject('rfq_salesman_email');
         } catch (\Throwable $e) {
@@ -9996,6 +10067,7 @@ class Procuremen extends Backend
                 // 占位符,转 HTML 后再替换成表格,避免被 htmlspecialchars 转义
                 'supplier_quotes' => '__RFQ_SUPPLIER_QUOTES_TABLE__',
                 'cywyxm'          => $cywyxm,
+                'platform_url'    => $platformUrl,
             ];
             try {
                 $mailPlain = $this->renderNotifyTemplate('rfq_salesman_email', $notifyVars);

+ 1 - 1
application/admin/model/Purchasesmstemplate.php

@@ -94,7 +94,7 @@ class Purchasesmstemplate extends Model
             ['tag' => '{process_lines_html}', 'label' => '订单工序明细(表格)', 'example' => '<table>…</table>', 'scenes' => '协助下发/供应商询价邮箱'],
             ['tag' => '{order_type}', 'label' => '订单类型', 'example' => '普通订单(价格优先)', 'scenes' => '协助下发邮箱'],
             ['tag' => '{score_weights}', 'label' => '评分比例', 'example' => '质量30%、价格50%、交货20%', 'scenes' => '协助下发邮箱'],
-            ['tag' => '{platform_url}', 'label' => '平台链接', 'example' => 'https://…', 'scenes' => '协助下发/供应商询价/采购确认邮箱'],
+            ['tag' => '{platform_url}', 'label' => '平台链接', 'example' => 'https://…', 'scenes' => '协助下发/供应商询价/业务员询价/采购确认邮箱'],
         ];
     }
 }

+ 1 - 1
application/admin/view/procuremen/details_fragment.html

@@ -212,7 +212,7 @@
     }
     .procuremen-details-wrap .procuremen-oper-log li {
         display: grid;
-        grid-template-columns: 7.5em 10em 8em minmax(0, 1fr);
+        grid-template-columns: 14em 10em 8em minmax(0, 1fr);
         column-gap: 6px;
         align-items: start;
         padding: 8px 12px;

+ 13 - 3
application/index/controller/Index.php

@@ -494,7 +494,8 @@ class Index extends Frontend
         $okHome = stripos($s, 'index/index/index') !== false;
         $okInbound = stripos($s, 'index/index/inboundscore') !== false;
         $okDelivery = stripos($s, 'index/index/deliveryscore') !== false;
-        if (!$okHome && !$okInbound && !$okDelivery) {
+        $okRfqAdd = stripos($s, 'index/index/rfqadd') !== false;
+        if (!$okHome && !$okInbound && !$okDelivery && !$okRfqAdd) {
             return '';
         }
         if (stripos($s, 'index/index/login') !== false) {
@@ -5217,6 +5218,14 @@ class Index extends Frontend
         if ($defaultDept === '' || $defaultDept === '业务员' || $defaultDept === '管理员') {
             $defaultDept = '营销中心';
         }
+        $statusTab = trim((string)$this->request->get('status', 'pending'));
+        if ($statusTab !== 'quoted') {
+            $statusTab = 'pending';
+        }
+        $focusSid = (int)$this->request->param('focus_sid', 0);
+        if ($focusSid >= 0) {
+            $focusSid = 0;
+        }
         $this->view->assign('mprocProfile', $profile);
         $this->view->assign('mprocBootstrapToken', trim((string)($user['token'] ?? '')));
         $this->view->assign('mprocBootstrapKeepHours', $this->mprocKeepHours());
@@ -5224,8 +5233,9 @@ class Index extends Frontend
         $this->view->assign('defaultCclbmmc', $defaultDept);
         $this->view->assign('adminNickname', trim((string)($profile['contact_name'] ?? '')) ?: '业务员');
         $this->view->assign('rfqDeptOptions', $this->mprocLoadRfqDeptNameOptions());
-        $this->view->assign('rfqStatusTab', 'pending');
-        $this->view->assign('rows', $this->mprocLoadRfqAddRows($user, '', 'pending'));
+        $this->view->assign('rfqStatusTab', $statusTab);
+        $this->view->assign('rfqFocusSid', $focusSid);
+        $this->view->assign('rows', $this->mprocLoadRfqAddRows($user, '', $statusTab));
 
         return $this->view->fetch();
     }

+ 46 - 3
application/index/view/index/index.html

@@ -1162,6 +1162,21 @@
         return hm ? (parseInt(hm[1], 10) || 0) : 0;
     }
 
+    function mprocParseListTabFromHash(hash) {
+        if (!hash) {
+            return '';
+        }
+        var m = String(hash).match(/(?:^#|[?&])mproc_tab=([a-z_]+)/i);
+        if (!m) {
+            return '';
+        }
+        var t = m[1];
+        if (t === 'rfq_done') {
+            t = 'rfq_submitted';
+        }
+        return MPROC_LIST_TABS.indexOf(t) !== -1 ? t : '';
+    }
+
     function mprocGetFocusEid() {
         var fe = parseInt(boot.focus_eid, 10) || 0;
         if (fe > 0) {
@@ -1191,7 +1206,7 @@
         return 0;
     }
 
-    /** 邮箱内打开时 query 可能丢失但 hash 仍在:补全地址让服务端也能定位到正确 Tab */
+    /** 邮箱内打开时 query 可能丢失但 hash 仍在:补全 focus_eid + tab,让服务端落到未提交/待报价 */
     function mprocRepairFocusUrlIfNeeded() {
         var fe = mprocParseFocusEidFromHash(window.location.hash);
         if (fe <= 0) {
@@ -1200,13 +1215,30 @@
         if (fe <= 0) {
             return false;
         }
+        var tabFromHash = mprocParseListTabFromHash(window.location.hash);
         mprocPersistFocusEid(fe);
         boot.focus_eid = fe;
+        if (tabFromHash) {
+            boot.focus_tab = tabFromHash;
+        }
         var qs = window.location.search || '';
-        if (/(?:^|[?&])focus_eid=\d+/.test(qs)) {
+        var hasFocus = /(?:^|[?&])focus_eid=\d+/.test(qs);
+        var hasTab = false;
+        try {
+            hasTab = new URLSearchParams(qs.charAt(0) === '?' ? qs.substring(1) : qs).has('tab');
+        } catch (ignoreTab) {}
+        var needRepair = !hasFocus || (tabFromHash && !hasTab);
+        if (!needRepair) {
             return false;
         }
-        var target = window.location.pathname + '?focus_eid=' + fe + '#mproc_fe=' + fe;
+        var target = window.location.pathname + '?focus_eid=' + fe;
+        if (tabFromHash) {
+            target += '&tab=' + encodeURIComponent(tabFromHash) + '&main_tab=orders';
+        }
+        target += '#mproc_fe=' + fe;
+        if (tabFromHash) {
+            target += '&mproc_tab=' + tabFromHash;
+        }
         if (target !== window.location.pathname + window.location.search + window.location.hash) {
             location.replace(target);
             return true;
@@ -2796,6 +2828,17 @@
                 urlHasTab = new URL(window.location.href).searchParams.has('tab');
             } catch (ignoreUrl) {}
             var focusEid = mprocGetFocusEid();
+            var hashTab = mprocParseListTabFromHash(window.location.hash);
+            var preferTab = '';
+            if (hashTab) {
+                preferTab = hashTab;
+            } else if (boot.focus_tab && MPROC_LIST_TABS.indexOf(boot.focus_tab) !== -1) {
+                preferTab = boot.focus_tab;
+            }
+            if (focusEid > 0 && preferTab && preferTab !== currentListTab) {
+                fetchOrderList(preferTab, getSearchQ());
+                return;
+            }
             if (!urlHasTab && focusEid <= 0) {
                 var saved = '';
                 try {

+ 20 - 1
application/index/view/index/login.html

@@ -300,6 +300,10 @@
         if (!url) {
             return url;
         }
+        // 业务员询价页勿改写成 focus_eid(会丢掉 status=pending)
+        if (/rfqadd/i.test(String(url)) || /rfqadd/i.test(String(redir || ''))) {
+            return url;
+        }
         if (/(?:\?|#)(?:mproc_fe=|focus_eid=)\d+/.test(url)) {
             return url;
         }
@@ -307,8 +311,23 @@
         if (fe <= 0) {
             return url;
         }
+        var tab = '';
+        try {
+            var hm = String(redir || '').match(/(?:^#|[?&#])mproc_tab=([a-z_]+)/i);
+            if (hm) {
+                tab = hm[1];
+            }
+        } catch (ignoreTab) {}
         var base = url.split('#')[0].split('?')[0];
-        return base + '?focus_eid=' + encodeURIComponent(String(fe)) + '#mproc_fe=' + fe;
+        var out = base + '?focus_eid=' + encodeURIComponent(String(fe));
+        if (tab) {
+            out += '&tab=' + encodeURIComponent(tab) + '&main_tab=orders';
+        }
+        out += '#mproc_fe=' + fe;
+        if (tab) {
+            out += '&mproc_tab=' + tab;
+        }
+        return out;
     }
 
     function mprocTokenExpireMs(d) {

+ 69 - 2
application/index/view/index/rfqadd.html

@@ -93,6 +93,10 @@
             background: #fff; border-radius: 10px; padding: 12px 14px;
             margin-bottom: 10px; box-shadow: 0 1px 4px rgba(0,0,0,.06);
         }
+        .card.rfq-card-highlight {
+            outline: 2px solid #3c8dbc;
+            box-shadow: 0 0 0 3px rgba(60,141,188,.18);
+        }
         .card .ord {
             font-size: 15px; font-weight: 700; color: #222; line-height: 1.4; word-break: break-all;
         }
@@ -294,7 +298,7 @@
         <div class="list-wrap" id="listMain">
         {if $rows}
         {volist name="rows" id="row"}
-        <div class="card">
+        <div class="card{if condition="isset($rfqFocusSid) && $rfqFocusSid && $rfqFocusSid == $row.scydgy_id"} rfq-card-highlight{/if}" data-scydgy-id="{$row.scydgy_id}">
             <div class="ord">{$row.CCYDH|default=''|htmlentities}{if $row.CYJMC} {$row.CYJMC|htmlentities}{/if}</div>
             <div class="meta">
                 <div><b>需求部门:</b>{$row.CCLBMMC|default=''|htmlentities}</div>
@@ -464,6 +468,50 @@
     var nextCcydh = {:json_encode(isset($nextOrderCcydh) ? (string)$nextOrderCcydh : '', JSON_UNESCAPED_UNICODE)};
     var currentStatus = {:json_encode(isset($rfqStatusTab) ? (string)$rfqStatusTab : 'pending', JSON_UNESCAPED_UNICODE)};
     if (currentStatus !== 'quoted') currentStatus = 'pending';
+    var focusSid = {:json_encode(isset($rfqFocusSid) ? (int)$rfqFocusSid : 0, JSON_UNESCAPED_UNICODE)};
+
+    /** 邮件链接:补全 status=pending + focus_sid,首次登录后进入待询价 */
+    function rfqRepairDeepLinkIfNeeded() {
+        var sid = focusSid;
+        var hash = window.location.hash || '';
+        if (!(sid < 0)) {
+            var hm = hash.match(/(?:^#|[?&])mproc_fs=(-?\d+)/i);
+            if (hm) {
+                sid = parseInt(hm[1], 10) || 0;
+            }
+        }
+        if (!(sid < 0)) {
+            try {
+                var sp = new URLSearchParams((window.location.search || '').replace(/^\?/, ''));
+                var qsSid = parseInt(sp.get('focus_sid'), 10) || 0;
+                if (qsSid < 0) {
+                    sid = qsSid;
+                }
+            } catch (ignoreQs) {}
+        }
+        if (!(sid < 0)) {
+            return false;
+        }
+        focusSid = sid;
+        var st = currentStatus === 'quoted' ? 'quoted' : 'pending';
+        var qs = window.location.search || '';
+        var hasStatus = /(?:^|[?&])status=/.test(qs);
+        var hasFocus = /(?:^|[?&])focus_sid=/.test(qs);
+        if (hasStatus && hasFocus) {
+            return false;
+        }
+        var target = window.location.pathname + '?status=' + encodeURIComponent(st)
+            + '&focus_sid=' + encodeURIComponent(String(sid))
+            + '#mproc_fs=' + sid;
+        if (target !== window.location.pathname + window.location.search + window.location.hash) {
+            location.replace(target);
+            return true;
+        }
+        return false;
+    }
+    if (rfqRepairDeepLinkIfNeeded()) {
+        return;
+    }
 
     try {
         if (token) {
@@ -572,7 +620,9 @@
             var name = String(row.CYJMC || '').trim();
             if (title && name) title += ' ' + name;
             else title = title || name || '';
-            html += '<div class="card">'
+            var sid = parseInt(row.scydgy_id, 10) || 0;
+            var hit = (focusSid < 0 && sid === focusSid) ? ' rfq-card-highlight' : '';
+            html += '<div class="card' + hit + '" data-scydgy-id="' + esc(sid) + '">'
                 + '<div class="ord">' + esc(title) + '</div>'
                 + '<div class="meta">'
                 + '<div><b>需求部门:</b>' + esc(row.CCLBMMC || '') + '</div>'
@@ -591,6 +641,21 @@
             html += '</div>';
         });
         main.innerHTML = html;
+        rfqScrollToFocus();
+    }
+
+    function rfqScrollToFocus() {
+        if (!(focusSid < 0)) {
+            return;
+        }
+        var card = document.querySelector('.card[data-scydgy-id="' + focusSid + '"]');
+        if (!card) {
+            return;
+        }
+        card.classList.add('rfq-card-highlight');
+        try {
+            card.scrollIntoView({ block: 'center' });
+        } catch (ignoreScroll) {}
     }
 
     function loadList() {
@@ -1015,6 +1080,8 @@
             });
         }
     })();
+    setTimeout(rfqScrollToFocus, 80);
+    setTimeout(rfqScrollToFocus, 400);
 })();
 </script>
 </body>