Huanyi 3 недель назад
Родитель
Сommit
4588176987
51 измененных файлов с 1901 добавлено и 565 удалено
  1. 31 65
      .idea/workspace.xml
  2. 5 0
      App.vue
  3. 13 0
      api/archieves/changeLog.js
  4. 27 0
      api/archieves/pet.js
  5. 24 0
      api/fulfiller/anamaly.js
  6. 78 0
      api/fulfiller/app.js
  7. 11 0
      api/fulfiller/audit.js
  8. 135 0
      api/fulfiller/fulfiller.js
  9. 11 0
      api/fulfiller/levelConfig.js
  10. 11 0
      api/fulfiller/levelRights.js
  11. 133 0
      api/fulfiller/log.js
  12. 123 0
      api/order/subOrder.js
  13. 22 0
      api/order/subOrderLog.js
  14. 14 0
      api/resource/sms.js
  15. 15 0
      api/service/list.js
  16. 9 0
      api/system/agreement.js
  17. 8 0
      api/system/areaStation.js
  18. 16 0
      api/system/dict.js
  19. 123 0
      components/agreement/index.vue
  20. 128 0
      components/privacy-popup/index.vue
  21. 5 0
      enums/agreement.json
  22. 6 3
      manifest.json
  23. 1 1
      pages/home/index.vue
  24. 15 0
      pages/home/logic.js
  25. 6 0
      pages/login/logic.js
  26. 133 0
      pages/mine/settings/about/agreement-detail.vue
  27. 38 13
      pages/orders/detail-logic.js
  28. 91 18
      pages/orders/logic.js
  29. 2 4
      pages/recruit/auth_logic.js
  30. 14 42
      pages/recruit/form.vue
  31. 92 93
      pages/recruit/logic.js
  32. 41 4
      pages/recruit/qualifications_logic.js
  33. 21 0
      pages/recruit/style.css
  34. 10 17
      pages/recruit/success_logic.js
  35. BIN
      unpackage/cache/apk/__UNI__76F5C47_cm.apk
  36. 1 1
      unpackage/cache/apk/apkurl
  37. 0 0
      unpackage/cache/apk/cmManifestCache.json
  38. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/app-service.js
  39. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/manifest.json
  40. 1 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/mine/settings/about/agreement-detail.css
  41. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/recruit/form.css
  42. 0 0
      unpackage/dist/build/app-plus/app-service.js
  43. 6 3
      unpackage/dist/build/app-plus/manifest.json
  44. 1 0
      unpackage/dist/build/app-plus/pages/mine/settings/about/agreement-detail.css
  45. 0 0
      unpackage/dist/build/app-plus/pages/recruit/form.css
  46. 305 301
      unpackage/dist/dev/app-plus/app-service.js
  47. 1 0
      unpackage/dist/dev/app-plus/manifest.json
  48. 60 0
      unpackage/dist/dev/app-plus/pages/mine/settings/about/agreement-detail.css
  49. 18 0
      unpackage/dist/dev/app-plus/pages/recruit/form.css
  50. BIN
      unpackage/release/apk/__UNI__76F5C47__20260324142623.apk
  51. 96 0
      utils/gps.js

+ 31 - 65
.idea/workspace.xml

@@ -4,51 +4,7 @@
     <option name="autoReloadType" value="SELECTIVE" />
   </component>
   <component name="ChangeListManager">
-    <list default="true" id="e5f5f697-2bd4-4205-922a-fb106cdbbdf5" name="Changes" comment="修复bug">
-      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/api/fulfiller.js" beforeDir="false" afterPath="$PROJECT_DIR$/api/fulfiller.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/components/custom-tabbar/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/components/custom-tabbar/index.vue" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/manifest.json" beforeDir="false" afterPath="$PROJECT_DIR$/manifest.json" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/apk/__UNI__76F5C47_cm.apk" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/apk/__UNI__76F5C47_cm.apk" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/apk/apkurl" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/apk/apkurl" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/apk/cmManifestCache.json" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/apk/cmManifestCache.json" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/app-config-service.js" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/app-config-service.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/app-service.js" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/app-service.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/manifest.json" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/manifest.json" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/home/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/home/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/home/work-status.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/home/work-status.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/login/login.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/login/login.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/level/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/level/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/order-stats.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/order-stats.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/points/detail.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/points/detail.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/rewards-all.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/rewards-all.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/wallet/bill.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/mine/wallet/bill.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/orders/anomaly.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/orders/anomaly.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/orders/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/orders/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/recruit/auth.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/recruit/auth.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/recruit/form.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/cache/wgt/__UNI__76F5C47/pages/recruit/form.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/app-config-service.js" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/app-config-service.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/app-service.js" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/app-service.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/manifest.json" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/manifest.json" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/home/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/home/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/home/work-status.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/home/work-status.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/login/login.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/login/login.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/level/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/level/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/order-stats.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/order-stats.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/points/detail.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/points/detail.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/rewards-all.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/rewards-all.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/wallet/bill.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/mine/wallet/bill.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/orders/anomaly.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/orders/anomaly.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/orders/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/orders/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/recruit/auth.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/recruit/auth.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/recruit/form.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/build/app-plus/pages/recruit/form.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/release/apk/__UNI__76F5C47__20260316202839.apk" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/release/apk/__UNI__76F5C47__20260317093308.apk" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/utils/config.js" beforeDir="false" afterPath="$PROJECT_DIR$/utils/config.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utils/request.js" beforeDir="false" afterPath="$PROJECT_DIR$/utils/request.js" afterDir="false" />
-    </list>
+    <list default="true" id="e5f5f697-2bd4-4205-922a-fb106cdbbdf5" name="Changes" comment="修复BUG" />
     <list id="6ae23f6a-53fe-4817-a1d7-106bcf184c18" name="New changelist" comment="不想提交的文件" />
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -86,25 +42,25 @@
     <option name="showLibraryContents" value="true" />
     <option name="showMembers" value="true" />
   </component>
-  <component name="PropertiesComponent"><![CDATA[{
-  "keyToString": {
-    "RunOnceActivity.ShowReadmeOnStart": "true",
-    "RunOnceActivity.git.unshallow": "true",
-    "RunOnceActivity.typescript.service.memoryLimit.init": "true",
-    "git-widget-placeholder": "dev/shenliang",
-    "javascript.preferred.runtime.type.id": "node",
-    "kotlin-language-version-configured": "true",
-    "last_opened_file_path": "D:/windsurfProject/petSystem/pet-system-fulfiller-app",
-    "node.js.detected.package.eslint": "true",
-    "node.js.detected.package.tslint": "true",
-    "node.js.selected.package.eslint": "(autodetect)",
-    "node.js.selected.package.tslint": "(autodetect)",
-    "nodejs_package_manager_path": "npm",
-    "settings.editor.selected.configurable": "MavenSettings",
-    "ts.external.directory.path": "D:\\Code\\WebStorm 2025.2\\plugins\\javascript-plugin\\jsLanguageServicesImpl\\external",
-    "vue.rearranger.settings.migration": "true"
+  <component name="PropertiesComponent">{
+  &quot;keyToString&quot;: {
+    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
+    &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
+    &quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
+    &quot;git-widget-placeholder&quot;: &quot;dev/shenliang&quot;,
+    &quot;javascript.preferred.runtime.type.id&quot;: &quot;node&quot;,
+    &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
+    &quot;last_opened_file_path&quot;: &quot;D:/windsurfProject/petSystem/pet-system-fulfiller-app&quot;,
+    &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
+    &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
+    &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
+    &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
+    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
+    &quot;settings.editor.selected.configurable&quot;: &quot;MavenSettings&quot;,
+    &quot;ts.external.directory.path&quot;: &quot;D:\\Code\\WebStorm 2025.2\\plugins\\javascript-plugin\\jsLanguageServicesImpl\\external&quot;,
+    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
   }
-}]]></component>
+}</component>
   <component name="SharedIndexes">
     <attachedChunks>
       <set>
@@ -134,6 +90,7 @@
       <workItem from="1773942353014" duration="21000" />
       <workItem from="1774012995939" duration="18000" />
       <workItem from="1774229878524" duration="4279000" />
+      <workItem from="1774277401659" duration="35000" />
     </task>
     <task id="LOCAL-00001" summary="1.完成app端履约者入驻相关功能开发&#10;2.完成app端履约者登录功能开发&#10;3.完成履约者个人中心功能开发">
       <option name="closed" value="true" />
@@ -223,7 +180,15 @@
       <option name="project" value="LOCAL" />
       <updated>1774013009505</updated>
     </task>
-    <option name="localTasksCounter" value="12" />
+    <task id="LOCAL-00012" summary="修复BUG">
+      <option name="closed" value="true" />
+      <created>1774277410829</created>
+      <option name="number" value="00012" />
+      <option name="presentableId" value="LOCAL-00012" />
+      <option name="project" value="LOCAL" />
+      <updated>1774277410829</updated>
+    </task>
+    <option name="localTasksCounter" value="13" />
     <servers />
   </component>
   <component name="TypeScriptGeneratedFilesManager">
@@ -243,6 +208,7 @@
     <MESSAGE value="改造登录;完善功能" />
     <MESSAGE value="优化" />
     <MESSAGE value="修复bug" />
-    <option name="LAST_COMMIT_MESSAGE" value="修复bug" />
+    <MESSAGE value="修复BUG" />
+    <option name="LAST_COMMIT_MESSAGE" value="修复BUG" />
   </component>
 </project>

+ 5 - 0
App.vue

@@ -5,6 +5,11 @@
 	export default {
 		onLaunch: function() {
 			console.log('App Launch')
+			// 退出或重新打开应用时,清除招募认证流程的暂存数据
+			uni.removeStorageSync('recruit_form_data');
+			uni.removeStorageSync('recruit_auth_data');
+			uni.removeStorageSync('recruit_qual_data');
+
 			// 已登录用户直接跳转首页,无需再进登录页
 			if (isLoggedIn()) {
 				startGpsTimer()

+ 13 - 0
api/archieves/changeLog.js

@@ -0,0 +1,13 @@
+import request from '@/utils/request'
+
+/**
+ * 获取变更日志列表(备注日志)
+ * @param {Object} params - { targetId, targetType }
+ */
+export function listChangeLog(params) {
+  return request({
+    url: '/archieves/changeLog/listAll',
+    method: 'GET',
+    data: params
+  })
+}

+ 27 - 0
api/archieves/pet.js

@@ -0,0 +1,27 @@
+/**
+ * 宠物档案 API
+ * @author steelwei
+ */
+import request from '@/utils/request'
+
+/**
+ * 获取宠物档案详情
+ * @param {number} id 宠物ID
+ */
+export function getPetDetail(id) {
+    return request({
+        url: `/archieves/pet/${id}`,
+        method: 'GET'
+    })
+}
+/**
+ * 提交宠物备注
+ * @param {Object} data - { petId, content }
+ */
+export function submitPetRemark(data) {
+    return request({
+        url: '/archieves/pet/remark',
+        method: 'POST',
+        data
+    })
+}

+ 24 - 0
api/fulfiller/anamaly.js

@@ -0,0 +1,24 @@
+import request from '@/utils/request'
+
+/**
+ * 异常上报
+ * @param {Object} data - { orderId, photos, type, content }
+ */
+export function uploadAnamaly(data) {
+  return request({
+    url: '/fulfiller/anamaly/upload',
+    method: 'POST',
+    data
+  })
+}
+
+/**
+ * 获取订单异常记录列表
+ * @param {number} orderId - 订单ID
+ */
+export function getAnomalyList(orderId) {
+  return request({
+    url: `/fulfiller/anamaly/listOnOrder?orderId=${orderId}`,
+    method: 'GET'
+  })
+}

+ 78 - 0
api/fulfiller/app.js

@@ -0,0 +1,78 @@
+import request from '@/utils/request'
+import { BASE_URL, CLIENT_ID, PLATFORM_CODE } from '@/utils/config'
+
+/**
+ * 提交入驻申请(招募表单)
+ * @param {Object} data - 申请数据
+ */
+export function submitAudit(data) {
+  return request({
+    url: '/fulfiller/app/audit/submit',
+    method: 'POST',
+    needToken: false,
+    data
+  })
+}
+
+/**
+ * 获取服务项目列表(动态获取服务类型)
+ */
+export function getServiceTypes() {
+  return request({
+    url: '/fulfiller/app/service/list',
+    method: 'GET',
+    needToken: false
+  })
+}
+
+/**
+ * 查询子级区域/站点列表(级联选择器用)
+ * @param {number} parentId - 父级ID,0或不传查顶级
+ */
+export function getAreaChildren(parentId = 0) {
+  return request({
+    url: '/fulfiller/app/area/children',
+    method: 'GET',
+    needToken: false,
+    data: { parentId }
+  })
+}
+
+/**
+ * 上传文件(图片等)
+ * @param {string} filePath - 本地文件路径
+ * @returns {Promise} - { url, fileName, ossId }
+ */
+export function uploadFile(filePath) {
+  return new Promise((resolve, reject) => {
+    const token = uni.getStorageSync('fulfiller_token')
+    uni.uploadFile({
+      url: BASE_URL + '/fulfiller/app/upload',
+      filePath: filePath,
+      name: 'file',
+	  timeout: 600000,
+      header: {
+        'clientid': CLIENT_ID,
+        'X-Platform-Code': PLATFORM_CODE,
+        'Authorization': token ? `Bearer ${token}` : '',
+      },
+      success: (res) => {
+        try {
+          const data = JSON.parse(res.data)
+          if (data.code === 200) {
+            resolve(data)
+          } else {
+            uni.showToast({ title: data.msg || '上传失败', icon: 'none' })
+            reject(data)
+          }
+        } catch (e) {
+          reject(e)
+        }
+      },
+      fail: (err) => {
+        uni.showToast({ title: '上传失败', icon: 'none' })
+        reject(err)
+      }
+    })
+  })
+}

+ 11 - 0
api/fulfiller/audit.js

@@ -0,0 +1,11 @@
+import request from '@/utils/request'
+
+/**
+ * 查询我的审核状态
+ */
+export function getMyAuditStatus() {
+  return request({
+    url: '/fulfiller/audit/my',
+    method: 'GET'
+  })
+}

+ 135 - 0
api/fulfiller/fulfiller.js

@@ -0,0 +1,135 @@
+/**
+ * 履约者档案相关 API
+ */
+import request from '@/utils/request'
+
+/**
+ * 接收GPS定位
+ * @param {Object} data - { longitude, latitude }
+ */
+export function uploadGps(data) {
+  return request({
+    url: '/fulfiller/fulfiller/gps',
+    method: 'POST',
+    data: data
+  })
+}
+
+/**
+ * 获取当前履约者个人档案 / 获取当前登录履约者信息
+ */
+export function getMyProfile() {
+  return request({
+    url: '/fulfiller/fulfiller/my',
+    method: 'GET'
+  })
+}
+
+// 别名,保持与原有调用兼容
+export const getUserInfo = getMyProfile;
+
+/**
+ * 修改头像
+ * @param {string} avatar - 头像ossId
+ */
+export function updateAvatar(avatar) {
+  return request({
+    url: '/fulfiller/fulfiller/my/avatar',
+    method: 'PUT',
+    data: { avatar }
+  })
+}
+
+/**
+ * 修改真实姓名
+ * @param {string} name - 真实姓名
+ */
+export function updateName(name) {
+  return request({
+    url: '/fulfiller/fulfiller/my/name',
+    method: 'PUT',
+    data: { name }
+  })
+}
+
+/**
+ * 修改工作状态
+ * @param {string} status - 工作状态 (resting:休息, busy:接单中)
+ */
+export function updateStatus(status) {
+  return request({
+    url: '/fulfiller/fulfiller/my/status',
+    method: 'PUT',
+    data: { status }
+  })
+}
+
+/**
+ * 修改工作城市/站点
+ * @param {Object} data - 包含 cityCode cityName stationId 等
+ */
+export function updateCity(data) {
+  return request({
+    url: '/fulfiller/fulfiller/my/city',
+    method: 'PUT',
+    data
+  })
+}
+
+/**
+ * 获取认证信息
+ */
+export function getAuthInfo() {
+  return request({
+    url: '/fulfiller/fulfiller/my/auth',
+    method: 'GET'
+  })
+}
+
+/**
+ * 修改手机号
+ * @param {string} phone - 新手机号
+ * @param {string} code - 验证码
+ */
+export function updatePhone(phone, code) {
+  return request({
+    url: '/fulfiller/fulfiller/my/phone',
+    method: 'PUT',
+    data: { phone, code }
+  })
+}
+
+/**
+ * 修改密码
+ * @param {string} oldPassword - 旧密码
+ * @param {string} newPassword - 新密码
+ */
+export function updatePassword(oldPassword, newPassword) {
+  return request({
+    url: '/fulfiller/fulfiller/my/password',
+    method: 'PUT',
+    data: { oldPassword, newPassword }
+  })
+}
+
+/**
+ * 注销账号
+ */
+export function deleteAccount() {
+  return request({
+    url: '/fulfiller/fulfiller/my/account',
+    method: 'DELETE'
+  })
+}
+
+/**
+ * 更新认证信息
+ * @param {Object} data - 认证数据
+ */
+export function updateAuthInfo(data) {
+  return request({
+    url: '/fulfiller/fulfiller/my/auth',
+    method: 'POST',
+    data
+  })
+}

+ 11 - 0
api/fulfiller/levelConfig.js

@@ -0,0 +1,11 @@
+import request from '@/utils/request'
+
+/**
+ * 获取所有等级配置列表
+ */
+export function listAllLevelConfigs() {
+  return request({
+    url: '/fulfiller/levelConfig/listAll',
+    method: 'GET'
+  })
+}

+ 11 - 0
api/fulfiller/levelRights.js

@@ -0,0 +1,11 @@
+import request from '@/utils/request'
+
+/**
+ * 获取所有等级权益列表
+ */
+export function listAllLevelRights() {
+  return request({
+    url: '/fulfiller/levelRights/listAll',
+    method: 'GET'
+  })
+}

+ 133 - 0
api/fulfiller/log.js

@@ -0,0 +1,133 @@
+import request from '@/utils/request';
+
+/**
+ * 获取APP端余额及变动记录
+ * @returns {Promise}
+ */
+export function getBalanceOnApp() {
+    return request({
+        url: '/fulfiller/log/balanceOnApp',
+        method: 'GET'
+    });
+}
+
+/**
+ * 分页获取APP端变动记录列表
+ * @param {Object} data 
+ * @returns {Promise}
+ */
+export function pageBalanceOnApp(data) {
+    return request({
+        url: '/fulfiller/log/pageBalanceOnApp',
+        method: 'GET',
+        data
+    });
+}
+
+/**
+ * 根据年月获取APP端变动记录列表
+ * @param {Object} data 
+ * @returns {Promise}
+ */
+export function listBalanceOnApp(data) {
+    return request({
+        url: '/fulfiller/log/listBalanceOnApp',
+        method: 'GET',
+        data
+    });
+}
+
+/**
+ * 获取APP端当前积分
+ * @returns {Promise}
+ */
+export function pointsOnApp() {
+    return request({
+        url: '/fulfiller/log/pointsOnApp',
+        method: 'GET'
+    });
+}
+
+/**
+ * 分页获取APP端积分变动记录列表
+ * @param {Object} data 
+ * @returns {Promise}
+ */
+export function pagePointsOnApp(data) {
+    return request({
+        url: '/fulfiller/log/pagePointsOnApp',
+        method: 'GET',
+        data
+    });
+}
+
+/**
+ * 根据年月获取APP端积分变动记录列表
+ * @param {Object} data 
+ * @returns {Promise}
+ */
+export function listPointsOnApp(data) {
+    return request({
+        url: '/fulfiller/log/listPointsOnApp',
+        method: 'GET',
+        data
+    });
+}
+/**
+ * 获取APP端奖惩统计值
+ * @param {Object} data 
+ * @returns {Promise}
+ */
+export function countOnAppReward(data) {
+    return request({
+        url: '/fulfiller/log/countOnAppReward',
+        method: 'GET',
+        data
+    });
+}
+
+/**
+ * 获取APP端奖惩列表数据
+ * @param {Object} data 
+ * @returns {Promise}
+ */
+export function listOnAppReward(data) {
+    return request({
+        url: '/fulfiller/log/listOnAppReward',
+        method: 'GET',
+        data
+    });
+}
+
+/**
+ * 获取我的积分日志
+ */
+export function getMyPointsLog(params) {
+  return request({
+    url: '/fulfiller/log/points',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 获取我的余额日志
+ */
+export function getMyBalanceLog(params) {
+  return request({
+    url: '/fulfiller/log/balance',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 获取我的奖惩记录
+ */
+export function getMyRewardLog(params) {
+  return request({
+    url: '/fulfiller/log/reward',
+    method: 'GET',
+    data: params
+  })
+}

+ 123 - 0
api/order/subOrder.js

@@ -0,0 +1,123 @@
+import request from '@/utils/request';
+
+/**
+ * 取消订单
+ * @param {Object} data - 请求参数
+ * @param {number} data.orderId - 订单ID
+ * @author antigravity
+ */
+export function cancelOrderApi(data) {
+    return request({
+        url: '/order/subOrder/cancel',
+        method: 'PUT',
+        data
+    });
+}
+
+/**
+ * 拒绝接单
+ * @param {Object} data - 请求参数
+ * @param {number} data.orderId - 订单ID
+ * @param {string} data.rejectReason - 拒绝理由
+ * @author antigravity
+ */
+export function rejectOrderApi(data) {
+    return request({
+        url: '/order/subOrder/reject',
+        method: 'PUT',
+        data
+    });
+}
+
+/**
+ * 获取待接单列表
+ * @param {Object} params - { service, minPrice, maxPrice, pageNum, pageSize }
+ */
+export function getPendingOrders(params) {
+  return request({
+    url: '/order/subOrder/listPendingAccept',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 接单
+ * @param {number} orderId - 订单ID
+ */
+export function acceptOrder(orderId) {
+  return request({
+    url: '/order/subOrder/accept',
+    method: 'PUT',
+    data: { orderId }
+  })
+}
+
+/**
+ * 获取订单数量统计数据
+ */
+export function getOrderCount() {
+  return request({
+    url: '/order/subOrder/count',
+    method: 'GET'
+  })
+}
+
+/**
+ * 分页获取统计页面的订单列表
+ * @param {Object} params - { status, pageNum, pageSize }
+ */
+export function getStatisticOrders(params) {
+  return request({
+    url: '/order/subOrder/listOnStatistic',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 获取我的订单列表
+ * @param {Object} params - { status, content, service, startServiceTime, endServiceTime }
+ */
+export function getMyOrders(params) {
+  return request({
+    url: '/order/subOrder/listOnMyOrder',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 获取订单详情
+ * @param {number} id - 订单ID
+ */
+export function getOrderInfo(id) {
+  return request({
+    url: `/order/subOrder/getInfo?id=${id}`,
+    method: 'GET'
+  })
+}
+
+/**
+ * 订单打卡
+ * @param {Object} data - { orderId, photos, content, step, title, startFlag, endFlag }
+ */
+export function clockIn(data) {
+  return request({
+    url: '/order/subOrder/clockIn',
+    method: 'PUT',
+    data
+  })
+}
+
+/**
+ * 提交宠护小结
+ * @param {Object} data - { orderId, content }
+ */
+export function submitNursingSummary(data) {
+  return request({
+    url: '/order/subOrder/nursingSummary',
+    method: 'PUT',
+    data
+  })
+}

+ 22 - 0
api/order/subOrderLog.js

@@ -0,0 +1,22 @@
+import request from '@/utils/request'
+
+/**
+ * 获取订单统计数据(含奖励、惩罚、拒单等合计)
+ */
+export function getOrderStats() {
+  return request({
+    url: '/order/subOrderLog/count',
+    method: 'GET'
+  })
+}
+
+/**
+ * 获取订单日志列表
+ * @param {string} orderId - 订单ID
+ */
+export function getOrderLogs(orderId) {
+  return request({
+    url: `/order/subOrderLog/list?orderId=${orderId}`,
+    method: 'GET'
+  })
+}

+ 14 - 0
api/resource/sms.js

@@ -0,0 +1,14 @@
+import request from '@/utils/request'
+
+/**
+ * 发送短信验证码
+ * @param {string} phonenumber - 手机号
+ */
+export function sendSmsCode(phonenumber) {
+  return request({
+    url: '/resource/sms/code',
+    method: 'GET',
+    needToken: false,
+    data: { phonenumber }
+  })
+}

+ 15 - 0
api/service/list.js

@@ -0,0 +1,15 @@
+/**
+ * 服务列表相关的 API
+ */
+import request from '@/utils/request'
+
+/**
+ * 获取所有服务类型列表
+ * @returns {Promise} 响应数据
+ */
+export function listAllService() {
+    return request({
+        url: '/service/list/listAll',
+        method: 'GET'
+    })
+}

+ 9 - 0
api/system/agreement.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 查询协议详细信息
+export function getAgreement(id) {
+  return request({
+    url: '/system/agreement/' + id,
+    method: 'get'
+  })
+}

+ 8 - 0
api/system/areaStation.js

@@ -0,0 +1,8 @@
+import request from '@/utils/request'
+
+export function getAreaStationList() {
+    return request({
+        url: '/system/areaStation/list',
+        method: 'GET'
+    })
+}

+ 16 - 0
api/system/dict.js

@@ -0,0 +1,16 @@
+/**
+ * 系统字典 API
+ * @author steelwei
+ */
+import request from '@/utils/request'
+
+/**
+ * 根据字典类型获取字典数据
+ * @param {string} dictType 字典类型
+ */
+export function getDictDataByType(dictType) {
+    return request({
+        url: `/system/dict/data/type/${dictType}`,
+        method: 'GET'
+    })
+}

+ 123 - 0
components/agreement/index.vue

@@ -0,0 +1,123 @@
+<template>
+  <view v-if="visible" class="agreement-mask" @touchmove.stop.prevent>
+    <view class="agreement-container">
+      <view class="agreement-header">
+        <text class="agreement-title">{{ title || '协议详情' }}</text>
+      </view>
+      <scroll-view scroll-y class="agreement-body">
+        <rich-text :nodes="content"></rich-text>
+      </scroll-view>
+      <view class="agreement-footer">
+        <button class="confirm-btn" @click="handleClose">确 定</button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+/**
+ * 协议详情组件 (纯 UI 组件)
+ * @property {Boolean} visible 控制显示隐藏
+ * @property {String} title 协议标题
+ * @property {String} content 协议内容 (支持 HTML)
+ * @event {Function} close 关闭回调
+ */
+export default {
+  name: 'Agreement',
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    title: {
+      type: String,
+      default: ''
+    },
+    content: {
+      type: String,
+      default: ''
+    }
+  },
+  methods: {
+    /**
+     * 关闭弹窗
+     */
+    handleClose() {
+      this.$emit('close');
+    }
+  }
+};
+</script>
+
+<style scoped>
+.agreement-mask {
+  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 {
+  width: 600rpx;
+  max-height: 80vh;
+  background-color: #ffffff;
+  border-radius: 20rpx;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  animation: fadeIn 0.3s ease;
+}
+
+.agreement-header {
+  padding: 30rpx;
+  text-align: center;
+  border-bottom: 2rpx solid #f5f5f5;
+}
+
+.agreement-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333333;
+}
+
+.agreement-body {
+  flex: 1;
+  padding: 30rpx;
+  font-size: 28rpx;
+  line-height: 1.6;
+  color: #666666;
+  max-height: 50vh;
+}
+
+.agreement-footer {
+  padding: 30rpx;
+  border-top: 2rpx solid #f5f5f5;
+}
+
+.confirm-btn {
+  width: 100%;
+  height: 80rpx;
+  line-height: 80rpx;
+  background-color: #ff5722;
+  color: #ffffff;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: scale(0.9);
+  }
+  to {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+</style>

+ 128 - 0
components/privacy-popup/index.vue

@@ -0,0 +1,128 @@
+<template>
+    <view class="popup-mask" v-if="visible" @click.stop>
+        <view class="popup-content">
+            <!-- 顶部: 标题 + 关闭按钮 -->
+            <view class="popup-header">
+                <text class="popup-title">{{ title }}</text>
+                <view class="close-icon" @click="close">×</view>
+            </view>
+            
+            <!-- 内容滚动区 -->
+            <scroll-view scroll-y class="popup-body">
+                <slot>
+                    <text class="default-text">{{ content }}</text>
+                </slot>
+            </scroll-view>
+            
+            <!-- 底部按钮 -->
+            <view class="popup-footer">
+                <button class="confirm-btn" @click="close">我知道了</button>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'privacy-popup',
+        props: {
+            visible: {
+                type: Boolean,
+                default: false
+            },
+            title: {
+                type: String,
+                default: '协议标题'
+            },
+            content: {
+                type: String, // 备用,主要用 slot
+                default: ''
+            }
+        },
+        methods: {
+            close() {
+                this.$emit('close');
+            }
+        }
+    }
+</script>
+
+<style scoped>
+    .popup-mask {
+        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 {
+        width: 80%; /* 宽度大概屏幕 80% */
+        max-height: 70%;
+        background-color: #fff;
+        border-radius: 16rpx;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden;
+    }
+
+    .popup-header {
+        height: 100rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        position: relative;
+        border-bottom: 2rpx solid #eee;
+    }
+
+    .popup-title {
+        font-size: 32rpx;
+        font-weight: bold;
+        color: #333;
+    }
+
+    .close-icon {
+        position: absolute;
+        right: 30rpx;
+        top: 50%;
+        transform: translateY(-50%);
+        font-size: 40rpx;
+        color: #999;
+        line-height: 1;
+        padding: 10rpx; /* 增加点击区域 */
+    }
+
+    .popup-body {
+        padding: 30rpx;
+        font-size: 28rpx;
+        color: #666;
+        line-height: 1.6;
+        max-height: 600rpx; /* 限制高度滚动 */
+        box-sizing: border-box;
+    }
+
+    .popup-footer {
+        padding: 30rpx;
+        border-top: 2rpx solid #eee;
+    }
+
+    .confirm-btn {
+        background: linear-gradient(90deg, #FF6F00 0%, #FF5722 100%);
+        color: #fff;
+        font-size: 30rpx;
+        font-weight: bold;
+        height: 80rpx;
+        line-height: 80rpx;
+        border-radius: 8rpx;
+        box-shadow: 0 4rpx 10rpx rgba(255, 87, 34, 0.2);
+    }
+    
+    .confirm-btn::after {
+        border: none;
+    }
+</style>

+ 5 - 0
enums/agreement.json

@@ -0,0 +1,5 @@
+[
+  { "label": "用户协议", "value": 1 },
+  { "label": "隐私政策", "value": 2 },
+  { "label": "履约者说明", "value": 3 }
+]

+ 6 - 3
manifest.json

@@ -2,8 +2,8 @@
     "name" : "履约者",
     "appid" : "__UNI__76F5C47",
     "description" : "履约者APP",
-    "versionName" : "0.1.0",
-    "versionCode" : 1,
+    "versionName" : "0.1.2",
+    "versionCode" : 2,
     "transformPx" : false,
     /* 5+App特有相关 */
     "app-plus" : {
@@ -40,7 +40,10 @@
                     "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
                     "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
                     "<uses-feature android:name=\"android.hardware.camera\"/>",
-                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>"
                 ]
             },
             /* ios打包配置 */

+ 1 - 1
pages/home/index.vue

@@ -28,7 +28,7 @@
                             <text class="arrow-down">▼</text>
                         </view>
                     </view>
-                    <view class="bottom-row">
+                    <view class="bottom-row" @click="handleManualLocation">
                         <text class="city-label">接单城市:{{ profile?.cityName || '暂无' }}</text>
                         <text class="city-arrow">></text>
                     </view>

+ 15 - 0
pages/home/logic.js

@@ -3,6 +3,7 @@ import { getPendingOrders, acceptOrder, getOrderCount, rejectOrderApi } from '@/
 import { listAllService } from '@/api/service/list'
 import { getAreaStationList } from '@/api/system/areaStation'
 import { isLoggedIn } from '@/utils/auth'
+import { reportGps } from '@/utils/gps'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 
 export default {
@@ -58,6 +59,9 @@ export default {
         this.checkWorkStatus();
         await this.loadServiceList();
         this.loadTaskList();
+        
+        // 显式请求一次定位授权
+        reportGps(true).catch(e => console.log('Init GPS check skipped', e));
     },
     onShow() {
         uni.hideTabBar()
@@ -176,6 +180,17 @@ export default {
                 url: '/pages/home/work-status'
             });
         },
+        async handleManualLocation() {
+            try {
+                uni.showLoading({ title: '定位获取中...', mask: true });
+                await reportGps(true);
+                uni.showToast({ title: '位置已更新', icon: 'success' });
+            } catch (e) {
+                console.error('Manual location failed', e);
+            } finally {
+                uni.hideLoading();
+            }
+        },
         startWork() {
             this.showConfirmModal = true;
         },

+ 6 - 0
pages/login/logic.js

@@ -21,6 +21,12 @@ export default {
             loginLoading: false
         }
     },
+    onLoad() {
+        // 进入登录页时,清除招募认证暂存数据
+        uni.removeStorageSync('recruit_form_data');
+        uni.removeStorageSync('recruit_auth_data');
+        uni.removeStorageSync('recruit_qual_data');
+    },
     methods: {
         /**
          * 显示协议弹窗

+ 133 - 0
pages/mine/settings/about/agreement-detail.vue

@@ -0,0 +1,133 @@
+<template>
+    <view class="container">
+        <!-- 自定义头部 -->
+        <view class="custom-header">
+            <view class="header-left" @click="navBack">
+                <image class="back-icon" src="/static/icons/chevron_right_dark.svg" style="transform: rotate(180deg);"></image>
+            </view>
+            <text class="header-title">{{ title || '协议详情' }}</text>
+            <view class="header-right"></view>
+        </view>
+        <view class="header-placeholder"></view>
+
+        <!-- 内容区域 -->
+        <scroll-view scroll-y class="content-scroll">
+            <view class="content-card">
+                <rich-text :nodes="content" class="rich-text"></rich-text>
+            </view>
+            <view class="safe-area-inset-bottom"></view>
+        </scroll-view>
+    </view>
+</template>
+
+<script>
+import { getAgreement } from '@/api/system/agreement'
+
+export default {
+    data() {
+        return {
+            id: null,
+            title: '',
+            content: '',
+            loading: false
+        }
+    },
+    onLoad(options) {
+        if (options.id) {
+            this.id = options.id;
+            this.loadDetail();
+        }
+    },
+    methods: {
+        async loadDetail() {
+            if (!this.id) return;
+            uni.showLoading({ title: '加载中...' });
+            try {
+                const res = await getAgreement(this.id);
+                if (res.code === 200 && res.data) {
+                    this.title = res.data.title;
+                    this.content = res.data.content;
+                    // 设置原生导航栏标题(作为兜底)
+                    uni.setNavigationBarTitle({ title: this.title });
+                } else {
+                    uni.showToast({ title: res.msg || '获取失败', icon: 'none' });
+                }
+            } catch (err) {
+                console.error('获取协议详情失败:', err);
+                uni.showToast({ title: '网络错误', icon: 'none' });
+            } finally {
+                uni.hideLoading();
+            }
+        },
+        navBack() {
+            uni.navigateBack({ delta: 1 });
+        }
+    }
+}
+</script>
+
+<style>
+page {
+    background-color: #fff;
+}
+.container {
+    display: flex;
+    flex-direction: column;
+    height: 100vh;
+}
+
+/* 自定义头部统一风格 */
+.custom-header {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 88rpx;
+    padding-top: var(--status-bar-height);
+    background-color: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-left: 30rpx;
+    padding-right: 30rpx;
+    box-sizing: content-box;
+    z-index: 100;
+    border-bottom: 1rpx solid #f5f5f5;
+}
+.header-placeholder {
+    height: calc(88rpx + var(--status-bar-height));
+    flex-shrink: 0;
+}
+.back-icon {
+    width: 40rpx;
+    height: 40rpx;
+}
+.header-title {
+    font-size: 32rpx;
+    font-weight: bold;
+    color: #333;
+}
+.header-right {
+    width: 40rpx;
+}
+
+.content-scroll {
+    flex: 1;
+    overflow: hidden;
+}
+
+.content-card {
+    padding: 30rpx 40rpx;
+    line-height: 1.6;
+}
+
+.rich-text {
+    font-size: 28rpx;
+    color: #333;
+}
+
+.safe-area-inset-bottom {
+    height: constant(safe-area-inset-bottom);
+    height: env(safe-area-inset-bottom);
+}
+</style>

+ 38 - 13
pages/orders/detail-logic.js

@@ -3,6 +3,7 @@ import { getOrderLogs } from '@/api/order/subOrderLog'
 import { uploadFile } from '@/api/fulfiller/app'
 import { getAnomalyList } from '@/api/fulfiller/anamaly'
 import { listAllService } from '@/api/service/list'
+import { reportGps } from '@/utils/gps'
 
 
 
@@ -134,6 +135,9 @@ export default {
             this.orderId = options.id
         }
         this.pageLoading = true
+        
+        // 显式请求一次定位授权
+        reportGps(true).catch(e => console.log('Init GPS check skipped', e));
         try {
             // 先加载字典
             await this.loadAnomalyTypeDict()
@@ -506,9 +510,34 @@ export default {
                 url: '/pages/orders/anomaly?orderId=' + (this.orderId || '')
             });
         },
+        /**
+         * 拨打电话 (带授权引导)
+         */
         callPhone() {
             const phoneNum = this.orderDetail.customerPhone || '18900008451'
-            uni.makePhoneCall({ phoneNumber: phoneNum });
+            if (!phoneNum) {
+                uni.showToast({ title: '手机号不存在', icon: 'none' });
+                return;
+            }
+            // 引导用户主动点击授权确认
+            uni.showModal({
+                title: '拨号提示',
+                content: `系统将为您拨打手机号: ${phoneNum},请授予拨号权限以正常通话。`,
+                confirmText: '呼叫',
+                cancelText: '取消',
+                success: (res) => {
+                    if (res.confirm) {
+                        uni.makePhoneCall({
+                            phoneNumber: phoneNum,
+                            fail: (err) => {
+                                console.error('拨号失败:', err);
+                                // 如果是由于权限拒绝,提示用户
+                                uni.showToast({ title: '无法唤起拨号盘,请检查权限设置', icon: 'none' });
+                            }
+                        });
+                    }
+                }
+            });
         },
         openNavigation(type) {
             this.navTargetPointType = type;
@@ -550,18 +579,14 @@ export default {
             } else {
                 // 如果没有经纬度,按照需求:使用自己当前的经纬度,然后搜索 fromAddress 或者 toAddress
                 uni.showLoading({ title: '获取当前位置...', mask: true });
-                uni.getLocation({
-                    type: 'gcj02',
-                    success: (res) => {
-                        uni.hideLoading();
-                        // 使用用户当前经纬度作为锚点打开地图,展示目标地址信息
-                        navigateTo(res.latitude, res.longitude, name, address);
-                    },
-                    fail: (err) => {
-                        uni.hideLoading();
-                        console.error('获取地理位置失败:', err);
-                        uni.showToast({ title: '无法获取当前位置信息', icon: 'none' });
-                    }
+                reportGps(true).then(res => {
+                    uni.hideLoading();
+                    // 使用用户当前经纬度作为锚点打开地图,展示目标地址信息
+                    navigateTo(res.latitude, res.longitude, name, address);
+                }).catch(err => {
+                    uni.hideLoading();
+                    console.error('获取地理位置失败:', err);
+                    // 具体的授权引导已在 reportGps 内部处理
                 });
             }
         },

+ 91 - 18
pages/orders/logic.js

@@ -1,5 +1,6 @@
 import { getMyOrders, cancelOrderApi } from '@/api/order/subOrder'
 import { listAllService } from '@/api/service/list'
+import { reportGps } from '@/utils/gps'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 
 export default {
@@ -39,6 +40,8 @@ export default {
     async onLoad() {
         await this.loadServiceList()
         await this.loadOrders()
+        // 显式请求一次定位授权
+        reportGps(true).catch(e => console.log('Init GPS check skipped', e));
     },
     onShow() {
         uni.hideTabBar()
@@ -233,18 +236,14 @@ export default {
             } else {
                 // 如果没有经纬度,按照需求:使用自己当前的经纬度,然后搜索 fromAddress 或者 toAddress
                 uni.showLoading({ title: '获取当前位置...', mask: true });
-                uni.getLocation({
-                    type: 'gcj02',
-                    success: (res) => {
-                        uni.hideLoading();
-                        // 使用用户当前经纬度作为锚点打开地图,展示目标地址信息
-                        navigateTo(res.latitude, res.longitude, name, address);
-                    },
-                    fail: (err) => {
-                        uni.hideLoading();
-                        console.error('获取地理位置失败:', err);
-                        uni.showToast({ title: '无法获取当前位置信息', icon: 'none' });
-                    }
+                reportGps(true).then(res => {
+                    uni.hideLoading();
+                    // 使用用户当前经纬度作为锚点打开地图,展示目标地址信息
+                    navigateTo(res.latitude, res.longitude, name, address);
+                }).catch(err => {
+                    uni.hideLoading();
+                    console.error('获取地理位置失败:', err);
+                    // 具体的授权引导已在 reportGps 内部处理
                 });
             }
         },
@@ -259,17 +258,91 @@ export default {
             this.activeCallItem = null;
         },
         doCall(type, item) {
-            let phoneNum = ''
-            const targetItem = item || this.activeCallItem
+            let phoneNum = '';
+            const targetItem = item || this.activeCallItem;
+            
+            // 1. 获取电话号码
             if (type === 'merchant') {
-                phoneNum = '18900008451'
+                phoneNum = '18900008451';
             } else if (type === 'customer') {
-                phoneNum = targetItem?.customerPhone || '13800000001'
+                phoneNum = targetItem?.customerPhone;
             }
-            if (phoneNum) {
-                uni.makePhoneCall({ phoneNumber: phoneNum })
+        
+            // 2. 基础校验
+            if (!phoneNum) {
+                uni.showToast({ title: '未找到电话号码', icon: 'none' });
+                this.activeCallItem = null;
+                return;
+            }
+        
+            // 3. 清洗号码 (去除空格、横杠等非数字字符)
+            phoneNum = phoneNum.replace(/[^\d]/g, '');
+            
+            // 二次校验:确保清洗后仍有数字
+            if (phoneNum.length < 3) {
+                uni.showToast({ title: '电话号码格式错误', icon: 'none' });
+                this.activeCallItem = null;
+                return;
             }
+        
+            console.log('正在发起直接呼叫:', phoneNum);
+        
+            // 4. 核心逻辑:区分环境处理
+            // #ifdef APP-PLUS
+            // App 端:使用 uni.makePhoneCall 直接发起呼叫
+            uni.makePhoneCall({
+                phoneNumber: phoneNum,
+                success: () => {
+                    console.log('成功唤起系统拨号盘');
+                },
+                fail: (err) => {
+                    console.error('拨号失败:', err);
+                    // 常见错误:Permission denied (权限被拒) 或 Activity not found
+                    let msg = '拨号失败';
+                    if (err.message && err.message.includes('permission')) {
+                        msg = '请在手机设置中允许"电话"权限';
+                    }
+                    uni.showToast({ title: msg, icon: 'none', duration: 3000 });
+                    
+                    // 如果失败,尝试引导用户去设置页 (仅限 Android)
+                    // #ifdef APP-ANDROID
+                    if (err.message && err.message.includes('permission')) {
+                        uni.showModal({
+                            title: '权限提示',
+                            content: '拨打电话需要电话权限,是否前往设置开启?',
+                            success: (res) => {
+                                if (res.confirm) {
+                                    plus.runtime.openURL("app-settings:"); 
+                                }
+                            }
+                        });
+                    }
+                    // #endif
+                },
+                complete: () => {
+                    this.activeCallItem = null; // 关闭弹窗
+                }
+            });
+            // #endif
+        
+            // #ifdef H5
+            // H5 端:使用 tel: 协议
+            window.location.href = `tel:${phoneNum}`;
             this.activeCallItem = null;
+            // #endif
+        
+            // #ifdef MP-WEIXIN
+            // 小程序端:直接调用 makePhoneCall (微信小程序支持直接弹框确认拨打)
+            uni.makePhoneCall({
+                phoneNumber: phoneNum,
+                fail: () => {
+                    uni.showToast({ title: '拨号失败', icon: 'none' });
+                },
+                complete: () => {
+                    this.activeCallItem = null;
+                }
+            });
+            // #endif
         },
         reportAbnormal(item) {
             uni.navigateTo({ url: '/pages/orders/anomaly?orderId=' + (item.id || '') });

+ 2 - 4
pages/recruit/auth_logic.js

@@ -34,16 +34,14 @@ export default {
             }
         }
         this.initDateData();
-        // 移除原有的 restoreAuthData 调用,因为用户要求进入页面即清空
+        // 进入页面即恢复历史填写数据(回显)
+        this.restoreAuthData();
     },
     onShow() {
         // 如果是从选择图片页面返回,不重置数据
         if (this.isChoosingImage) {
             this.isChoosingImage = false;
-            return;
         }
-        // 正常进入页面时清空所有信息
-        this.resetFormData();
     },
     methods: {
         // --- 日期选择器逻辑 ---

+ 14 - 42
pages/recruit/form.vue

@@ -102,23 +102,15 @@
         </view>
       </view>
 
-      <!-- 工作城市 -->
+      <!-- 所属站点 -->
       <view class="form-item">
-        <text class="label">工作城市</text>
-        <view class="input-box" @click="openCityPicker">
-            <text :style="{color: formData.city ? '#333' : '#ccc'}">{{ formData.city || '请选择工作城市' }}</text>
-            <!-- 灰色箭头 SVG -->
-            <svg class="arrow-right" style="width:24rpx; height:24rpx; margin-left: auto;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
-                <path d="M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-45.056L382.592 149.312a29.12 29.12 0 0 0-41.728 0z" fill="#CCCCCC"></path>
-            </svg>
-        </view>
-      </view>
-
-      <!-- 服务站点 -->
-      <view class="form-item">
-        <text class="label">服务站点</text>
-        <view class="input-box" @click="openStationPicker">
-            <text :style="{color: formData.station ? '#333' : '#ccc'}">{{ formData.station || '请选择服务站点' }}</text>
+        <text class="label">所属站点</text>
+        <view class="input-box" @click="openStationPickerCascader">
+            <view class="station-display" v-if="formData.station">
+                <text class="area-tag" v-if="formData.areaPath">{{ formData.areaPath }}</text>
+                <text class="station-name">{{ formData.station }}</text>
+            </view>
+            <text v-else style="color: #ccc">请选择所属站点</text>
             <!-- 灰色箭头 SVG -->
             <svg class="arrow-right" style="width:24rpx; height:24rpx; margin-left: auto;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
                 <path d="M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-45.056L382.592 149.312a29.12 29.12 0 0 0-41.728 0z" fill="#CCCCCC"></path>
@@ -132,13 +124,13 @@
     <!-- 自定义日期选择器 (已存在) --> 
     <!-- ... (Picker content) ... -->
 
-    <!-- 自定义城市选择器 (级联版) -->
-    <view class="picker-mask" :class="{show: showCityPicker}" @click="closeCityPicker">
+    <!-- 自定义站点选择器 (级联版) -->
+    <view class="picker-mask" :class="{show: showStationPickerCascader}" @click="closeStationPickerCascader">
         <view class="picker-content" @click.stop>
             <view class="picker-header">
-                <text class="picker-btn-cancel" @click="closeCityPicker">取消</text>
-                <text class="picker-title">请选择工作城市</text>
-                <text class="picker-btn-confirm" @click="confirmCity">确定</text>
+                <text class="picker-btn-cancel" @click="closeStationPickerCascader">取消</text>
+                <text class="picker-title">请选择所属站点</text>
+                <text class="picker-btn-confirm" @click="closeStationPickerCascader">关闭</text>
             </view>
             <view class="picker-body">
                 <!-- 左侧:垂直路径 -->
@@ -169,7 +161,7 @@
                         class="list-item" 
                         v-for="(item, index) in currentList" 
                         :key="item.id"
-                        @click="selectCityItem(item)"
+                        @click="selectStationItem(item)"
                     >
                         {{ item.name }}
                     </view>
@@ -181,26 +173,6 @@
         </view>
     </view>
 
-    <!-- 自定义站点选择器 -->
-    <view class="picker-mask" :class="{show: showStationPicker}" @click="closeStationPicker">
-        <view class="picker-content" @click.stop>
-            <view class="picker-header" style="justify-content: center; position: relative;">
-                <text class="picker-btn-cancel" style="position: absolute; left: 30rpx;" @click="closeStationPicker">取消</text>
-                <text class="picker-title">选择服务站点</text>
-            </view>
-            <scroll-view scroll-y class="picker-list">
-                <view 
-                    class="station-item" 
-                    v-for="(item, index) in stationList" 
-                    :key="index"
-                    @click="selectStation(item)"
-                >
-                    {{ item.name }}
-                </view>
-            </scroll-view>
-        </view>
-    </view>
-
     <!-- 底部协议与按钮 -->
     <view class="footer-actions">
         <view class="agreement-row">

+ 92 - 93
pages/recruit/logic.js

@@ -1,5 +1,5 @@
 import { sendSmsCode } from '@/api/resource/sms'
-import { getAreaChildren } from '@/api/fulfiller/app'
+import { getAreaStationList } from '@/api/system/areaStation'
 import { listAllService } from '@/api/service/list'
 import { getAgreement } from '@/api/system/agreement'
 
@@ -16,9 +16,9 @@ export default {
                 birthday: '',
                 password: '',
                 serviceType: [],
-                city: '',
                 station: '',
-                stationId: null
+                stationId: null,
+                areaPath: '' // 用于回显“区域+站点”名称
             },
             showPwd: false,
             isAgreed: false,
@@ -38,16 +38,13 @@ export default {
             tempMonth: 0,
             tempDay: 0,
 
-            // 城市选择器相关(从后端加载
-            showCityPicker: false,
+            // 站点选择器(级联版
+            showStationPickerCascader: false,
             selectStep: 0,
             selectedPathway: [],
             currentList: [],
-            selectedCityId: null,
-
-            // 站点选择器相关(从后端加载)
-            showStationPicker: false,
-            stationList: [],
+            fullStationData: [], // 全量数据
+            selectedStationId: null,
 
             // 协议弹窗
             showPrivacy: false,
@@ -56,14 +53,48 @@ export default {
             currentAgreementId: '' // 当前协议ID
         }
     },
-    created() {
+    onLoad() {
         this.initDateData();
         this.loadServiceTypes();
+        this.loadAreaStationData(); // 预加载站点全量数据
+        // 尝试从缓存中恢复数据(回显)
+        this.restoreFormData();
     },
     beforeDestroy() {
         if (this.timer) clearInterval(this.timer);
     },
     methods: {
+        async loadAreaStationData() {
+            try {
+                const res = await getAreaStationList();
+                this.fullStationData = res.data || [];
+                // 暂时保存在内存
+            } catch (err) {
+                console.error('加载站点列表失败:', err);
+            }
+        },
+        restoreFormData() {
+            try {
+                const saved = uni.getStorageSync('recruit_form_data');
+                if (saved) {
+                    const d = JSON.parse(saved);
+                    // 深度合并或手动赋值
+                    Object.assign(this.formData, d);
+                    // 恢复私有的路径状态
+                    if (d._selectedPathway) {
+                        this.selectedPathway = d._selectedPathway;
+                        this.selectStep = this.selectedPathway.length;
+                    }
+                    // 加载站点列表(如果选了区域的话)
+                    if (this.selectedPathway.length > 0) {
+                        const last = this.selectedPathway[this.selectedPathway.length - 1];
+                        if (last) this.loadStations(last.id);
+                    }
+                }
+            } catch (e) {
+                console.error('恢复表单数据失败', e);
+            }
+        },
         initDateData() {
             const now = new Date();
             const currentYear = now.getFullYear();
@@ -174,112 +205,72 @@ export default {
             }
         }, */
 
-        // 城市选择器 logic(从后端加载)
-        async openCityPicker() {
-            this.showCityPicker = true;
+        // 站点级联选择逻辑 (从全量本地数据中根据 parentId 过滤)
+        async openStationPickerCascader() {
+            this.showStationPickerCascader = true;
             if (this.selectedPathway.length === 0) {
-                await this.resetCityPicker();
+                await this.resetStationPicker();
             }
         },
-        async resetCityPicker() {
+        async resetStationPicker() {
             this.selectStep = 0;
             this.selectedPathway = [];
-            await this.loadAreaChildren(0);
+            this.filterLocalChildren(0);
         },
-        closeCityPicker() {
-            this.showCityPicker = false;
+        closeStationPickerCascader() {
+            this.showStationPickerCascader = false;
         },
-        async loadAreaChildren(parentId) {
-            try {
-                const res = await getAreaChildren(parentId);
-                // 城市选择器只显示 城市(0) 和 区域(1),不显示站点(2)
-                this.currentList = (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.currentList = [];
-            }
+        filterLocalChildren(parentId) {
+            // 从全量数据中筛选当前层级的子项
+            this.currentList = this.fullStationData.filter(item => item.parentId == parentId);
         },
-        async selectCityItem(item) {
+        async selectStationItem(item) {
             this.selectedPathway[this.selectStep] = item;
-            // type: 0=城市, 1=区域
-            // 城市级(0)继续加载子级区域
-            if (item.type === 0) {
+            
+            // 在全量数据中查找这是否有子节点 (子级联)
+            const sons = this.fullStationData.filter(i => i.parentId == item.id);
+            
+            if (sons.length > 0) {
+                // 进入下一步
                 this.selectStep++;
                 this.selectedPathway = this.selectedPathway.slice(0, this.selectStep);
-                await this.loadAreaChildren(item.id);
-                // 如果已无子级区域,自动确认
-                if (this.currentList.length === 0) {
-                    this.selectedCityId = item.id;
-                    this.confirmCity();
-                }
+                this.currentList = sons;
             } else {
-                // 区域级(1)选完即确认,站点由站点选择器单独加载
-                this.selectedCityId = item.id;
-                this.confirmCity();
+                // 没有子节点了,说明是最终的站点(叶子节点)
+                this.confirmStation();
             }
         },
         async jumpToStep(step) {
             this.selectStep = step;
             if (step === 0) {
-                await this.loadAreaChildren(0);
+                this.filterLocalChildren(0);
             } else {
                 const parent = this.selectedPathway[step - 1];
                 if (parent) {
-                    await this.loadAreaChildren(parent.id);
+                    this.filterLocalChildren(parent.id);
                 }
             }
         },
-        confirmCity() {
-            const fullPath = this.selectedPathway.map(i => i.name).join(' ');
-            this.formData.city = fullPath;
-            // 重置已选站点
-            this.formData.station = '';
-            this.formData.stationId = null;
-            // 选完城市/区域后加载该区域下的站点(type=2)
-            const lastSelected = this.selectedPathway[this.selectedPathway.length - 1];
-            if (lastSelected) {
-                this.loadStations(lastSelected.id);
-            }
-            this.closeCityPicker();
+        confirmStation() {
+            const path = this.selectedPathway.map(i => i.name);
+            const stationName = path[path.length - 1];
+            const areaName = path.slice(0, -1).join(' '); // 排除站点后的父级名
+            
+            this.formData.station = stationName;
+            this.formData.stationId = this.selectedPathway[this.selectedPathway.length - 1].id;
+            this.formData.areaPath = areaName;
+            
+            this.closeStationPickerCascader();
         },
 
-        // 站点选择器(从后端加载,只取type=2的站点)
-        async loadStations(parentId) {
-            try {
-                const res = await getAreaChildren(parentId);
-                this.stationList = (res.data || [])
-                    .filter(item => item.type === 2)
-                    .map(item => ({
-                        id: item.id,
-                        name: item.name
-                    }));
-            } catch (err) {
-                console.error('加载站点数据失败:', err);
-                this.stationList = [];
-            }
-        },
-        openStationPicker() {
-            if (this.stationList.length === 0) {
-                uni.showToast({ title: '请先选择工作城市', icon: 'none' });
-                return;
-            }
-            this.showStationPicker = true;
-        },
-        closeStationPicker() {
-            this.showStationPicker = false;
-        },
-        selectStation(item) {
-            this.formData.station = item.name;
-            this.formData.stationId = item.id;
-            this.closeStationPicker();
-        },
+        // --- 废弃的功能逻辑 (已被站点选择合并或移除) ---
+        /* async loadStations(parentId) { ... } */
+        /* openStationPicker() { ... } */
+        /* selectStation(item) { ... } */
+        /* async openCityPicker() { ... } */
+        /* loadAreaChildren(parentId) { ... } */
+        /* async selectCityItem(item) { ... } */
+        /* confirmCity() { ... } */
 
         async openPrivacy() {
             try {
@@ -316,8 +307,16 @@ export default {
                 uni.showToast({ title: '请选择服务类型', icon: 'none' });
                 return;
             }
+            if (!this.formData.stationId) {
+                uni.showToast({ title: '请选择所属站点', icon: 'none' });
+                return;
+            }
             // 暂存表单数据到本地,供后续页面组装提交
-            uni.setStorageSync('recruit_form_data', JSON.stringify(this.formData));
+            // 同时保存 selectedPathway 确保站点级联状态能恢复
+            uni.setStorageSync('recruit_form_data', JSON.stringify({
+                ...this.formData,
+                _selectedPathway: this.selectedPathway // 私有存储,仅用于回显
+            }));
             // 传递选中的服务类型对象(id+name)给后续页面
             const selectedServices = this.serviceTypes.filter(s => this.formData.serviceType.includes(s.id));
             const services = JSON.stringify(selectedServices);

+ 41 - 4
pages/recruit/qualifications_logic.js

@@ -14,15 +14,40 @@ export default {
                 this.serviceTypes = JSON.parse(decodeURIComponent(options.services));
                 // 初始化 qualifications 对象(以服务名称为key)
                 this.serviceTypes.forEach(item => {
-                    this.qualifications[item.name] = [];
-                    this.qualOssIds[item.name] = [];
+                    this.qualifications[item.name] = this.qualifications[item.name] || [];
+                    this.qualOssIds[item.name] = this.qualOssIds[item.name] || [];
                 });
             } catch (e) {
                 console.error('Parse services failed', e);
             }
         }
+        // 从缓存恢复
+        this.restoreQualData();
     },
     methods: {
+        saveQualData() {
+            try {
+                uni.setStorageSync('recruit_qual_data', JSON.stringify({
+                    qualifications: this.qualifications,
+                    qualOssIds: this.qualOssIds
+                }));
+            } catch (e) {
+                console.error('保存资质数据失败', e);
+            }
+        },
+        restoreQualData() {
+            try {
+                const saved = uni.getStorageSync('recruit_qual_data');
+                if (saved) {
+                    const d = JSON.parse(saved);
+                    this.qualifications = d.qualifications || {};
+                    this.qualOssIds = d.qualOssIds || {};
+                    this.$forceUpdate();
+                }
+            } catch (e) {
+                console.error('恢复资质数据失败', e);
+            }
+        },
         chooseImage(serviceName) {
             uni.chooseImage({
                 count: 9,
@@ -40,6 +65,7 @@ export default {
                         try {
                             const uploadRes = await uploadFile(tempPath);
                             this.qualOssIds[serviceName].push(uploadRes.data.ossId);
+                            this.saveQualData();
                         } catch (err) {
                             console.error('上传资质图片失败:', err);
                         }
@@ -52,6 +78,7 @@ export default {
             if (this.qualOssIds[serviceName]) {
                 this.qualOssIds[serviceName].splice(index, 1);
             }
+            this.saveQualData();
             this.$forceUpdate();
         },
         goBackToForm() {
@@ -94,7 +121,7 @@ export default {
                 gender: recruitData.gender === 1 ? '0' : '1',
                 birthday: recruitData.birthday || '',
                 serviceTypes: (recruitData.serviceType || []).join(','),  // 逗号分隔的服务类型ID
-                city: recruitData.city || '',
+                city: recruitData.areaPath || '',
                 stationId: recruitData.stationId || null,
                 realName: recruitData.realName || '',
                 idCard: recruitData.idNumber || '',
@@ -108,8 +135,18 @@ export default {
             try {
                 await submitAudit(auditData)
                 uni.hideLoading()
+                
+                const s = encodeURIComponent(recruitData.station || '');
+                const n = encodeURIComponent(recruitData.name || '');
+                const p = recruitData.mobile || '';
+
+                // 提交完成后清除缓存(因为数据已经传给下一步了)
+                uni.removeStorageSync('recruit_form_data');
+                uni.removeStorageSync('recruit_auth_data');
+                uni.removeStorageSync('recruit_qual_data');
+
                 uni.reLaunch({
-                    url: '/pages/recruit/success'
+                    url: `/pages/recruit/success?station=${s}&name=${n}&phone=${p}`
                 })
             } catch (err) {
                 uni.hideLoading()

+ 21 - 0
pages/recruit/style.css

@@ -57,6 +57,27 @@ page {
     font-size: 28rpx;
 }
 
+/* 站点显示容器 */
+.station-display {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    line-height: 1.4;
+    padding: 10rpx 0;
+}
+
+.area-tag {
+    font-size: 22rpx;
+    color: #999;
+    margin-bottom: 4rpx;
+}
+
+.station-name {
+    font-size: 28rpx;
+    color: #333;
+    font-weight: 500;
+}
+
 /* 特殊样式: 手机号前缀 */
 .prefix-area {
     display: flex;

+ 10 - 17
pages/recruit/success_logic.js

@@ -6,26 +6,19 @@ export default {
             phone: ''
         }
     },
-    onShow() {
-        this.loadSuccessData();
+    onLoad(options) {
+        if (options.station) {
+            this.station = decodeURIComponent(options.station);
+        }
+        if (options.name) {
+            this.name = decodeURIComponent(options.name);
+        }
+        if (options.phone) {
+            this.phone = options.phone;
+        }
     },
     methods: {
-        loadSuccessData() {
-            try {
-                const stored = uni.getStorageSync('recruit_form_data');
-                if (stored) {
-                    const data = JSON.parse(stored);
-                    this.station = data.station || '未设置';
-                    this.name = data.name || '未设置';
-                    this.phone = data.mobile || '未设置';
-                }
-            } catch (e) {
-                console.error('加载成功页数据失败', e);
-            }
-        },
         goHome() {
-            // 清理缓存的数据
-            uni.removeStorageSync('recruit_form_data');
             // 返回首页或登录页
             uni.reLaunch({
                 url: '/pages/login/login'

BIN
unpackage/cache/apk/__UNI__76F5C47_cm.apk


+ 1 - 1
unpackage/cache/apk/apkurl

@@ -1 +1 @@
-https://app.liuyingyong.cn/build/download/8de56ef0-2683-11f1-91c0-d16aab915886
+https://app.liuyingyong.cn/build/download/4a05bfe0-2747-11f1-8c1e-3d7a3a479d7d

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/apk/cmManifestCache.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/app-service.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/manifest.json


+ 1 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/mine/settings/about/agreement-detail.css

@@ -0,0 +1 @@
+body{background-color:#fff}.container{display:flex;flex-direction:column;height:100vh}.custom-header{position:fixed;top:0;left:0;width:100%;height:2.75rem;padding-top:var(--status-bar-height);background-color:#fff;display:flex;align-items:center;justify-content:space-between;padding-left:.9375rem;padding-right:.9375rem;box-sizing:content-box;z-index:100;border-bottom:.03125rem solid #f5f5f5}.header-placeholder{height:calc(2.75rem + var(--status-bar-height));flex-shrink:0}.back-icon{width:1.25rem;height:1.25rem}.header-title{font-size:1rem;font-weight:700;color:#333}.header-right{width:1.25rem}.content-scroll{flex:1;overflow:hidden}.content-card{padding:.9375rem 1.25rem;line-height:1.6}.rich-text{font-size:.875rem;color:#333}.safe-area-inset-bottom{height:constant(safe-area-inset-bottom);height:env(safe-area-inset-bottom)}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/recruit/form.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/dist/build/app-plus/app-service.js


+ 6 - 3
unpackage/dist/build/app-plus/manifest.json

@@ -7,8 +7,8 @@
   "id": "__UNI__76F5C47",
   "name": "履约者",
   "version": {
-    "name": "0.1.0",
-    "code": 1
+    "name": "0.1.2",
+    "code": 2
   },
   "description": "履约者APP",
   "developer": {
@@ -91,7 +91,10 @@
           "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
           "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
           "<uses-feature android:name=\"android.hardware.camera\"/>",
-          "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+          "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+          "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />",
+          "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />",
+          "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>"
         ]
       },
       "apple": {

+ 1 - 0
unpackage/dist/build/app-plus/pages/mine/settings/about/agreement-detail.css

@@ -0,0 +1 @@
+body{background-color:#fff}.container{display:flex;flex-direction:column;height:100vh}.custom-header{position:fixed;top:0;left:0;width:100%;height:2.75rem;padding-top:var(--status-bar-height);background-color:#fff;display:flex;align-items:center;justify-content:space-between;padding-left:.9375rem;padding-right:.9375rem;box-sizing:content-box;z-index:100;border-bottom:.03125rem solid #f5f5f5}.header-placeholder{height:calc(2.75rem + var(--status-bar-height));flex-shrink:0}.back-icon{width:1.25rem;height:1.25rem}.header-title{font-size:1rem;font-weight:700;color:#333}.header-right{width:1.25rem}.content-scroll{flex:1;overflow:hidden}.content-card{padding:.9375rem 1.25rem;line-height:1.6}.rich-text{font-size:.875rem;color:#333}.safe-area-inset-bottom{height:constant(safe-area-inset-bottom);height:env(safe-area-inset-bottom)}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/dist/build/app-plus/pages/recruit/form.css


Разница между файлами не показана из-за своего большого размера
+ 305 - 301
unpackage/dist/dev/app-plus/app-service.js


+ 1 - 0
unpackage/dist/dev/app-plus/manifest.json

@@ -19,6 +19,7 @@
   "permissions": {
     "Camera": {},
     "VideoPlayer": {},
+    "Contacts": {},
     "UniNView": {
       "description": "UniNView原生渲染"
     }

+ 60 - 0
unpackage/dist/dev/app-plus/pages/mine/settings/about/agreement-detail.css

@@ -0,0 +1,60 @@
+
+body {
+    background-color: #fff;
+}
+.container {
+    display: flex;
+    flex-direction: column;
+    height: 100vh;
+}
+
+/* 自定义头部统一风格 */
+.custom-header {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 2.75rem;
+    padding-top: var(--status-bar-height);
+    background-color: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-left: 0.9375rem;
+    padding-right: 0.9375rem;
+    box-sizing: content-box;
+    z-index: 100;
+    border-bottom: 0.03125rem solid #f5f5f5;
+}
+.header-placeholder {
+    height: calc(2.75rem + var(--status-bar-height));
+    flex-shrink: 0;
+}
+.back-icon {
+    width: 1.25rem;
+    height: 1.25rem;
+}
+.header-title {
+    font-size: 1rem;
+    font-weight: bold;
+    color: #333;
+}
+.header-right {
+    width: 1.25rem;
+}
+.content-scroll {
+    flex: 1;
+    overflow: hidden;
+}
+.content-card {
+    padding: 0.9375rem 1.25rem;
+    line-height: 1.6;
+}
+.rich-text {
+    font-size: 0.875rem;
+    color: #333;
+}
+.safe-area-inset-bottom {
+    height: constant(safe-area-inset-bottom);
+    height: env(safe-area-inset-bottom);
+}

+ 18 - 0
unpackage/dist/dev/app-plus/pages/recruit/form.css

@@ -114,6 +114,24 @@ body {
     height: 100%;
     font-size: 0.875rem;
 }
+/* 站点显示容器 */
+.station-display {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    line-height: 1.4;
+    padding: 0.3125rem 0;
+}
+.area-tag {
+    font-size: 0.6875rem;
+    color: #999;
+    margin-bottom: 0.125rem;
+}
+.station-name {
+    font-size: 0.875rem;
+    color: #333;
+    font-weight: 500;
+}
 /* 特殊样式: 手机号前缀 */
 .prefix-area {
     display: flex;

BIN
unpackage/release/apk/__UNI__76F5C47__20260324142623.apk


+ 96 - 0
utils/gps.js

@@ -0,0 +1,96 @@
+/**
+ * GPS 定位定时上报功能
+ */
+import { uploadGps } from '@/api/fulfiller/fulfiller.js'
+
+let gpsTimer = null
+
+/**
+ * 获取并上传GPS定位 (带授权检查)
+ */
+export function reportGps(manual = false) {
+  return new Promise((resolve, reject) => {
+    uni.getLocation({
+      type: 'wgs84',
+      success: function (res) {
+        const data = {
+          longitude: res.longitude,
+          latitude: res.latitude
+        }
+        uploadGps(data).then(() => {
+          console.log('GPS定位上传成功', data)
+          resolve(res)
+        }).catch(err => {
+          console.error('GPS定位上传失败', err)
+          reject(err)
+        })
+      },
+      fail: function (err) {
+        console.error('获取GPS定位失败', err)
+        // 如果是手动触发且失败,尝试引导授权
+        if (manual) {
+          checkAndRequestPermission(reject)
+        } else {
+          reject(err)
+        }
+      }
+    })
+  })
+}
+
+/**
+ * 检查并请求定位权限
+ */
+function checkAndRequestPermission(reject) {
+  uni.getSetting({
+    success(res) {
+      if (!res.authSetting['scope.userLocation']) {
+        uni.showModal({
+          title: '定位未授权',
+          content: '请开启定位权限,以便为您推荐附近的订单并记录服务轨迹',
+          confirmText: '去设置',
+          success: (modalRes) => {
+            if (modalRes.confirm) {
+              uni.openSetting({
+                success: (settingRes) => {
+                  if (settingRes.authSetting['scope.userLocation']) {
+                    reportGps(true)
+                  }
+                }
+              })
+            } else {
+              if (reject) reject(new Error('User denied location permission'))
+            }
+          }
+        })
+      } else {
+        // 权限其实是有的,可能是系统GPS屏蔽了或其他原因
+        uni.showToast({ title: '获取定位失败,请检查手机GPS是否开启', icon: 'none' })
+        if (reject) reject(new Error('Location failed even with permission'))
+      }
+    }
+  })
+}
+
+export function startGpsTimer() {
+  const isEnabled = uni.getStorageSync('GPS_REPORT_ENABLED')
+  if (isEnabled === false) {
+    stopGpsTimer()
+    return
+  }
+  stopGpsTimer()
+  reportGps() // 默认静默尝试
+  gpsTimer = setInterval(() => {
+    reportGps()
+  }, 1200000)
+}
+
+/**
+ * 停止GPS定时上报
+ */
+export function stopGpsTimer() {
+  if (gpsTimer) {
+    clearInterval(gpsTimer)
+    gpsTimer = null
+  }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов