約系統(tǒng)源碼拆解與二次開發(fā))
簡介一套基于ThinkPHPMySQLuniappFastAdmin后臺開發(fā)的上門家政按摩H5小程序與預(yù)約系統(tǒng)源碼面向需要快速落地同城O2O上門服務(wù)的開發(fā)者、外包團(tuán)隊(duì)及產(chǎn)品經(jīng)理可直接部署或二次開發(fā)。程序已測試通過配套完整包含公眾號通知、后端主程序、前端uniapp源碼、數(shù)據(jù)庫SQL及通知音頻文件。資源包共2000個文件大小66.31MB主要文件類型為865個JavaScript、546個Vue組件、350個Markdown文檔、91個JSON配置以及HTML、CSS、XML、Shell、SQL、Doc、Python等文件覆蓋前端頁面、接口交互、數(shù)據(jù)庫腳本和環(huán)境配置。運(yùn)行環(huán)境需LinuxNginxPHP7.2MySQL5.6并安裝fileinfo、Redis擴(kuò)展系統(tǒng)基于微信生態(tài)需備案域名、認(rèn)證服務(wù)號、微信支付及小程序賬號H5指公眾號H5須在微信中訪問。目前已有255人學(xué)習(xí)下載。可借源碼理解FastAdmin權(quán)限管理與uniapp多端開發(fā)的完整協(xié)作流程掌握預(yù)約下單、服務(wù)派單、支付回調(diào)、公眾號通知和音頻提醒等關(guān)鍵模塊的落地實(shí)現(xiàn)是學(xué)習(xí)同城上門預(yù)約類項(xiàng)目的實(shí)用參考。1. 上門預(yù)約系統(tǒng)到底解決了什么問題一套三端代碼的本地生活服務(wù)樣板做本地生活服務(wù)的人應(yīng)該都有同感用戶端想要小程序順手還能分享H5管理端要能派單、能結(jié)算、能調(diào)整服務(wù)價格兩頭都不能少。這套基于 thinkphp mysql uniapp 組合的上門預(yù)約系統(tǒng)把幾個端一次性湊齊了——前臺用 uni-app 生成微信小程序和 H5后臺用 fastadmin 搭好了管理界面服務(wù)端用 ThinkPHP 寫接口MySQL 負(fù)責(zé)訂單、技師、用戶、地址這些核心數(shù)據(jù)。對想快速上線上門家政、按摩、保潔這類業(yè)務(wù)的團(tuán)隊(duì)來說它省掉的不只是 UI 工作量還有從下單、派單到完工結(jié)算的整條業(yè)務(wù)鏈路。下面按源碼實(shí)際結(jié)構(gòu)拆開講。2. 三端架構(gòu)拆解ThinkPHP、FastAdmin 與 uni-app 如何各司其職這套系統(tǒng)的架構(gòu)一句話能說清一個基于 ThinkPHP 的 FastAdmin 應(yīng)用提供全部后端能力用戶端是獨(dú)立的 uni-app 工程數(shù)據(jù)統(tǒng)一存在 MySQL 里。FastAdmin 跑在同一個 PHP 應(yīng)用上通過 admin 模塊提供后臺用戶端走 api 模塊兩者共用同一套模型和配置。這種一個應(yīng)用兩套入口的結(jié)構(gòu)是 FastAdmin 系源碼最常見的部署形態(tài)好處是二次開發(fā)時不用維護(hù)兩套后端壞處是路由和權(quán)限要分清楚別讓前端接口和管理頁面混在一起。2.1 為什么是這套組合二次開發(fā)視角的選型邏輯先說結(jié)論這套組合選的不是最強(qiáng)的技術(shù)而是最省人力的技術(shù)。FastAdmin 基于 ThinkPHP自帶管理員登錄、權(quán)限節(jié)點(diǎn)、通用列表和表單生成后臺管理頁基本不用從零寫uni-app 用 Vue 語法寫一套代碼編譯輸出微信小程序和 H5對上門預(yù)約這種重分享、輕安裝的業(yè)務(wù)剛好合適thinkphp mysql 的部署資料多一臺 Nginx 就能撐起一個小型平臺。從改造角度看這套選型還有一個隱藏優(yōu)點(diǎn)api 模塊和 admin 模塊在同一個應(yīng)用里訂單模型在 application/common/model 下是前后端共用的。后面加字段、改狀態(tài)、接支付只需要改一處模型不會出現(xiàn)后臺改完了接口沒跟上的問題。等業(yè)務(wù)量上來接口和后臺也可以拆成兩個應(yīng)用分別部署目錄結(jié)構(gòu)已經(jīng)留好了拆分邊界。2.2 目錄結(jié)構(gòu)后端、后臺、前端各自的主力目錄與用途源碼解壓后主要看兩部分PHP 后端工程和 uniapp 前端工程。下面這張表把最常用的目錄列出來其他目錄大多可以暫時忽略。關(guān)注點(diǎn)目錄說明站點(diǎn)入口public/Web 根目錄必須指向這里下有 index.php 與 uploads后臺控制器application/admin/controller/FastAdmin 管理端按訂單、服務(wù)、技師等模塊分文件前臺接口application/api/controller/H5 / 小程序請求的控制器Order、Service、Technician 等數(shù)據(jù)層application/common/model/訂單模型、技師模型前后端共用數(shù)據(jù)庫配置application/database.php數(shù)據(jù)庫連接參數(shù)、表前綴前端頁面uniapp/pages/首頁、服務(wù)列表、確認(rèn)訂單、訂單詳情等頁面前端公共uniapp/common/request.js 等公共方法幾個重點(diǎn)目錄展開說。application/api/controller 是用戶端實(shí)際打到的接口層FastAdmin 的 API 模塊路由通常是 index.php?s/api/控制器/方法前端請求路徑會拼成類似 /api/order/create 的形式。public 下的 uploads 存放后臺上傳的服務(wù)圖、技師頭像站點(diǎn)根目錄必須指向 public否則上傳文件會落到外部目錄導(dǎo)致前端加載不到。uniapp 前端里pages.json 控制頁面路由和 tabBarmanifest.json 是小程序 appid、H5 域名這些配置的集中地聯(lián)調(diào)時改的最多的就是這兩個文件加 common/request.js。2.3 數(shù)據(jù)庫設(shè)計(jì)訂單表、技師表、服務(wù)表怎么配合表前綴默認(rèn)是 fa_安裝時也可以改。核心就三張業(yè)務(wù)表加一張用戶表。服務(wù)項(xiàng)目表結(jié)構(gòu)大致是這樣字段類型說明idint主鍵namevarchar服務(wù)名稱如保開關(guān)、推拿pricedecimal(10,2)單價單位元unitvarchar計(jì)費(fèi)單位次 / 小時service_minutesint預(yù)計(jì)服務(wù)時長category_idint所屬分類statustinyint1 上架0 下架技師表 fa_technician 除了姓名、頭像、手機(jī)號之外最重要的是 lng、lat、server_radius 三個字段。lng 和 lat 是技師當(dāng)前位置坐標(biāo)server_radius 是服務(wù)半徑單位公里派單時用這兩個字段做距離篩選。訂單表 fa_order 則是業(yè)務(wù)核心包含 order_no 訂單號、user_id、service_id、technician_id、address、address_coordinate、amount、status、serve_time、pay_time、finish_time、remark 和創(chuàng)建時間。address_coordinate 字段存的是經(jīng)度,緯度逗號拼接的字符串派單 SQL 會直接用它做計(jì)算。訂單狀態(tài) status 是這套源碼的靈魂常見的定義是 1 待支付、2 已支付待派單、3 已派單、4 服務(wù)中、5 已完成、6 已取消。價格字段統(tǒng)一 decimal(10,2)不要用 float否則累計(jì)對賬時會出現(xiàn)精度問題。這些字段定義是理解后面核心代碼的前提。3. 核心代碼鏈路一次預(yù)約從下單到派單的完整實(shí)現(xiàn)把用戶操作到后臺派單的過程串一遍用戶選服務(wù)填地址和時間提交訂單后臺按距離找技師技師接單開始服務(wù)完成訂單。對應(yīng)到代碼上是三個核心文件前端的請求封裝、后端的下單控制器、派單控制器。下面按這條鏈路逐個拆。3.1 統(tǒng)一請求封裝token 與 401 跳轉(zhuǎn)都收在一處所有前端請求走同一個封裝函數(shù)是這類項(xiàng)目最基礎(chǔ)也最重要的約定。uniapp 工程里 common/request.js 負(fù)責(zé)統(tǒng)一拼接域名、帶 token、處理登錄過期。// uniapp/common/request.js const BASE_URL https://api.yourdomain.com/api; function request(path, data {}, method GET) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL path, method, data, header: { token: uni.getStorageSync(token) || , Content-Type: application/json }, success: res { // 后端統(tǒng)一返回 code 字段401 表示登錄態(tài)失效 if (res.data.code 401) { uni.removeStorageSync(token); uni.navigateTo({ url: /pages/login/login }); reject(res.data); return; } resolve(res.data); }, fail: reject }); }); } export default request;邏輯說明每個請求自動從本地緩存拿 token 放進(jìn) header后端靠這個 token 識別用戶身份一旦接口返回 401說明 token 失效或未登錄前端統(tǒng)一清理本地狀態(tài)并跳轉(zhuǎn)登錄頁。BASE_URL 是聯(lián)調(diào)時第一個要替換的地方后續(xù)所有頁面都通過 import request 來發(fā)請求不要分散在各頁面里用 uni.request 裸調(diào)。參數(shù)說明path 是接口路徑data 是請求體method 默認(rèn) GETPOST 接口調(diào)用時顯式傳入第三個參數(shù)。后端要求的 token 字段名是 token如果你的項(xiàng)目改成了 Authorization這里和 FastAdmin 的 API 驗(yàn)權(quán)配置要同步改否則會出現(xiàn)登錄成功但業(yè)務(wù)接口全部未登錄的問題。3.2 創(chuàng)建訂單接口必傳參數(shù)、狀態(tài)初值、事務(wù)用戶點(diǎn)提交訂單時前端把服務(wù) ID、地址、期望上門時間、備注 POST 到 api/order/create。后臺控制器的核心邏輯是校驗(yàn)服務(wù)是否存在組裝訂單數(shù)據(jù)寫庫并返回訂單 ID。// application/api/controller/Order.php public function create() { $uid $this-user_id; $service_id $this-request-post(service_id); $address $this-request-post(address); $serve_time $this-request-post(serve_time); $remark $this-request-post(remark, ); $service ServiceModel::get($service_id); if (!$service || $service-status ! 1) { $this-error(服務(wù)項(xiàng)目不存在或已下架); } $order_data [ order_no date(YmdHis) . mt_rand(1000, 9999), user_id $uid, service_id $service_id, amount $service-price, address $address, serve_time $serve_time, remark $remark, status 1 // 待支付 ]; Db::startTrans(); try { $order OrderModel::create($order_data); Db::commit(); $this-success(下單成功, [order_id $order-id]); } catch (\Exception $e) { Db::rollback(); $this-error(下單失敗請重試); } }邏輯說明先查服務(wù)表確認(rèn)服務(wù)存在且在上架狀態(tài)再組裝訂單數(shù)據(jù)。order_no 用時間加隨機(jī)數(shù)生成保證不會重復(fù)amount 直接取服務(wù)價格不讓前端傳價格這是防止用戶篡改金額的基本做法。status 初始化為 1 待支付。事務(wù)在這里雖然沒有跨表寫入但后續(xù)如果加優(yōu)惠券核銷、加積分記錄事務(wù)就非常必要了所以保留這個結(jié)構(gòu)。參數(shù)說明service_id 和 address 是必傳serve_time 是預(yù)約時間remark 可選。前端傳價格一律不接收。下單成功后返回 order_id前端再用這個訂單 ID 喚起支付或跳轉(zhuǎn)訂單詳情。如果想加提交訂單時鎖定技師在 order_data 里加 technician_id 字段即可第 6 章會專門講這個改造。3.3 附近技師查詢一段基于經(jīng)緯度的派單 SQL派單是整個上門預(yù)約系統(tǒng)的核心難點(diǎn)。這套源碼里的做法很直接以訂單地址的經(jīng)緯度為中心計(jì)算技師表里每個在崗技師的直線距離過濾出服務(wù)半徑內(nèi)的技師按距離升序返回。// application/api/controller/Assign.php public function findNearby() { $order_id $this-request-post(order_id); $order OrderModel::get($order_id); if (!$order) { $this-error(訂單不存在); } list($lng, $lat) explode(,, $order-address_coordinate); $list TechnicianModel::where(status, 1) -field(id, name, avatar, server_radius, (6378.138 * 2 * ASIN(SQRT(POW(SIN(({$lat} * PI() / 180 - lat * PI() / 180) / 2), 2) COS({$lat} * PI() / 180) * COS(lat * PI() / 180) * POW(SIN(({$lng} * PI() / 180 - lng * PI() / 180) / 2), 2)))) AS distance) -having(distance server_radius) -order(distance) -select(); $this-success(附近技師, $list); }邏輯說明這段 SQL 用的是 Haversine 公式6378.138 是地球半徑公里數(shù)distance 的單位是公里。查詢條件有兩個技師狀態(tài)為 1在崗且算出來的距離小于技師自己設(shè)定的服務(wù)半徑。結(jié)果按距離從近到遠(yuǎn)排前端拿到的就是一個可選的技師列表。參數(shù)說明address_coordinate 字段必須是經(jīng)度,緯度格式如果為空這條 SQL 會直接報錯。常見的兜底做法是退化成按城市加狀態(tài)篩選先在地址表里解析出城市字段然后查 city_id 匹配的技師。這個兜底建議在改造時加上能避免訂單因?yàn)槿弊鴺?biāo)卡死在派單環(huán)節(jié)。3.4 FastAdmin 后臺訂單列表與狀態(tài)切換怎么配置后端接口就緒后管理后臺的訂單列表不需要手寫頁面FastAdmin 提供了命令行生成器。在應(yīng)用根目錄執(zhí)行以下命令就能給 fa_order 表生成一套標(biāo)準(zhǔn)的后臺增刪改查php think crud -t fa_order -u 1執(zhí)行后application/admin 下會出現(xiàn) Order 控制器和視圖文件后臺菜單自動掛到 admin/order 下。列表展示哪些字段、字段怎么格式化由 application/admin/view/order/index.html 里對應(yīng)的 JS 配置 control 決定。訂單狀態(tài)切換是運(yùn)營最頻繁的操作對應(yīng)的代碼邏輯通常長這樣// application/admin/view/order/index.html 對應(yīng)的事件綁定 $(.btn-change-status).on(click, function () { var id $(this).data(id); $.ajax({ url: order/changeStatus, data: { id: id, status: 3 }, // 3 表示已派單 success: function (res) { if (res.code 1) { window.location.reload(); } } }); });邏輯說明changeStatus 是后臺控制器里自定義的方法接收訂單 ID 和目標(biāo)狀態(tài)更新后刷新列表。這套結(jié)構(gòu)的好處是每個狀態(tài)流轉(zhuǎn)動作都可以掛獨(dú)立的校驗(yàn)比如從服務(wù)中改成已完成時強(qiáng)制填寫完結(jié)金額從已派單改成已取消時記錄取消原因。參數(shù)說明data 里的 id 是訂單主鍵status 是目標(biāo)狀態(tài)碼。后臺控制器里對應(yīng)接收這兩個參數(shù)并在操作前用管理員權(quán)限做一次驗(yàn)權(quán)防止越權(quán)改單。4. 本地搭建與運(yùn)行從空服務(wù)器到 H5小程序跑通這一章目標(biāo)很直接把這套源碼在你自己的機(jī)器上跑起來。先說后端初始化再說前端聯(lián)調(diào)最后給一個三端已經(jīng)連通的判斷方法。整個過程中 90% 的問題集中在配置和路徑上代碼本身基本不用動。4.1 后端初始化導(dǎo)庫、改配置、配偽靜態(tài)環(huán)境準(zhǔn)備按 FastAdmin 的常規(guī)要求來PHP 7.1 到 7.4 最穩(wěn)MySQL 5.7 以上Nginx 或 Apache 都行。先把數(shù)據(jù)庫建好再把源碼自帶的 SQL 文件導(dǎo)入。SQL 文件一般在根目錄或 sql 目錄下不同發(fā)行版位置不同先找一下。mysql -uroot -p -e create database fastadmin_home default charset utf8mb4; mysql -uroot -p fastadmin_home install.sql兩條命令的含義第一條創(chuàng)建名為 fastadmin_home 的數(shù)據(jù)庫指定 utf8mb4 字符集第二條把 install.sql 里的表結(jié)構(gòu)和初始數(shù)據(jù)導(dǎo)入這個庫。導(dǎo)入成功后會看到 fa_user、fa_order、fa_technician 等表。接著修改 application/database.php把數(shù)據(jù)庫連接參數(shù)換成你自己的。需要改的字段是 hostname、database、username、password、prefix。prefix 默認(rèn) fa_如果導(dǎo)入的 SQL 里表前綴不是 fa_這里必須改成一致的否則后臺登錄會報表不存在。最后是偽靜態(tài)配置。FastAdmin 的路由是 pathinfo 風(fēng)格Nginx 下站點(diǎn)根目錄要指向 public并加上 rewrite。配置示例server { listen 80; server_name localhost; root /path/to/項(xiàng)目根目錄/public; index index.php; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } } }提示根目錄指錯是最常見的啟動失敗原因。一定要指到 public 目錄不是項(xiàng)目根目錄。配置完成后訪問 http://localhost/index.php?s/admin 進(jìn)入后臺默認(rèn)管理員賬號通常為 admin密碼 admin 或 123456。上線前第一件事就是改掉默認(rèn)密碼不要在這上面偷懶。4.2 前端聯(lián)調(diào)H5 與微信小程序?qū)雍蠖说牡胤讲灰粯忧岸斯こ逃?HBuilderX 打開 uniapp 目錄第一步修改 common/request.js 里的 BASE_URL。如果后端跑在本機(jī)H5 開發(fā)模式建議用代理解決跨域不用改 BASE_URL 的域名。manifest.json 的 h5 節(jié)點(diǎn)配置本地代理// uniapp manifest.json 的開發(fā)配置片段 h5: { devServer: { proxy: { /api: { target: http://127.0.0.1:8080, changeOrigin: true } } } }邏輯說明開發(fā)環(huán)境下前端請求 /api 開頭的地址會自動轉(zhuǎn)發(fā)到本機(jī) 8080 端口的 PHP 應(yīng)用繞開瀏覽器跨域限制。生產(chǎn)環(huán)境 H5 直接配 HTTPS 域名不需要代理。微信小程序和 H5 的差別在域名校驗(yàn)。微信開發(fā)者工具里默認(rèn)會校驗(yàn) request 合法域名本地聯(lián)調(diào)時可以在詳情 → 本地設(shè)置里勾選不校驗(yàn)合法域名。但真機(jī)預(yù)覽和體驗(yàn)版必須在小程序后臺把 API 域名加入 request 合法域名而且要求是已備案的 HTTPS 域名。這點(diǎn)后面避坑章還會再提。4.3 三端已連通的判斷方法跑通后怎么確認(rèn)鏈路是通的按三條線自檢第一打開后端 runtime/log 目錄下的日志文件看請求是否進(jìn)來、在哪一步停下第二用接口調(diào)試工具先調(diào)登錄接口拿 token再帶 token 調(diào)一次訂單列表接口接口能通說明 PHP 應(yīng)用本身沒問題第三在后臺創(chuàng)建一個測試訂單然后查 MySQL 的 fa_order 表能看到新記錄就說明數(shù)據(jù)鏈路完整。這三條自檢順序固定日志確認(rèn)請求到達(dá)接口確認(rèn)邏輯正確數(shù)據(jù)庫確認(rèn)落庫成功。按這個順序排查能把前端問題和后端問題快速分開避免在兩端來回猜。5. 避坑記錄搭建與聯(lián)調(diào)中最常踩的五個坑這一章是實(shí)際操作里反復(fù)遇到的坑按現(xiàn)象到原因再到解決展開。每一條都對應(yīng)一個具體的排查路徑希望你看完之后遇到同類問題能直接定位。5.1 小程序請求全部失敗url not in domain list現(xiàn)象微信開發(fā)者工具里所有 uni.request 請求都報 request:fail url not in domain list頁面數(shù)據(jù)全空。原因小程序平臺對 request 接口有域名白名單限制。開發(fā)者工具默認(rèn)開啟合法域名校驗(yàn)而你的 API 域名沒在小程序后臺配置或者開發(fā)者工具里的不校驗(yàn)合法域名選項(xiàng)沒勾上。解決本地聯(lián)調(diào)階段在微信開發(fā)者工具的詳情 → 本地設(shè)置里勾選不校驗(yàn)合法域名真機(jī)預(yù)覽和發(fā)布前到微信公眾平臺把 API 域名加入 request 合法域名。注意域名必須是 HTTPS、已備案且協(xié)議和域名要完全一致http 和 https 不能混著填。5.2 后臺圖片上傳成功前端加載不出來現(xiàn)象FastAdmin 后臺上傳的服務(wù)圖片、技師頭像顯示正常但小程序和 H5 頁面請求這些圖片時 404 或空白。原因上傳文件默認(rèn)落在 public/uploads 目錄正確訪問路徑是 /uploads/xxx.jpg。但很多部署把站點(diǎn)根目錄指到了項(xiàng)目根目錄導(dǎo)致上傳文件實(shí)際在項(xiàng)目根目錄以外或路徑不對前端拼接的 URL 自然打不開。解決先把 Nginx 站點(diǎn)根目錄固定到 public這是根本前端圖片地址用相對路徑 /uploads/xxx.jpg不要在前端寫死帶域名和端口號的絕對地址避免域名一換就大面積裂圖。5.3 登錄成功但其他接口全部提示未登錄現(xiàn)象登錄接口返回 token 正常前端也存進(jìn)了緩存但一點(diǎn)進(jìn)訂單詳情就跳回登錄頁打開接口返回 401 未登錄。原因前后端 token 字段名不一致。前端 header 里發(fā)送的是 token后端 API 驗(yàn)權(quán)默認(rèn)也從 header 的 token 取如果某一端改成了 Authorization或者大小寫不一致驗(yàn)權(quán)就會失敗。另一種情況是 token 本身已過期后端正確返回 401前端統(tǒng)一跳轉(zhuǎn)看起來像沒登錄上。解決找到后端 API 驗(yàn)權(quán)的入口確認(rèn)它從哪個 header 取 token把前端 request.js 的 header 改成一致。同時在登錄接口里檢查 token 有效期配置開發(fā)階段可以把過期時間調(diào)長一點(diǎn)減少干擾。5.4 SQL 導(dǎo)入失敗字符集與版本不匹配現(xiàn)象導(dǎo)入 install.sql 時報錯常見信息有 Index column size too large 或 Unknown collation: utf8mb4_unicode_ci。原因SQL 文件是按 MySQL 5.7 和 utf8mb4 生成的你本地 MySQL 版本過低或者建庫時沒有指定字符集導(dǎo)致表結(jié)構(gòu)里的索引和排序規(guī)則不被支持。解決建庫時顯式指定 utf8mb4命令用 create database xxx default charset utf8mb4把 MySQL 升級到 5.7 以上。如果還有報錯檢查 sql_mode 是不是嚴(yán)格模式臨時可以關(guān)掉 strict mode 再導(dǎo)入。5.5 訂單狀態(tài)對不上前端永遠(yuǎn)顯示待支付現(xiàn)象后臺訂單已經(jīng)是已支付用戶端卻一直顯示待支付狀態(tài)名和實(shí)際流轉(zhuǎn)對不上。原因訂單狀態(tài)枚舉的前后端定義不一致。后臺代碼把 1 定義為待支付前端頁面把 0 定義為待支付兩邊沒有共用同一份狀態(tài)字典。解決在前后端各維護(hù)一份公共狀態(tài)字典后端在 common 常量里定義前端在 common/status.js 里定義字段順序和數(shù)值必須完全一致。凡是展示狀態(tài)的地方都從字典取值禁止在頁面里寫死狀態(tài)數(shù)字。改完順手把 fa_order 表里歷史數(shù)據(jù)的 status 也校正一遍。6. 二次開發(fā)實(shí)戰(zhàn)把后臺派單改成用戶自選技師最后做一個最典型的業(yè)務(wù)改造把默認(rèn)的后臺指派技師改成用戶自己選技師。這個改動覆蓋數(shù)據(jù)庫、接口、前端三處做完之后這套源碼的骨架你基本就摸清了。6.1 先鎖定改動范圍要改的地方總共三處加一個字段。數(shù)據(jù)庫給 fa_order 表加 technician_id 字段存用戶選的技師 ID后端創(chuàng)建訂單接口加一個可選參數(shù) technician_id并在傳值時把訂單狀態(tài)改成待技師接單前端確認(rèn)訂單頁加一個技師選擇列表展示附近技師和距離。后臺派單流程保留作為無人選擇時的兜底。ALTER TABLE fa_order ADD COLUMN technician_id INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 用戶自選技師ID AFTER service_id;邏輯說明technician_id 默認(rèn) 0 表示未指定非 0 表示用戶指定。訂單詳情查詢時聯(lián)查 fa_technician 表就能拿到技師姓名和頭像。6.2 前后端的具體改法前端在用戶選完服務(wù)后調(diào)一次附近技師接口把結(jié)果渲染成可選列表。距離字段在后端返回時單位是公里展示前格式化一下會更友好。// 預(yù)約確認(rèn)頁的技師選擇邏輯 async function loadTechnicians(serviceId) { const res await request(/order/canAssign, { service_id: serviceId }, POST); const list res.data.map(t ({ ...t, distanceText: t.distance 1 ? t.distance.toFixed(2) km : Math.round(t.distance * 1000) m })); this.technicians list; }邏輯說明canAssign 接口內(nèi)部就是第 3 章那段 Haversine 查詢只是入口從訂單 ID 換成了服務(wù) ID。前端拿到列表后用戶點(diǎn)選某個技師就把技師 ID 存進(jìn)表單提交訂單時隨請求體一起發(fā)送。后端在創(chuàng)建訂單接口里加一段判斷$technician_id $this-request-post(technician_id, 0); if ($technician_id) { $tech TechnicianModel::get($technician_id); if (!$tech || $tech-status ! 1) { $this-error(技師不可用請重新選擇); } $order_data[technician_id] $technician_id; $order_data[status] 2; // 待技師接單原 2 是已支付待派單這里沿用 2 }邏輯說明傳了 technician_id 才會校驗(yàn)技師狀態(tài)并把訂單狀態(tài)置為待技師接單不傳則維持原來的待支付流程由后臺派單。這兩個分支互不影響。6.3 改造后的驗(yàn)證驗(yàn)證要覆蓋兩條路徑在 FastAdmin 后臺給某技師填上經(jīng)緯度用前端選該技師下單查 fa_order 表確認(rèn) technician_id 有值、status 為 2再走一遍不選技師的舊流程確認(rèn)后臺派單仍然正常。兩條路徑都通改造才算完成。拆這類源碼最值錢的地方其實(shí)不是某個接口怎么寫而是狀態(tài)和字段的約定怎么貫穿三端。我這次改的時候第一件事不是寫代碼而是先翻 fa_order 的 status 注釋和前端的公共字典把待派單、待接單的定義對齊才動手。從那以后我接手任何 FastAdmin 項(xiàng)目第一件事都是先導(dǎo)出數(shù)據(jù)庫字典再碰控制器這個習(xí)慣幫我少走了不少彎路。希望這份拆解和改造思路對你也有用。本文還有配套的精品資源點(diǎn)擊獲取