소스 검색

修复bug

Huanyi 3 주 전
부모
커밋
8ffa54944e

+ 14 - 4
.idea/workspace.xml

@@ -4,7 +4,7 @@
     <option name="autoReloadType" value="SELECTIVE" />
   </component>
   <component name="ChangeListManager">
-    <list default="true" id="e5f5f697-2bd4-4205-922a-fb106cdbbdf5" name="Changes" comment="改造登录;完善功能" />
+    <list default="true" id="e5f5f697-2bd4-4205-922a-fb106cdbbdf5" name="Changes" comment="优化" />
     <list id="6ae23f6a-53fe-4817-a1d7-106bcf184c18" name="New changelist" comment="不想提交的文件" />
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -63,7 +63,7 @@
   <component name="SharedIndexes">
     <attachedChunks>
       <set>
-        <option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-WS-253.31033.133" />
+        <option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-WS-253.32098.39" />
       </set>
     </attachedChunks>
   </component>
@@ -86,6 +86,7 @@
       <workItem from="1773309863004" duration="21000" />
       <workItem from="1773334157405" duration="23000" />
       <workItem from="1773772227895" duration="40000" />
+      <workItem from="1773942353014" duration="21000" />
     </task>
     <task id="LOCAL-00001" summary="1.完成app端履约者入驻相关功能开发&#10;2.完成app端履约者登录功能开发&#10;3.完成履约者个人中心功能开发">
       <option name="closed" value="true" />
@@ -159,7 +160,15 @@
       <option name="project" value="LOCAL" />
       <updated>1773772253104</updated>
     </task>
-    <option name="localTasksCounter" value="10" />
+    <task id="LOCAL-00010" summary="优化">
+      <option name="closed" value="true" />
+      <created>1773942371940</created>
+      <option name="number" value="00010" />
+      <option name="presentableId" value="LOCAL-00010" />
+      <option name="project" value="LOCAL" />
+      <updated>1773942371940</updated>
+    </task>
+    <option name="localTasksCounter" value="11" />
     <servers />
   </component>
   <component name="TypeScriptGeneratedFilesManager">
@@ -177,6 +186,7 @@
     <MESSAGE value="接单成功,异常上报成功,UI初步调整,订单详情优化" />
     <MESSAGE value="整改基本完成" />
     <MESSAGE value="改造登录;完善功能" />
-    <option name="LAST_COMMIT_MESSAGE" value="改造登录;完善功能" />
+    <MESSAGE value="优化" />
+    <option name="LAST_COMMIT_MESSAGE" value="优化" />
   </component>
 </project>

+ 2 - 0
App.vue

@@ -1,11 +1,13 @@
 <script>
 	import { isLoggedIn } from '@/utils/auth'
+	import { startGpsTimer } from '@/utils/gps'
 
 	export default {
 		onLaunch: function() {
 			console.log('App Launch')
 			// 已登录用户直接跳转首页,无需再进登录页
 			if (isLoggedIn()) {
+				startGpsTimer()
 				uni.switchTab({
 					url: '/pages/home/index'
 				})

+ 4 - 5
api/fulfiller.js

@@ -174,16 +174,15 @@ export function updateStatus(status) {
 }
 
 /**
- * 修改工作城市
- * @param {string} cityCode - 城市编码
- * @param {string} cityName - 城市名称
+ * 修改工作城市/站点
+ * @param {Object} data - 包含 cityCode cityName stationId 等
  * @author steelwei
  */
-export function updateCity(cityCode, cityName) {
+export function updateCity(data) {
   return request({
     url: '/fulfiller/fulfiller/my/city',
     method: 'PUT',
-    data: { cityCode, cityName }
+    data
   })
 }
 

+ 10 - 11
pages/login/logic.js

@@ -1,5 +1,6 @@
 import { loginByPassword, loginBySms, sendSmsCode } from '@/api/auth'
 import { setToken } from '@/utils/auth'
+import { startGpsTimer } from '@/utils/gps'
 
 export default {
     data() {
@@ -13,21 +14,17 @@ export default {
             countDown: 0,
             timer: null,
             showAgreementModal: false,
-            agreementTitle: '',
-            agreementContent: '',
+            currentAgreementId: '', // 当前显示的协议ID
             loginLoading: false
         }
     },
     methods: {
-        showAgreement(type) {
-            this.agreementTitle = type === 1 ? '用户服务协议' : '隐私政策';
-
-            if (type === 1) {
-                this.agreementContent = '1. 服务条款\n欢迎使用宠宝平台。您在使用本服务时需遵守以下条款...\n\n2. 用户责任\n用户需对自己的行为负责...\n\n3. 账号管理\n请妥善保管您的账号密码...';
-            } else {
-                this.agreementContent = '1. 信息收集\n为了提供服务,我们需要收集您的手机号、地理位置、设备信息等必要数据。\n\n2. 信息使用\n您的位置信息将用于订单匹配和路径规划;您的联系方式将用于接单通知和客户沟通。\n\n3. 信息保护\n我们将采取严格的安全措施保护您的个人信息,未经授权不会向第三方披露。';
-            }
-
+        /**
+         * 显示协议弹窗
+         * @param {Number} id 协议ID (1: 用户服务协议, 2: 隐私政策)
+         */
+        showAgreement(id) {
+            this.currentAgreementId = id;
             this.showAgreementModal = true;
         },
         /* async getVerifyCode() {
@@ -112,6 +109,8 @@ export default {
                     setToken(token);
                 }
 
+                startGpsTimer();
+
                 uni.showToast({ title: '登录成功', icon: 'success' });
                 setTimeout(() => {
                     uni.switchTab({

+ 6 - 7
pages/login/login.vue

@@ -135,14 +135,13 @@
         </view>
       </view>
       
-      <!-- 协议弹窗 组件 -->
-      <privacy-popup 
+      <!-- 协议弹窗 公共组件 -->
+      <agreement 
         :visible="showAgreementModal" 
-        :title="agreementTitle" 
-        :content="agreementContent"
+        :agreement-id="currentAgreementId"
         @close="showAgreementModal = false"
       >
-      </privacy-popup>
+      </agreement>
     </view>
   </view>
 </template>
@@ -150,12 +149,12 @@
 <script>
 // 使用脚本文件
 import logic from './logic.js';
-import PrivacyPopup from '@/components/privacy-popup/privacy-popup.vue';
+import Agreement from '@/src/components/agreement/index.vue';
 
 export default {
     ...logic,
     components: {
-        PrivacyPopup
+        Agreement
     }
 }
 </script>

+ 71 - 59
pages/mine/settings/profile/index.vue

@@ -42,17 +42,11 @@
         </view>
 
         <view class="group-card">
-            <view class="list-item" @click="showCityPicker">
-                <text class="item-title">工作城市</text>
-                <view class="item-right">
-                    <text class="item-value">{{ userInfo.city }}</text>
-                    <image class="arrow-icon" src="/static/icons/chevron_right.svg"></image>
-                </view>
-            </view>
-            <view class="list-item no-border">
+            <view class="list-item no-border" @click="showCityPicker">
                 <text class="item-title">所属站点</text>
                 <view class="item-right">
-                    <text class="item-value">{{ userInfo.stationName || '未分配站点' }}</text>
+                    <text class="item-value">{{ userInfo.stationFullName || '未分配站点' }}</text>
+                    <image class="arrow-icon" src="/static/icons/chevron_right.svg"></image>
                 </view>
             </view>
         </view>
@@ -67,12 +61,12 @@
             </view>
         </view>
 
-        <!-- 城市选择弹窗 (级联版,与我要加入页面一致) -->
+        <!-- 城市站点选择弹窗 (级联版树形结构) -->
         <view class="popup-mask" v-if="isCityPickerShow" @click="closeCityPicker">
             <view class="popup-content" @click.stop>
                 <view class="popup-header-row">
                     <text class="popup-btn-cancel" @click="closeCityPicker">取消</text>
-                    <text class="popup-title-text">请选择工作城市</text>
+                    <text class="popup-title-text">请选择工作城市和站点</text>
                     <text class="popup-btn-confirm" @click="confirmCity">确定</text>
                 </view>
                 <view class="picker-body">
@@ -118,7 +112,8 @@
 
 <script>
 // 引入 API @author steelwei
-import { getMyProfile, updateAvatar, updateName, updateStatus, updateCity, uploadFile, getAreaChildren } from '@/api/fulfiller'
+import { getMyProfile, updateAvatar, updateName, updateStatus, updateCity, uploadFile } from '@/api/fulfiller'
+import { getAreaStationList } from '@/api/system/areaStation'
 
 export default {
     data() {
@@ -129,16 +124,18 @@ export default {
                 workStatus: '',
                 city: '',
                 avatar: '/static/touxiang.png',
-                stationName: ''
+                stationName: '',
+                stationFullName: ''
             },
             isStatusPickerShow: false,
             isCityPickerShow: false,
             
-            // 城市级联选择器(与我要加入页面一致)
+            // 城市站点级联选择器
             selectStep: 0,
             selectedPathway: [],
             currentCityList: [],
-            selectedCityId: null
+            selectedCityId: null,
+            fullTree: [] // 树形结构数据
         }
     },
     onLoad() {
@@ -164,7 +161,8 @@ export default {
                         workStatus: this.formatStatus(data.status),
                         city: data.cityName || '',
                         avatar: data.avatarUrl || '/static/touxiang.png',
-                        stationName: data.stationName || '未分配站点'
+                        stationName: data.stationName || '',
+                        stationFullName: data.cityName && data.stationName ? `${data.cityName}/${data.stationName}` : (data.stationName || '未分配站点')
                     };
                 } else {
                     uni.showToast({ title: res.msg || '加载失败', icon: 'none' });
@@ -262,88 +260,102 @@ export default {
             }
         },
         
-        // 城市级联选择器(与我要加入页面一致) @author steelwei
+        // 城市和站点级联选择器 @author steelwei
         async showCityPicker() {
             this.isCityPickerShow = true;
+            if (this.fullTree.length === 0) {
+                await this.loadAreaStationTree();
+            }
             if (this.selectedPathway.length === 0) {
-                await this.resetCityPicker();
+                this.resetCityPicker();
             }
         },
-        async resetCityPicker() {
+        async loadAreaStationTree() {
+            try {
+                uni.showLoading({ title: '加载中...' });
+                const res = await getAreaStationList();
+                const list = res.data || [];
+                // 列表转树形结构
+                let map = {};
+                let roots = [];
+                list.forEach(node => {
+                    map[node.id] = { ...node, children: [] };
+                });
+                list.forEach(node => {
+                    if (node.parentId === 0 || !map[node.parentId]) {
+                        roots.push(map[node.id]);
+                    } else {
+                        map[node.parentId].children.push(map[node.id]);
+                    }
+                });
+                this.fullTree = roots;
+            } catch (err) {
+                console.error('加载站点数据失败:', err);
+                this.fullTree = [];
+            } finally {
+                uni.hideLoading();
+            }
+        },
+        resetCityPicker() {
             this.selectStep = 0;
             this.selectedPathway = [];
-            await this.loadAreaChildren(0);
+            this.currentCityList = this.fullTree;
         },
         closeCityPicker() {
             this.isCityPickerShow = false;
         },
-        async loadAreaChildren(parentId) {
-            try {
-                const res = await getAreaChildren(parentId);
-                // 城市选择器只显示 城市(0) 和 区域(1),不显示站点(2)
-                this.currentCityList = (res.data || [])
-                    .filter(item => item.type !== 2)
-                    .map(item => ({
-                        id: item.id,
-                        name: item.name,
-                        type: item.type,
-                        parentId: item.parentId
-                    }));
-            } catch (err) {
-                console.error('加载区域数据失败:', err);
-                this.currentCityList = [];
-            }
-        },
-        async selectCityItem(item) {
+        selectCityItem(item) {
             this.selectedPathway[this.selectStep] = item;
-            // type: 0=城市, 1=区域
-            if (item.type === 0) {
+            
+            // 如果有下级(如城市下的区域,区域下的站点)则继续选
+            if (item.children && item.children.length > 0) {
                 this.selectStep++;
                 this.selectedPathway = this.selectedPathway.slice(0, this.selectStep);
-                await this.loadAreaChildren(item.id);
-                if (this.currentCityList.length === 0) {
-                    this.selectedCityId = item.id;
-                    this.confirmCity();
-                }
+                this.currentCityList = item.children;
             } else {
-                // 区域级(1)选完即确认
+                // 没有下级,直接确认
                 this.selectedCityId = item.id;
                 this.confirmCity();
             }
         },
-        async jumpToStep(step) {
+        jumpToStep(step) {
             this.selectStep = step;
             if (step === 0) {
-                await this.loadAreaChildren(0);
+                this.currentCityList = this.fullTree;
             } else {
                 const parent = this.selectedPathway[step - 1];
-                if (parent) {
-                    await this.loadAreaChildren(parent.id);
-                }
+                this.currentCityList = parent ? parent.children : [];
             }
         },
-        // 确认城市选择 @author steelwei
+        // 确认城市与站点选择 @author steelwei
         async confirmCity() {
             if (this.selectedPathway.length === 0) {
-                uni.showToast({ title: '请选择城市', icon: 'none' });
+                uni.showToast({ title: '请选择站点', icon: 'none' });
                 return;
             }
-            const cityName = this.selectedPathway.map(i => i.name).join(' ');
-            const cityCode = String(this.selectedCityId);
+            
+            // 找出最后选中的节点 (站点)
+            let stationNode = this.selectedPathway[this.selectedPathway.length - 1];
+            // 将路径名称用 / 加起来
+            const fullName = this.selectedPathway.map(i => i.name).join('/');
+            
+            const reqData = {
+                stationId: stationNode.id
+            };
             
             try {
-                const res = await updateCity(cityCode, cityName);
+                const res = await updateCity(reqData);
                 if (res.code === 200) {
-                    this.userInfo.city = cityName;
+                    this.userInfo.stationFullName = fullName;
                     uni.showToast({ title: '修改成功', icon: 'success' });
                     this.closeCityPicker();
-                    // 重置选择器,下次打开重新加载
+                    // 重置以便下一次打开
                     this.selectedPathway = [];
                 } else {
                     uni.showToast({ title: res.msg || '修改失败', icon: 'none' });
                 }
             } catch (error) {
-                console.error('修改城市失败:', error);
+                console.error('修改失败:', error);
                 uni.showToast({ title: '网络错误', icon: 'none' });
             }
         }

+ 7 - 8
pages/recruit/form.vue

@@ -242,26 +242,25 @@
         </view>
     </view>
 
-    <!-- 协议弹窗 -->
-    <privacy-popup 
+    <!-- 协议弹窗 公共组件 -->
+    <agreement 
         :visible="showPrivacy" 
-        :title="privacyTitle" 
-        :content="privacyContent"
+        :agreement-id="currentAgreementId"
         @close="showPrivacy = false"
     >
-    </privacy-popup>
+    </agreement>
 
   </view>
 </template>
 
-<script>
+<script>// 使用脚本文件
 import logic from './logic.js';
-import PrivacyPopup from '@/components/privacy-popup/privacy-popup.vue';
+import Agreement from '@/src/components/agreement/index.vue';
 
 export default {
     ...logic,
     components: {
-        PrivacyPopup
+        Agreement
     }
 }
 </script>

+ 2 - 4
pages/recruit/logic.js

@@ -50,8 +50,7 @@ export default {
 
             // 协议弹窗
             showPrivacy: false,
-            privacyTitle: '',
-            privacyContent: ''
+            currentAgreementId: '' // 当前协议ID
         }
     },
     created() {
@@ -280,8 +279,7 @@ export default {
         },
 
         openPrivacy() {
-            this.privacyTitle = '宠宝履约者说明';
-            this.privacyContent = '1. 履约职责\n作为宠宝履约者,您需要按照平台标准完成宠物接送、喂遛或洗护服务,确保宠物安全与健康。\n\n2. 结算方式\n服务费用将根据订单类型和距离计算,定期结算至您的账户。具体结算周期请查看钱包说明。\n\n3. 行为规范\n请在这个过程中保持专业,穿着整洁,礼貌待人。严禁虐待宠物,违反者将承担法律责任。';
+            this.currentAgreementId = 3; // 履约者说明
             this.showPrivacy = true;
         },
 

+ 252 - 174
unpackage/dist/dev/app-plus/app-service.js

@@ -31,81 +31,6 @@ if (uni.restoreGlobal) {
 }
 (function(vue) {
   "use strict";
-  const _export_sfc = (sfc, props) => {
-    const target = sfc.__vccOpts || sfc;
-    for (const [key, val] of props) {
-      target[key] = val;
-    }
-    return target;
-  };
-  const _sfc_main$D = {
-    name: "privacy-popup",
-    props: {
-      visible: {
-        type: Boolean,
-        default: false
-      },
-      title: {
-        type: String,
-        default: "协议标题"
-      },
-      content: {
-        type: String,
-        // 备用,主要用 slot
-        default: ""
-      }
-    },
-    methods: {
-      close() {
-        this.$emit("close");
-      }
-    }
-  };
-  function _sfc_render$C(_ctx, _cache, $props, $setup, $data, $options) {
-    return $props.visible ? (vue.openBlock(), vue.createElementBlock("view", {
-      key: 0,
-      class: "popup-mask",
-      onClick: _cache[2] || (_cache[2] = vue.withModifiers(() => {
-      }, ["stop"]))
-    }, [
-      vue.createElementVNode("view", { class: "popup-content" }, [
-        vue.createElementVNode("view", { class: "popup-header" }, [
-          vue.createElementVNode(
-            "text",
-            { class: "popup-title" },
-            vue.toDisplayString($props.title),
-            1
-            /* TEXT */
-          ),
-          vue.createElementVNode("view", {
-            class: "close-icon",
-            onClick: _cache[0] || (_cache[0] = (...args) => $options.close && $options.close(...args))
-          }, "×")
-        ]),
-        vue.createElementVNode("scroll-view", {
-          "scroll-y": "",
-          class: "popup-body"
-        }, [
-          vue.renderSlot(_ctx.$slots, "default", {}, () => [
-            vue.createElementVNode(
-              "text",
-              { class: "default-text" },
-              vue.toDisplayString($props.content),
-              1
-              /* TEXT */
-            )
-          ], true)
-        ]),
-        vue.createElementVNode("view", { class: "popup-footer" }, [
-          vue.createElementVNode("button", {
-            class: "confirm-btn",
-            onClick: _cache[1] || (_cache[1] = (...args) => $options.close && $options.close(...args))
-          }, "我知道了")
-        ])
-      ])
-    ])) : vue.createCommentVNode("v-if", true);
-  }
-  const __easycom_0 = /* @__PURE__ */ _export_sfc(_sfc_main$D, [["render", _sfc_render$C], ["__scopeId", "data-v-af3fbef1"], ["__file", "E:/CodeProjects/Cursor/pet-system-fulfiller-app/components/privacy-popup/privacy-popup.vue"]]);
   function formatAppLog(type, filename, ...args) {
     if (uni.__log__) {
       uni.__log__(type, filename, ...args);
@@ -113,12 +38,49 @@ if (uni.restoreGlobal) {
       console[type].apply(console, [...args, filename]);
     }
   }
-  function resolveEasycom(component, easycom) {
-    return typeof component === "string" ? easycom : component;
-  }
   const BASE_URL = "http://192.168.1.118:8080";
   const CLIENT_ID = "fe63fea7be31b0200b496d08bc6b517d";
   const PLATFORM_CODE = "FlfAppPlatformCodeX9kR7mT3wQ5vZ8nB1jY6pD4sL0hC2gA";
+  function uploadGps(data) {
+    return request({
+      url: "/fulfiller/fulfiller/gps",
+      method: "POST",
+      data
+    });
+  }
+  let gpsTimer = null;
+  function reportGps() {
+    uni.getLocation({
+      type: "wgs84",
+      success: function(res) {
+        const data = {
+          longitude: res.longitude,
+          latitude: res.latitude
+        };
+        uploadGps(data).then(() => {
+          formatAppLog("log", "at utils/gps.js:20", "GPS定位上传成功", data);
+        }).catch((err) => {
+          formatAppLog("error", "at utils/gps.js:22", "GPS定位上传失败", err);
+        });
+      },
+      fail: function(err) {
+        formatAppLog("error", "at utils/gps.js:26", "获取GPS定位失败", err);
+      }
+    });
+  }
+  function startGpsTimer() {
+    stopGpsTimer();
+    reportGps();
+    gpsTimer = setInterval(() => {
+      reportGps();
+    }, 12e5);
+  }
+  function stopGpsTimer() {
+    if (gpsTimer) {
+      clearInterval(gpsTimer);
+      gpsTimer = null;
+    }
+  }
   const TOKEN_KEY = "fulfiller_token";
   const USER_INFO_KEY = "fulfiller_user_info";
   function getToken() {
@@ -139,6 +101,7 @@ if (uni.restoreGlobal) {
   function clearAuth() {
     removeToken();
     removeUserInfo();
+    stopGpsTimer();
   }
   function request(options = {}) {
     const {
@@ -235,19 +198,18 @@ if (uni.restoreGlobal) {
         countDown: 0,
         timer: null,
         showAgreementModal: false,
-        agreementTitle: "",
-        agreementContent: "",
+        currentAgreementId: "",
+        // 当前显示的协议ID
         loginLoading: false
       };
     },
     methods: {
-      showAgreement(type) {
-        this.agreementTitle = type === 1 ? "用户服务协议" : "隐私政策";
-        if (type === 1) {
-          this.agreementContent = "1. 服务条款\n欢迎使用宠宝平台。您在使用本服务时需遵守以下条款...\n\n2. 用户责任\n用户需对自己的行为负责...\n\n3. 账号管理\n请妥善保管您的账号密码...";
-        } else {
-          this.agreementContent = "1. 信息收集\n为了提供服务,我们需要收集您的手机号、地理位置、设备信息等必要数据。\n\n2. 信息使用\n您的位置信息将用于订单匹配和路径规划;您的联系方式将用于接单通知和客户沟通。\n\n3. 信息保护\n我们将采取严格的安全措施保护您的个人信息,未经授权不会向第三方披露。";
-        }
+      /**
+       * 显示协议弹窗
+       * @param {Number} id 协议ID (1: 用户服务协议, 2: 隐私政策)
+       */
+      showAgreement(id) {
+        this.currentAgreementId = id;
         this.showAgreementModal = true;
       },
       /* async getVerifyCode() {
@@ -277,7 +239,7 @@ if (uni.restoreGlobal) {
                               uni.showToast({ title: '验证码已发送', icon: 'none' });
                           }
                       } catch (err) {
-                          __f__('error','at pages/login/logic.js:60','发送验证码失败:', err);
+                          __f__('error','at pages/login/logic.js:57','发送验证码失败:', err);
                       }
                   }, */
       async handleLogin() {
@@ -308,6 +270,7 @@ if (uni.restoreGlobal) {
           if (token) {
             setToken(token);
           }
+          startGpsTimer();
           uni.showToast({ title: "登录成功", icon: "success" });
           setTimeout(() => {
             uni.switchTab({
@@ -315,7 +278,7 @@ if (uni.restoreGlobal) {
             });
           }, 1e3);
         } catch (err) {
-          formatAppLog("error", "at pages/login/logic.js:123", "登录失败:", err);
+          formatAppLog("error", "at pages/login/logic.js:122", "登录失败:", err);
         } finally {
           this.loginLoading = false;
           uni.hideLoading();
@@ -333,16 +296,124 @@ if (uni.restoreGlobal) {
       }
     }
   };
+  function getAgreement(id) {
+    return request({
+      url: "/system/agreement/" + id,
+      method: "get"
+    });
+  }
+  const _export_sfc = (sfc, props) => {
+    const target = sfc.__vccOpts || sfc;
+    for (const [key, val] of props) {
+      target[key] = val;
+    }
+    return target;
+  };
+  const _sfc_main$D = {
+    name: "Agreement",
+    props: {
+      visible: {
+        type: Boolean,
+        default: false
+      },
+      agreementId: {
+        type: [Number, String],
+        default: ""
+      }
+    },
+    data() {
+      return {
+        detail: {
+          title: "",
+          content: ""
+        }
+      };
+    },
+    watch: {
+      visible(newVal) {
+        if (newVal && this.agreementId) {
+          this.fetchAgreementDetail();
+        }
+      }
+    },
+    methods: {
+      /**
+       * 获取协议详情
+       */
+      async fetchAgreementDetail() {
+        try {
+          uni.showLoading({ title: "加载中..." });
+          const res = await getAgreement(this.agreementId);
+          if (res.code === 200) {
+            this.detail = res.data;
+          } else {
+            uni.showToast({ title: res.msg || "获取协议失败", icon: "none" });
+          }
+        } catch (error) {
+          formatAppLog("error", "at src/components/agreement/index.vue:67", "获取协议详情失败:", error);
+        } finally {
+          uni.hideLoading();
+        }
+      },
+      /**
+       * 关闭弹窗
+       */
+      handleClose() {
+        this.$emit("close");
+      }
+    }
+  };
+  function _sfc_render$C(_ctx, _cache, $props, $setup, $data, $options) {
+    return $props.visible ? (vue.openBlock(), vue.createElementBlock(
+      "view",
+      {
+        key: 0,
+        class: "agreement-mask",
+        onTouchmove: _cache[1] || (_cache[1] = vue.withModifiers(() => {
+        }, ["stop", "prevent"]))
+      },
+      [
+        vue.createElementVNode("view", { class: "agreement-container" }, [
+          vue.createElementVNode("view", { class: "agreement-header" }, [
+            vue.createElementVNode(
+              "text",
+              { class: "agreement-title" },
+              vue.toDisplayString($data.detail.title || "协议详情"),
+              1
+              /* TEXT */
+            )
+          ]),
+          vue.createElementVNode("scroll-view", {
+            "scroll-y": "",
+            class: "agreement-body"
+          }, [
+            vue.createElementVNode("rich-text", {
+              nodes: $data.detail.content
+            }, null, 8, ["nodes"])
+          ]),
+          vue.createElementVNode("view", { class: "agreement-footer" }, [
+            vue.createElementVNode("button", {
+              class: "confirm-btn",
+              onClick: _cache[0] || (_cache[0] = (...args) => $options.handleClose && $options.handleClose(...args))
+            }, "确 定")
+          ])
+        ])
+      ],
+      32
+      /* NEED_HYDRATION */
+    )) : vue.createCommentVNode("v-if", true);
+  }
+  const Agreement = /* @__PURE__ */ _export_sfc(_sfc_main$D, [["render", _sfc_render$C], ["__scopeId", "data-v-fe2c2596"], ["__file", "E:/CodeProjects/Cursor/pet-system-fulfiller-app/src/components/agreement/index.vue"]]);
   const _imports_0$3 = "/static/header.png";
   const _imports_1$8 = "/static/logo.png";
   const _sfc_main$C = {
     ...logic$9,
     components: {
-      PrivacyPopup: __easycom_0
+      Agreement
     }
   };
   function _sfc_render$B(_ctx, _cache, $props, $setup, $data, $options) {
-    const _component_privacy_popup = resolveEasycom(vue.resolveDynamicComponent("privacy-popup"), __easycom_0);
+    const _component_agreement = vue.resolveComponent("agreement");
     return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [
       vue.createElementVNode("view", { class: "banner-area" }, [
         vue.createElementVNode("image", {
@@ -528,12 +599,11 @@ if (uni.restoreGlobal) {
             vue.createElementVNode("text", null, " 宠宝履约者招募")
           ])
         ]),
-        vue.createVNode(_component_privacy_popup, {
+        vue.createVNode(_component_agreement, {
           visible: _ctx.showAgreementModal,
-          title: _ctx.agreementTitle,
-          content: _ctx.agreementContent,
+          "agreement-id": _ctx.currentAgreementId,
           onClose: _cache[8] || (_cache[8] = ($event) => _ctx.showAgreementModal = false)
-        }, null, 8, ["visible", "title", "content"])
+        }, null, 8, ["visible", "agreement-id"])
       ])
     ]);
   }
@@ -713,11 +783,11 @@ if (uni.restoreGlobal) {
       data: { status }
     });
   }
-  function updateCity(cityCode, cityName) {
+  function updateCity(data) {
     return request({
       url: "/fulfiller/fulfiller/my/city",
       method: "PUT",
-      data: { cityCode, cityName }
+      data
     });
   }
   function getAuthInfo() {
@@ -892,8 +962,8 @@ if (uni.restoreGlobal) {
         stationList: [],
         // 协议弹窗
         showPrivacy: false,
-        privacyTitle: "",
-        privacyContent: ""
+        currentAgreementId: ""
+        // 当前协议ID
       };
     },
     created() {
@@ -958,7 +1028,7 @@ if (uni.restoreGlobal) {
             name: item.name
           }));
         } catch (err) {
-          formatAppLog("error", "at pages/recruit/logic.js:134", "加载服务类型失败:", err);
+          formatAppLog("error", "at pages/recruit/logic.js:133", "加载服务类型失败:", err);
           this.serviceTypes = [];
         }
       },
@@ -993,7 +1063,7 @@ if (uni.restoreGlobal) {
                   uni.showToast({ title: '验证码已发送', icon: 'none' });
               }
           } catch (err) {
-              __f__('error','at pages/recruit/logic.js:171','发送验证码失败:', err);
+              __f__('error','at pages/recruit/logic.js:170','发送验证码失败:', err);
           }
       }, */
       // 城市选择器 logic(从后端加载)
@@ -1021,7 +1091,7 @@ if (uni.restoreGlobal) {
             parentId: item.parentId
           }));
         } catch (err) {
-          formatAppLog("error", "at pages/recruit/logic.js:203", "加载区域数据失败:", err);
+          formatAppLog("error", "at pages/recruit/logic.js:202", "加载区域数据失败:", err);
           this.currentList = [];
         }
       },
@@ -1071,7 +1141,7 @@ if (uni.restoreGlobal) {
             name: item.name
           }));
         } catch (err) {
-          formatAppLog("error", "at pages/recruit/logic.js:262", "加载站点数据失败:", err);
+          formatAppLog("error", "at pages/recruit/logic.js:261", "加载站点数据失败:", err);
           this.stationList = [];
         }
       },
@@ -1091,8 +1161,7 @@ if (uni.restoreGlobal) {
         this.closeStationPicker();
       },
       openPrivacy() {
-        this.privacyTitle = "宠宝履约者说明";
-        this.privacyContent = "1. 履约职责\n作为宠宝履约者,您需要按照平台标准完成宠物接送、喂遛或洗护服务,确保宠物安全与健康。\n\n2. 结算方式\n服务费用将根据订单类型和距离计算,定期结算至您的账户。具体结算周期请查看钱包说明。\n\n3. 行为规范\n请在这个过程中保持专业,穿着整洁,礼貌待人。严禁虐待宠物,违反者将承担法律责任。";
+        this.currentAgreementId = 3;
         this.showPrivacy = true;
       },
       goToAuth() {
@@ -1124,11 +1193,11 @@ if (uni.restoreGlobal) {
   const _sfc_main$A = {
     ...logic$7,
     components: {
-      PrivacyPopup: __easycom_0
+      Agreement
     }
   };
   function _sfc_render$z(_ctx, _cache, $props, $setup, $data, $options) {
-    const _component_privacy_popup = resolveEasycom(vue.resolveDynamicComponent("privacy-popup"), __easycom_0);
+    const _component_agreement = vue.resolveComponent("agreement");
     return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [
       vue.createElementVNode("view", { class: "card" }, [
         vue.createElementVNode("view", { class: "form-item" }, [
@@ -1661,12 +1730,11 @@ if (uni.restoreGlobal) {
         2
         /* CLASS */
       ),
-      vue.createVNode(_component_privacy_popup, {
+      vue.createVNode(_component_agreement, {
         visible: _ctx.showPrivacy,
-        title: _ctx.privacyTitle,
-        content: _ctx.privacyContent,
+        "agreement-id": _ctx.currentAgreementId,
         onClose: _cache[24] || (_cache[24] = ($event) => _ctx.showPrivacy = false)
-      }, null, 8, ["visible", "title", "content"])
+      }, null, 8, ["visible", "agreement-id"])
     ]);
   }
   const PagesRecruitForm = /* @__PURE__ */ _export_sfc(_sfc_main$A, [["render", _sfc_render$z], ["__file", "E:/CodeProjects/Cursor/pet-system-fulfiller-app/pages/recruit/form.vue"]]);
@@ -8923,6 +8991,12 @@ if (uni.restoreGlobal) {
     ]);
   }
   const PagesMineSettingsIndex = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["render", _sfc_render$j], ["__file", "E:/CodeProjects/Cursor/pet-system-fulfiller-app/pages/mine/settings/index.vue"]]);
+  function getAreaStationList() {
+    return request({
+      url: "/system/areaStation/list",
+      method: "GET"
+    });
+  }
   const _sfc_main$j = {
     data() {
       return {
@@ -8932,15 +9006,18 @@ if (uni.restoreGlobal) {
           workStatus: "",
           city: "",
           avatar: "/static/touxiang.png",
-          stationName: ""
+          stationName: "",
+          stationFullName: ""
         },
         isStatusPickerShow: false,
         isCityPickerShow: false,
-        // 城市级联选择器(与我要加入页面一致)
+        // 城市站点级联选择器
         selectStep: 0,
         selectedPathway: [],
         currentCityList: [],
-        selectedCityId: null
+        selectedCityId: null,
+        fullTree: []
+        // 树形结构数据
       };
     },
     onLoad() {
@@ -8966,13 +9043,14 @@ if (uni.restoreGlobal) {
               workStatus: this.formatStatus(data.status),
               city: data.cityName || "",
               avatar: data.avatarUrl || "/static/touxiang.png",
-              stationName: data.stationName || "未分配站点"
+              stationName: data.stationName || "",
+              stationFullName: data.cityName && data.stationName ? `${data.cityName}/${data.stationName}` : data.stationName || "未分配站点"
             };
           } else {
             uni.showToast({ title: res.msg || "加载失败", icon: "none" });
           }
         } catch (error) {
-          formatAppLog("error", "at pages/mine/settings/profile/index.vue:173", "加载用户信息失败:", error);
+          formatAppLog("error", "at pages/mine/settings/profile/index.vue:171", "加载用户信息失败:", error);
           uni.showToast({ title: "网络错误", icon: "none" });
         } finally {
           uni.hideLoading();
@@ -9010,7 +9088,7 @@ if (uni.restoreGlobal) {
                 }
               }
             } catch (error) {
-              formatAppLog("error", "at pages/mine/settings/profile/index.vue:218", "修改头像失败:", error);
+              formatAppLog("error", "at pages/mine/settings/profile/index.vue:216", "修改头像失败:", error);
               uni.showToast({ title: "上传失败", icon: "none" });
             } finally {
               uni.hideLoading();
@@ -9045,79 +9123,90 @@ if (uni.restoreGlobal) {
             uni.showToast({ title: res.msg || "修改失败", icon: "none" });
           }
         } catch (error) {
-          formatAppLog("error", "at pages/mine/settings/profile/index.vue:258", "修改状态失败:", error);
+          formatAppLog("error", "at pages/mine/settings/profile/index.vue:256", "修改状态失败:", error);
           uni.showToast({ title: "网络错误", icon: "none" });
         } finally {
           this.closeStatusPicker();
         }
       },
-      // 城市级联选择器(与我要加入页面一致) @author steelwei
+      // 城市和站点级联选择器 @author steelwei
       async showCityPicker() {
         this.isCityPickerShow = true;
+        if (this.fullTree.length === 0) {
+          await this.loadAreaStationTree();
+        }
         if (this.selectedPathway.length === 0) {
-          await this.resetCityPicker();
+          this.resetCityPicker();
         }
       },
-      async resetCityPicker() {
+      async loadAreaStationTree() {
+        try {
+          uni.showLoading({ title: "加载中..." });
+          const res = await getAreaStationList();
+          const list = res.data || [];
+          let map = {};
+          let roots = [];
+          list.forEach((node) => {
+            map[node.id] = { ...node, children: [] };
+          });
+          list.forEach((node) => {
+            if (node.parentId === 0 || !map[node.parentId]) {
+              roots.push(map[node.id]);
+            } else {
+              map[node.parentId].children.push(map[node.id]);
+            }
+          });
+          this.fullTree = roots;
+        } catch (err) {
+          formatAppLog("error", "at pages/mine/settings/profile/index.vue:293", "加载站点数据失败:", err);
+          this.fullTree = [];
+        } finally {
+          uni.hideLoading();
+        }
+      },
+      resetCityPicker() {
         this.selectStep = 0;
         this.selectedPathway = [];
-        await this.loadAreaChildren(0);
+        this.currentCityList = this.fullTree;
       },
       closeCityPicker() {
         this.isCityPickerShow = false;
       },
-      async loadAreaChildren(parentId) {
-        try {
-          const res = await getAreaChildren(parentId);
-          this.currentCityList = (res.data || []).filter((item) => item.type !== 2).map((item) => ({
-            id: item.id,
-            name: item.name,
-            type: item.type,
-            parentId: item.parentId
-          }));
-        } catch (err) {
-          formatAppLog("error", "at pages/mine/settings/profile/index.vue:293", "加载区域数据失败:", err);
-          this.currentCityList = [];
-        }
-      },
-      async selectCityItem(item) {
+      selectCityItem(item) {
         this.selectedPathway[this.selectStep] = item;
-        if (item.type === 0) {
+        if (item.children && item.children.length > 0) {
           this.selectStep++;
           this.selectedPathway = this.selectedPathway.slice(0, this.selectStep);
-          await this.loadAreaChildren(item.id);
-          if (this.currentCityList.length === 0) {
-            this.selectedCityId = item.id;
-            this.confirmCity();
-          }
+          this.currentCityList = item.children;
         } else {
           this.selectedCityId = item.id;
           this.confirmCity();
         }
       },
-      async jumpToStep(step) {
+      jumpToStep(step) {
         this.selectStep = step;
         if (step === 0) {
-          await this.loadAreaChildren(0);
+          this.currentCityList = this.fullTree;
         } else {
           const parent = this.selectedPathway[step - 1];
-          if (parent) {
-            await this.loadAreaChildren(parent.id);
-          }
+          this.currentCityList = parent ? parent.children : [];
         }
       },
-      // 确认城市选择 @author steelwei
+      // 确认城市与站点选择 @author steelwei
       async confirmCity() {
         if (this.selectedPathway.length === 0) {
-          uni.showToast({ title: "请选择城市", icon: "none" });
+          uni.showToast({ title: "请选择站点", icon: "none" });
           return;
         }
-        const cityName = this.selectedPathway.map((i) => i.name).join(" ");
-        const cityCode = String(this.selectedCityId);
+        let stationNode = this.selectedPathway[this.selectedPathway.length - 1];
+        const fullName = this.selectedPathway.map((i) => i.name).join("/");
+        const reqData = {
+          stationId: stationNode.id
+        };
         try {
-          const res = await updateCity(cityCode, cityName);
+          const res = await updateCity(reqData);
           if (res.code === 200) {
-            this.userInfo.city = cityName;
+            this.userInfo.stationFullName = fullName;
             uni.showToast({ title: "修改成功", icon: "success" });
             this.closeCityPicker();
             this.selectedPathway = [];
@@ -9125,7 +9214,7 @@ if (uni.restoreGlobal) {
             uni.showToast({ title: res.msg || "修改失败", icon: "none" });
           }
         } catch (error) {
-          formatAppLog("error", "at pages/mine/settings/profile/index.vue:346", "修改城市失败:", error);
+          formatAppLog("error", "at pages/mine/settings/profile/index.vue:358", "修改失败:", error);
           uni.showToast({ title: "网络错误", icon: "none" });
         }
       }
@@ -9209,15 +9298,15 @@ if (uni.restoreGlobal) {
       ]),
       vue.createElementVNode("view", { class: "group-card" }, [
         vue.createElementVNode("view", {
-          class: "list-item",
+          class: "list-item no-border",
           onClick: _cache[4] || (_cache[4] = (...args) => $options.showCityPicker && $options.showCityPicker(...args))
         }, [
-          vue.createElementVNode("text", { class: "item-title" }, "工作城市"),
+          vue.createElementVNode("text", { class: "item-title" }, "所属站点"),
           vue.createElementVNode("view", { class: "item-right" }, [
             vue.createElementVNode(
               "text",
               { class: "item-value" },
-              vue.toDisplayString($data.userInfo.city),
+              vue.toDisplayString($data.userInfo.stationFullName || "未分配站点"),
               1
               /* TEXT */
             ),
@@ -9226,18 +9315,6 @@ if (uni.restoreGlobal) {
               src: _imports_3
             })
           ])
-        ]),
-        vue.createElementVNode("view", { class: "list-item no-border" }, [
-          vue.createElementVNode("text", { class: "item-title" }, "所属站点"),
-          vue.createElementVNode("view", { class: "item-right" }, [
-            vue.createElementVNode(
-              "text",
-              { class: "item-value" },
-              vue.toDisplayString($data.userInfo.stationName || "未分配站点"),
-              1
-              /* TEXT */
-            )
-          ])
         ])
       ]),
       $data.isStatusPickerShow ? (vue.openBlock(), vue.createElementBlock("view", {
@@ -9280,7 +9357,7 @@ if (uni.restoreGlobal) {
               class: "popup-btn-cancel",
               onClick: _cache[10] || (_cache[10] = (...args) => $options.closeCityPicker && $options.closeCityPicker(...args))
             }, "取消"),
-            vue.createElementVNode("text", { class: "popup-title-text" }, "请选择工作城市"),
+            vue.createElementVNode("text", { class: "popup-title-text" }, "请选择工作城市和站点"),
             vue.createElementVNode("text", {
               class: "popup-btn-confirm",
               onClick: _cache[11] || (_cache[11] = (...args) => $options.confirmCity && $options.confirmCity(...args))
@@ -12520,18 +12597,19 @@ if (uni.restoreGlobal) {
   __definePage("pages/mine/points/detail", PagesMinePointsDetail);
   const _sfc_main = {
     onLaunch: function() {
-      formatAppLog("log", "at App.vue:6", "App Launch");
+      formatAppLog("log", "at App.vue:7", "App Launch");
       if (isLoggedIn()) {
+        startGpsTimer();
         uni.switchTab({
           url: "/pages/home/index"
         });
       }
     },
     onShow: function() {
-      formatAppLog("log", "at App.vue:15", "App Show");
+      formatAppLog("log", "at App.vue:17", "App Show");
     },
     onHide: function() {
-      formatAppLog("log", "at App.vue:18", "App Hide");
+      formatAppLog("log", "at App.vue:20", "App Hide");
     }
   };
   const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "E:/CodeProjects/Cursor/pet-system-fulfiller-app/App.vue"]]);

+ 62 - 68
unpackage/dist/dev/app-plus/pages/login/login.css

@@ -1,72 +1,66 @@
 
-.popup-mask[data-v-af3fbef1] {
-        position: fixed;
-        top: 0;
-        left: 0;
-        right: 0;
-        bottom: 0;
-        background-color: rgba(0, 0, 0, 0.4); /* 半透明遮罩 */
-        z-index: 999;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-}
-.popup-content[data-v-af3fbef1] {
-        width: 80%; /* 宽度大概屏幕 80% */
-        max-height: 70%;
-        background-color: #fff;
-        border-radius: 0.5rem;
-        display: flex;
-        flex-direction: column;
-        overflow: hidden;
-}
-.popup-header[data-v-af3fbef1] {
-        height: 3.125rem;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        position: relative;
-        border-bottom: 0.0625rem solid #eee;
-}
-.popup-title[data-v-af3fbef1] {
-        font-size: 1rem;
-        font-weight: bold;
-        color: #333;
-}
-.close-icon[data-v-af3fbef1] {
-        position: absolute;
-        right: 0.9375rem;
-        top: 50%;
-        transform: translateY(-50%);
-        font-size: 1.25rem;
-        color: #999;
-        line-height: 1;
-        padding: 0.3125rem; /* 增加点击区域 */
-}
-.popup-body[data-v-af3fbef1] {
-        padding: 0.9375rem;
-        font-size: 0.875rem;
-        color: #666;
-        line-height: 1.6;
-        max-height: 18.75rem; /* 限制高度滚动 */
-        box-sizing: border-box;
-}
-.popup-footer[data-v-af3fbef1] {
-        padding: 0.9375rem;
-        border-top: 0.0625rem solid #eee;
-}
-.confirm-btn[data-v-af3fbef1] {
-        background: linear-gradient(90deg, #FF6F00 0%, #FF5722 100%);
-        color: #fff;
-        font-size: 0.9375rem;
-        font-weight: bold;
-        height: 2.5rem;
-        line-height: 2.5rem;
-        border-radius: 0.25rem;
-        box-shadow: 0 0.125rem 0.3125rem rgba(255, 87, 34, 0.2);
-}
-.confirm-btn[data-v-af3fbef1]::after {
-        border: none;
+.agreement-mask[data-v-fe2c2596] {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.6);
+  z-index: 1000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.agreement-container[data-v-fe2c2596] {
+  width: 18.75rem;
+  max-height: 80vh;
+  background-color: #ffffff;
+  border-radius: 0.625rem;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  animation: fadeIn-fe2c2596 0.3s ease;
+}
+.agreement-header[data-v-fe2c2596] {
+  padding: 0.9375rem;
+  text-align: center;
+  border-bottom: 0.0625rem solid #f5f5f5;
+}
+.agreement-title[data-v-fe2c2596] {
+  font-size: 1rem;
+  font-weight: bold;
+  color: #333333;
+}
+.agreement-body[data-v-fe2c2596] {
+  flex: 1;
+  padding: 0.9375rem;
+  font-size: 0.875rem;
+  line-height: 1.6;
+  color: #666666;
+  max-height: 50vh;
+}
+.agreement-footer[data-v-fe2c2596] {
+  padding: 0.9375rem;
+  border-top: 0.0625rem solid #f5f5f5;
+}
+.confirm-btn[data-v-fe2c2596] {
+  width: 100%;
+  height: 2.5rem;
+  line-height: 2.5rem;
+  background-color: #ff5722;
+  color: #ffffff;
+  border-radius: 1.25rem;
+  font-size: 0.875rem;
+}
+@keyframes fadeIn-fe2c2596 {
+from {
+    opacity: 0;
+    transform: scale(0.9);
+}
+to {
+    opacity: 1;
+    transform: scale(1);
+}
 }
 
 

+ 62 - 68
unpackage/dist/dev/app-plus/pages/recruit/form.css

@@ -1,72 +1,66 @@
 
-.popup-mask[data-v-af3fbef1] {
-        position: fixed;
-        top: 0;
-        left: 0;
-        right: 0;
-        bottom: 0;
-        background-color: rgba(0, 0, 0, 0.4); /* 半透明遮罩 */
-        z-index: 999;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-}
-.popup-content[data-v-af3fbef1] {
-        width: 80%; /* 宽度大概屏幕 80% */
-        max-height: 70%;
-        background-color: #fff;
-        border-radius: 0.5rem;
-        display: flex;
-        flex-direction: column;
-        overflow: hidden;
-}
-.popup-header[data-v-af3fbef1] {
-        height: 3.125rem;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        position: relative;
-        border-bottom: 0.0625rem solid #eee;
-}
-.popup-title[data-v-af3fbef1] {
-        font-size: 1rem;
-        font-weight: bold;
-        color: #333;
-}
-.close-icon[data-v-af3fbef1] {
-        position: absolute;
-        right: 0.9375rem;
-        top: 50%;
-        transform: translateY(-50%);
-        font-size: 1.25rem;
-        color: #999;
-        line-height: 1;
-        padding: 0.3125rem; /* 增加点击区域 */
-}
-.popup-body[data-v-af3fbef1] {
-        padding: 0.9375rem;
-        font-size: 0.875rem;
-        color: #666;
-        line-height: 1.6;
-        max-height: 18.75rem; /* 限制高度滚动 */
-        box-sizing: border-box;
-}
-.popup-footer[data-v-af3fbef1] {
-        padding: 0.9375rem;
-        border-top: 0.0625rem solid #eee;
-}
-.confirm-btn[data-v-af3fbef1] {
-        background: linear-gradient(90deg, #FF6F00 0%, #FF5722 100%);
-        color: #fff;
-        font-size: 0.9375rem;
-        font-weight: bold;
-        height: 2.5rem;
-        line-height: 2.5rem;
-        border-radius: 0.25rem;
-        box-shadow: 0 0.125rem 0.3125rem rgba(255, 87, 34, 0.2);
-}
-.confirm-btn[data-v-af3fbef1]::after {
-        border: none;
+.agreement-mask[data-v-fe2c2596] {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.6);
+  z-index: 1000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.agreement-container[data-v-fe2c2596] {
+  width: 18.75rem;
+  max-height: 80vh;
+  background-color: #ffffff;
+  border-radius: 0.625rem;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  animation: fadeIn-fe2c2596 0.3s ease;
+}
+.agreement-header[data-v-fe2c2596] {
+  padding: 0.9375rem;
+  text-align: center;
+  border-bottom: 0.0625rem solid #f5f5f5;
+}
+.agreement-title[data-v-fe2c2596] {
+  font-size: 1rem;
+  font-weight: bold;
+  color: #333333;
+}
+.agreement-body[data-v-fe2c2596] {
+  flex: 1;
+  padding: 0.9375rem;
+  font-size: 0.875rem;
+  line-height: 1.6;
+  color: #666666;
+  max-height: 50vh;
+}
+.agreement-footer[data-v-fe2c2596] {
+  padding: 0.9375rem;
+  border-top: 0.0625rem solid #f5f5f5;
+}
+.confirm-btn[data-v-fe2c2596] {
+  width: 100%;
+  height: 2.5rem;
+  line-height: 2.5rem;
+  background-color: #ff5722;
+  color: #ffffff;
+  border-radius: 1.25rem;
+  font-size: 0.875rem;
+}
+@keyframes fadeIn-fe2c2596 {
+from {
+    opacity: 0;
+    transform: scale(0.9);
+}
+to {
+    opacity: 1;
+    transform: scale(1);
+}
 }
 
 /* 页面背景 */

+ 2 - 0
utils/auth.js

@@ -2,6 +2,7 @@
  * Token 存储管理
  * @author steelwei
  */
+import { stopGpsTimer } from './gps'
 
 const TOKEN_KEY = 'fulfiller_token'
 const USER_INFO_KEY = 'fulfiller_user_info'
@@ -69,4 +70,5 @@ export function removeUserInfo() {
 export function clearAuth() {
   removeToken()
   removeUserInfo()
+  stopGpsTimer()
 }

+ 1 - 1
utils/config.js

@@ -4,7 +4,7 @@
  */
 
 // API 基础地址(开发环境)
-export const BASE_URL = 'http://192.168.1.118:8080'
+ export const BASE_URL = 'http://192.168.1.118:8080'
 // export const BASE_URL = 'http://8.136.194.143/api'
 // export const BASE_URL = 'http://192.168.0.103:8080'