Переглянути джерело

前端小程序代码上传

sunlupeng 7 місяців тому
батько
коміт
cddca59a5d
100 змінених файлів з 22297 додано та 0 видалено
  1. BIN
      elevator-ui/bak/.DS_Store
  2. 14 0
      elevator-ui/bak/README.md
  3. 55 0
      elevator-ui/bak/eslint.config.js
  4. 20 0
      elevator-ui/bak/index.html
  5. 18 0
      elevator-ui/bak/jsconfig.json
  6. 92 0
      elevator-ui/bak/package.json
  7. 11864 0
      elevator-ui/bak/pnpm-lock.yaml
  8. 10 0
      elevator-ui/bak/shims-uni.d.ts
  9. BIN
      elevator-ui/bak/src/.DS_Store
  10. 74 0
      elevator-ui/bak/src/App.vue
  11. 6 0
      elevator-ui/bak/src/api/building.js
  12. 294 0
      elevator-ui/bak/src/api/elevator.js
  13. 31 0
      elevator-ui/bak/src/api/expert.js
  14. 21 0
      elevator-ui/bak/src/api/system.js
  15. 141 0
      elevator-ui/bak/src/auto-import.d.ts
  16. BIN
      elevator-ui/bak/src/components/.DS_Store
  17. 39 0
      elevator-ui/bak/src/components/FloatFixedAddBtn/FloatFixedAddBtn.vue
  18. 729 0
      elevator-ui/bak/src/components/HandwrittenSignature/HandwrittenSignature.vue
  19. 199 0
      elevator-ui/bak/src/components/HandwrittenSignature/context.js
  20. 64 0
      elevator-ui/bak/src/components/HandwrittenSignature/props.js
  21. 228 0
      elevator-ui/bak/src/components/HandwrittenSignature/render.js
  22. 1 0
      elevator-ui/bak/src/components/HandwrittenSignature/signature.js
  23. 181 0
      elevator-ui/bak/src/components/HandwrittenSignature/utils.js
  24. 39 0
      elevator-ui/bak/src/components/UserAvatar/UserAvatar.vue
  25. 23 0
      elevator-ui/bak/src/composables/setting/env.js
  26. 9 0
      elevator-ui/bak/src/composables/use-cdn.js
  27. 50 0
      elevator-ui/bak/src/composables/use-fetch.js
  28. 13 0
      elevator-ui/bak/src/composables/use-list-scroll.js
  29. 12 0
      elevator-ui/bak/src/composables/use-page-scroll.js
  30. 71 0
      elevator-ui/bak/src/composables/use-paging.js
  31. 66 0
      elevator-ui/bak/src/composables/use-uni-api.js
  32. 46 0
      elevator-ui/bak/src/composables/use-upload.js
  33. 28 0
      elevator-ui/bak/src/config/index.js
  34. 25 0
      elevator-ui/bak/src/constants/state.js
  35. 3 0
      elevator-ui/bak/src/constants/storage.js
  36. 21 0
      elevator-ui/bak/src/constants/work.js
  37. 9 0
      elevator-ui/bak/src/helper/async.js
  38. 19 0
      elevator-ui/bak/src/main.js
  39. 70 0
      elevator-ui/bak/src/manifest.json
  40. 529 0
      elevator-ui/bak/src/pages.json
  41. BIN
      elevator-ui/bak/src/pages/.DS_Store
  42. 31 0
      elevator-ui/bak/src/pages/components/FloatFixedAddBtn.vue
  43. 73 0
      elevator-ui/bak/src/pages/components/PositionPicker.vue
  44. 447 0
      elevator-ui/bak/src/pages/components/StructureCascade.vue
  45. BIN
      elevator-ui/bak/src/pages/expert/apply-order/.DS_Store
  46. 35 0
      elevator-ui/bak/src/pages/expert/apply-order/add-record.vue
  47. 245 0
      elevator-ui/bak/src/pages/expert/apply-order/create.vue
  48. 211 0
      elevator-ui/bak/src/pages/expert/apply-order/detail.vue
  49. 46 0
      elevator-ui/bak/src/pages/expert/apply-order/evaluation.vue
  50. 189 0
      elevator-ui/bak/src/pages/expert/apply-order/index.vue
  51. 144 0
      elevator-ui/bak/src/pages/expert/apply-order/not-started.vue
  52. 128 0
      elevator-ui/bak/src/pages/expert/apply-order/plan.vue
  53. 184 0
      elevator-ui/bak/src/pages/expert/apply-order/record.vue
  54. 165 0
      elevator-ui/bak/src/pages/expert/apply-order/report.vue
  55. 134 0
      elevator-ui/bak/src/pages/expert/apply-order/state-record.vue
  56. 215 0
      elevator-ui/bak/src/pages/expert/apply-order/undone-detail.vue
  57. 409 0
      elevator-ui/bak/src/pages/expert/apply-order/undone-detail_bak.vue
  58. 65 0
      elevator-ui/bak/src/pages/expert/apply-order/user-signature.vue
  59. 138 0
      elevator-ui/bak/src/pages/expert/apply-order/work-sheet.vue
  60. 32 0
      elevator-ui/bak/src/pages/expert/index.vue
  61. 56 0
      elevator-ui/bak/src/pages/expert/mine/index.vue
  62. BIN
      elevator-ui/bak/src/pages/expert/order/.DS_Store
  63. 35 0
      elevator-ui/bak/src/pages/expert/order/add-record.vue
  64. 196 0
      elevator-ui/bak/src/pages/expert/order/create.vue
  65. 163 0
      elevator-ui/bak/src/pages/expert/order/detail.vue
  66. 46 0
      elevator-ui/bak/src/pages/expert/order/evaluation.vue
  67. 227 0
      elevator-ui/bak/src/pages/expert/order/index.vue
  68. 145 0
      elevator-ui/bak/src/pages/expert/order/not-started.vue
  69. 128 0
      elevator-ui/bak/src/pages/expert/order/plan.vue
  70. 184 0
      elevator-ui/bak/src/pages/expert/order/record.vue
  71. 165 0
      elevator-ui/bak/src/pages/expert/order/report.vue
  72. 134 0
      elevator-ui/bak/src/pages/expert/order/state-record.vue
  73. 203 0
      elevator-ui/bak/src/pages/expert/order/undone-detail.vue
  74. 409 0
      elevator-ui/bak/src/pages/expert/order/undone-detail_bak.vue
  75. 65 0
      elevator-ui/bak/src/pages/expert/order/user-signature.vue
  76. 138 0
      elevator-ui/bak/src/pages/expert/order/work-sheet.vue
  77. 38 0
      elevator-ui/bak/src/pages/expert/use-help/index.vue
  78. 49 0
      elevator-ui/bak/src/pages/index/index.vue
  79. BIN
      elevator-ui/bak/src/pages/redirect/.DS_Store
  80. 65 0
      elevator-ui/bak/src/pages/redirect/elevator.vue
  81. 65 0
      elevator-ui/bak/src/pages/redirect/index.vue
  82. 138 0
      elevator-ui/bak/src/pages/tabbar/message/index.vue
  83. 115 0
      elevator-ui/bak/src/pages/tabbar/mine/index.vue
  84. 61 0
      elevator-ui/bak/src/pages/tabbar/work/components/AppGroupItem.vue
  85. 227 0
      elevator-ui/bak/src/pages/tabbar/work/constants.js
  86. 132 0
      elevator-ui/bak/src/pages/tabbar/work/index.vue
  87. BIN
      elevator-ui/bak/src/pages/views/.DS_Store
  88. 20 0
      elevator-ui/bak/src/pages/views/about-app/index.vue
  89. 30 0
      elevator-ui/bak/src/pages/views/basic-info/components/CardCellData.vue
  90. 99 0
      elevator-ui/bak/src/pages/views/basic-info/feedback.vue
  91. 145 0
      elevator-ui/bak/src/pages/views/basic-info/index.vue
  92. 67 0
      elevator-ui/bak/src/pages/views/big-screen/index.vue
  93. 84 0
      elevator-ui/bak/src/pages/views/check-in/components/CalendarTab.vue
  94. 195 0
      elevator-ui/bak/src/pages/views/check-in/components/CheckInTab.vue
  95. 21 0
      elevator-ui/bak/src/pages/views/check-in/index.vue
  96. 149 0
      elevator-ui/bak/src/pages/views/choose-elevator/index.vue
  97. 186 0
      elevator-ui/bak/src/pages/views/choose-worker/index.vue
  98. 30 0
      elevator-ui/bak/src/pages/views/elevator-data/components/CardCellData.vue
  99. 82 0
      elevator-ui/bak/src/pages/views/elevator-data/index.vue
  100. 0 0
      elevator-ui/bak/src/pages/views/elevator-examine/detail.vue

BIN
elevator-ui/bak/.DS_Store


+ 14 - 0
elevator-ui/bak/README.md

@@ -0,0 +1,14 @@
+## 项目介绍
+
+本项目是基于[uniapp cli](https://zh.uniapp.dcloud.io/worktile/CLI.html)创建,适用于小程序、H5、App快速搭建uniapp项目的脚手架。
+
+## 使用框架
+
+- [uview-plus](https://uiadmin.net/uview-plus/)
+  - uview-plus,是全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水,基于uView2.0初步修改,后续会陆续修复vue3兼容性,以及组合式API改造等。
+
+- [z-paging](https://z-paging.zxlee.cn/api/props/common.html)
+  - 一个uni-app (opens new window)分页组件。全平台兼容,支持自定义下拉刷新、上拉加载更多,支持虚拟列表,支持自动管理空数据图、点击返回顶部,支持聊天分页、本地分页,支持展示最后更新时间,支持国际化等等。
+
+- [unocss](https://unocss.dev/)
+  - unocss 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。

+ 55 - 0
elevator-ui/bak/eslint.config.js

@@ -0,0 +1,55 @@
+import { defineConfig } from '@llius/eslint-config'
+
+export default defineConfig(
+  {},
+  {
+    files: ['src/**/*.{js,vue}'],
+    rules: {
+      // 去除一行中的末尾多余空格
+      'no-trailing-spaces': [
+        'error',
+        {
+          skipBlankLines: true
+        }
+      ],
+      // v-model:visible检测
+      'vue/no-v-model-argument': 'off',
+      'vue/no-v-for-template-key-on-child': 'off',
+      'vue/no-v-for-template-key': 'off',
+      'vue/no-use-v-if-with-v-for': 'off',
+      'vue/no-v-html': 'off',
+      // fn () 去除函数调用后的空格
+      'func-call-spacing': ['error', 'never'],
+      'vue/v-on-event-hyphenation': 'off',
+      // 非模板字符串不需要使用反引号
+      quotes: [
+        'error',
+        'single',
+        {
+          avoidEscape: true,
+          allowTemplateLiterals: false
+        }
+      ],
+      'prettier/prettier': [
+        'error',
+        {
+          printWidth: 80,
+          tabWidth: 2,
+          useTabs: false,
+          semi: false,
+          vueIndentScriptAndStyle: false,
+          singleQuote: true,
+          trailingComma: 'none',
+          bracketSpacing: true,
+          arrowParens: 'always',
+          requirePragma: false,
+          insertPragma: false,
+          htmlWhitespaceSensitivity: 'ignore'
+        },
+        {
+          usePrettierrc: false
+        }
+      ]
+    }
+  }
+)

+ 20 - 0
elevator-ui/bak/index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 18 - 0
elevator-ui/bak/jsconfig.json

@@ -0,0 +1,18 @@
+{
+  "compilerOptions": {
+    "types": [],
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["src/*"],
+      "api/*": ["src/api/*"],
+      "pages/*": ["src/pages/*"],
+      "components/*": ["src/pages/components/*"],
+      "utils/*": ["src/utils/*"],
+      "helper/*": ["src/helper/*"],
+      "store/*": ["src/store/*"],
+      "constants/*": ["src/constants/*"],
+      "hooks/*": ["src/composables/*"]
+    }
+  },
+  "skipLibCheck": true
+}

+ 92 - 0
elevator-ui/bak/package.json

@@ -0,0 +1,92 @@
+{
+  "name": "荣君安科技",
+  "type": "module",
+  "version": "0.0.1",
+  "scripts": {
+    "dev:app": "uni -p app",
+    "dev:app-android": "uni -p app-android",
+    "dev:app-ios": "uni -p app-ios",
+    "dev:app-harmony": "uni -p app-harmony",
+    "dev:custom": "uni -p",
+    "dev:h5": "uni",
+    "dev:h5:ssr": "uni --ssr",
+    "dev:mp-alipay": "uni -p mp-alipay",
+    "dev:mp-baidu": "uni -p mp-baidu",
+    "dev:mp-jd": "uni -p mp-jd",
+    "dev:mp-kuaishou": "uni -p mp-kuaishou",
+    "dev:mp-lark": "uni -p mp-lark",
+    "dev:mp-qq": "uni -p mp-qq",
+    "dev:mp-toutiao": "uni -p mp-toutiao",
+    "dev:mp-weixin": "uni -p mp-weixin",
+    "dev:mp-xhs": "uni -p mp-xhs",
+    "dev:quickapp-webview": "uni -p quickapp-webview",
+    "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
+    "dev:quickapp-webview-union": "uni -p quickapp-webview-union",
+    "build:app": "uni build -p app",
+    "build:app-android": "uni build -p app-android",
+    "build:app-ios": "uni build -p app-ios",
+    "build:app-harmony": "uni build -p app-harmony",
+    "build:custom": "uni build -p",
+    "build:h5": "uni build",
+    "build:h5:ssr": "uni build --ssr",
+    "build:mp-alipay": "uni build -p mp-alipay",
+    "build:mp-baidu": "uni build -p mp-baidu",
+    "build:mp-jd": "uni build -p mp-jd",
+    "build:mp-kuaishou": "uni build -p mp-kuaishou",
+    "build:mp-lark": "uni build -p mp-lark",
+    "build:mp-qq": "uni build -p mp-qq",
+    "build:mp-toutiao": "uni build -p mp-toutiao",
+    "build:mp-weixin": "uni build -p mp-weixin",
+    "build:mp-xhs": "uni build -p mp-xhs",
+    "build:quickapp-webview": "uni build -p quickapp-webview",
+    "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
+    "build:quickapp-webview-union": "uni build -p quickapp-webview-union",
+    "eslint:fix": "eslint --fix src"
+  },
+  "dependencies": {
+    "@dcloudio/uni-app": "3.0.0-4020920240930001",
+    "@dcloudio/uni-app-harmony": "3.0.0-4020920240930001",
+    "@dcloudio/uni-app-plus": "3.0.0-4020920240930001",
+    "@dcloudio/uni-components": "3.0.0-4020920240930001",
+    "@dcloudio/uni-h5": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-alipay": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-baidu": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-jd": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-kuaishou": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-lark": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-qq": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-toutiao": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-weixin": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-xhs": "3.0.0-4020920240930001",
+    "@dcloudio/uni-quickapp-webview": "3.0.0-4020920240930001",
+    "@uni-helper/uni-use": "^0.19.12",
+    "@vueuse/core": "9",
+    "clipboard": "^2.0.11",
+    "crypto-js": "^4.2.0",
+    "dayjs": "^1.11.13",
+    "flyio": "^0.6.14",
+    "jsencrypt": "^3.3.2",
+    "pinia": "^2.2.2",
+    "uview-plus": "^3.3.26",
+    "vue": "^3.4.38",
+    "vue-i18n": "^9.14.0"
+  },
+  "devDependencies": {
+    "@dcloudio/types": "^3.4.12",
+    "@dcloudio/uni-automator": "3.0.0-4020920240930001",
+    "@dcloudio/uni-cli-shared": "3.0.0-4020920240930001",
+    "@dcloudio/uni-stacktracey": "3.0.0-4020920240930001",
+    "@dcloudio/vite-plugin-uni": "3.0.0-4020920240930001",
+    "@llius/eslint-config": "^1.0.4",
+    "@uni-helper/unocss-preset-uni": "^0.2.9",
+    "@unocss/preset-rem-to-px": "^0.62.2",
+    "@vue/runtime-core": "^3.4.38",
+    "eslint": "^8.57.0",
+    "sass": "^1.77.8",
+    "sass-loader": "10",
+    "unocss": "^0.62.2",
+    "unocss-applet": "^0.8.2",
+    "unplugin-auto-import": "^0.18.2",
+    "vite": "5.2.8"
+  }
+}

Різницю між файлами не показано, бо вона завелика
+ 11864 - 0
elevator-ui/bak/pnpm-lock.yaml


+ 10 - 0
elevator-ui/bak/shims-uni.d.ts

@@ -0,0 +1,10 @@
+/// <reference types='@dcloudio/types' />
+import 'vue'
+
+declare module '@vue/runtime-core' {
+  type Hooks = App.AppInstance & Page.PageInstance;
+
+  interface ComponentCustomOptions extends Hooks {
+
+  }
+}

BIN
elevator-ui/bak/src/.DS_Store


+ 74 - 0
elevator-ui/bak/src/App.vue

@@ -0,0 +1,74 @@
+<script setup>
+import { useUserStore } from '@/store/user'
+const { setUserInfo, setUserRoles, setPlace } = useUserStore()
+
+const launchUpdate = () => {
+  const updateManager = uni.getUpdateManager()
+  const onUpdateReady = () => {
+    updateManager.onUpdateReady(() => {
+      uni.showModal({
+        title: '更新提示',
+        content: '新版本已经准备好,是否重启应用?',
+        success(res) {
+          if (res.confirm) {
+            updateManager.applyUpdate()
+          }
+        }
+      })
+    })
+  }
+
+  updateManager &&
+    updateManager.onCheckForUpdate((res) => {
+      console.log(`是否检测到有新版本更新: ${res.hasUpdate ? '是' : '否'}`)
+      res.hasUpdate && onUpdateReady()
+    })
+
+  updateManager &&
+    updateManager.onUpdateFailed((err) => {
+      console.log('新的版本下载失败', err)
+    })
+}
+
+onLaunch(() => {
+  console.log('App Launch')
+  setPlace()
+  setUserInfo()
+  setUserRoles()
+})
+
+onShow(() => {
+  console.log('App Show')
+  launchUpdate()
+})
+
+onHide(() => {
+  console.log('App Hide')
+})
+</script>
+
+<style lang="scss">
+/* uview-plus styles */
+@import 'uview-plus/index.scss';
+/* iconfont custom icon */
+@import './static/iconfont/iconfont.css';
+</style>
+
+<style>
+/*每个页面公共css */
+view {
+  box-sizing: border-box;
+}
+.hold-table::before {
+  content: '';
+  display: table;
+}
+
+/* 重置 cell 样式 */
+.u-cell__body .u-cell__body__content {
+  flex: none;
+}
+.u-cell__body .u-cell__value {
+  max-width: 420rpx;
+}
+</style>

+ 6 - 0
elevator-ui/bak/src/api/building.js

@@ -0,0 +1,6 @@
+import request from 'utils/request'
+
+// 获取楼栋列表
+export const getBuildingListApi = (data) => {
+  return request.tryGet('/building/buildingInfo/list', data)
+}

+ 294 - 0
elevator-ui/bak/src/api/elevator.js

@@ -0,0 +1,294 @@
+import request from 'utils/request'
+
+// 获取电梯列表
+export const getElevatorListApi = (data) => {
+  return request.tryGet('/elevator/info/list', data)
+}
+
+// 获取电梯详情
+export const getElevatorInfoApi = (data) => {
+  return request.tryGet(`/elevator/info/${data.id}`, data)
+}
+
+// 获取电梯详情 - 无 token
+export const getElevatorInfoWxApi = (data) => {
+  return request.tryGet('/wechat/portal/ma/wxff99592fd1b5d95b/getEleInfo', data)
+}
+
+// 获取电梯详情 - 无 token
+export const getElevatorIdInfoWxApi = (data) => {
+  return request.tryGet(
+    `/wechat/portal/ma/wxff99592fd1b5d95b/getEleInfo/${data.id}`,
+    data
+  )
+}
+
+// 根据电梯编号获取电梯信息
+export const getElevatorInfoDetailApi = (data) => {
+  return request.tryGet(`/elevator/info/detail/${data.elevatorNum}`, data)
+}
+
+// 获取电梯救援列表
+export const getOtherMaintainRecordListApi = (data) => {
+  return request.tryGet('/otherRecord/otherMaintainRecord/list', data)
+}
+
+// 获取电梯维保计划列表
+export const getMaintainPlanListApi = (data) => {
+  return request.tryGet('/maintenancePlan/maintenancePlan/list', data)
+}
+
+// 获取电梯维保列表
+export const getMaintainRecordListApi = (data) => {
+  return request.tryGet('/mrecord/maintenanceRecord/list', data)
+}
+
+// 获取电梯维保详情
+export const getMaintainRecordInfoApi = (data) => {
+  return request.tryGet(`/mrecord/maintenanceRecord/${data.id}`, data)
+}
+
+// 获取电梯运行记录
+export const getBehaviorMessageListApi = (data) => {
+  return request.tryGet('/behavior/behaviorMessage/list', data)
+}
+
+// 获取电梯运行详情
+export const getBehaviorMessageInfoApi = (data) => {
+  return request.tryGet(`/behavior/behaviorMessage/${data.id}`, data)
+}
+
+// 获取签到列表
+export const getSignInListApi = (data) => {
+  return request.tryGet('/sign/signInInfo/list', data)
+}
+
+// 签到
+export const postSignInApi = (data) => {
+  return request.tryPost('/sign/signInInfo', data)
+}
+
+// 获取乘客反馈列表
+export const getPassengerFeedbackListApi = (data) => {
+  return request.tryGet('/feedback/passengerFeedback/list', data)
+}
+
+// 提交乘客反馈列表
+export const postPassengerFeedbackListApi = (data) => {
+  return request.tryPost('/feedback/passengerFeedback', data)
+}
+
+// 获取故障列表
+export const getFaultMessageListApi = (data) => {
+  return request.tryGet('/fault/faultMessage/list', data)
+}
+
+// 获取电梯操作记录
+export const getWorkOperationRecordListApi = (data) => {
+  return request.tryGet('/elevator/workOperationRecord/list', data)
+}
+
+// 获取电梯操作详情
+export const getWorkOperationRecordInfoApi = (data) => {
+  return request.tryGet(`/elevator/workOperationRecord/${data.id}`, data)
+}
+
+// 获取员工信息列表
+export const getWorkerInfoListApi = (data) => {
+  return request.tryGet('/worker/workerInfo/list', data)
+}
+
+// 获取电梯详情统计
+export const getElevatorDetailStatisticsApi = (data) => {
+  return request.tryGet(
+    '/elevator/behaviorStatistics/elevator/detail/statistics',
+    data
+  )
+}
+
+// 获取电梯实时数据
+export const getElevatorRealDataApi = (data) => {
+  return request.tryGet(`/elevator/info/real/${data.elevatorNum}`, data)
+}
+
+// 获取维保完成数量统计
+export const getCompleteStatisticsApi = (data) => {
+  return request.tryGet(
+    '/maintenancePlan/maintenancePlan/complete/statistics/list',
+    data
+  )
+}
+
+// 获取工作数据
+export const getWorkDataListApi = (data) => {
+  return request.tryGet('/work/workData/list', {
+    ...data,
+    miniSearchFlag: true
+  })
+  // return request.tryGet('/work/workData/list', data)
+}
+
+// 获取工作数据详情
+export const getWorkDataInfoApi = (data) => {
+  return request.tryGet(`/work/workData/${data.id}`, data)
+}
+
+// 派发工作数据信息
+export const postWorkDataDistributionApi = (data) => {
+  return request.tryPost('/work/workData/distribution', data)
+}
+
+// 修改工作数据
+export const putWorkDataApi = (data) => {
+  return request.tryPut('/work/workData', data)
+}
+
+// 轿厢 - 新增工作记录轿厢信息
+export const postWorkElevatorCabinApi = (data) => {
+  return request.tryPost('/elevator/workElevatorCabin', data)
+}
+
+// 轿厢 - 获取工作记录轿厢信息
+export const getWorkElevatorCabinInfoApi = (data) => {
+  return request.tryGet(`/elevator/workElevatorCabin/info/${data.id}`, data)
+}
+
+// 轿顶 - 新增工作记录轿顶信息
+export const postWorkCarRoofApi = (data) => {
+  return request.tryPost('/elevator/workCarRoof', data)
+}
+
+// 轿顶 - 获取工作记录轿顶信息
+export const getWorkCarRoofInfoApi = (data) => {
+  return request.tryGet(`/elevator/workCarRoof/info/${data.id}`, data)
+}
+
+// 机房 - 新增工作记录机房信息
+export const postWorkMachineRoomApi = (data) => {
+  return request.tryPost('/elevator/workPit', data)
+}
+
+// 机房 - 获取工作记录机房信息
+export const getWorkMachineRoomInfoApi = (data) => {
+  return request.tryGet(`/elevator/workMachineRoom/info/${data.id}`, data)
+}
+
+// 底坑 - 新增工作记录底坑信息
+export const postWorkPitApi = (data) => {
+  return request.tryPost('/elevator/workPit', data)
+}
+
+// 底坑 - 获取工作记录底坑信息
+export const getWorkPitInfoApi = (data) => {
+  return request.tryGet(`/elevator/workPit/info/${data.id}`, data)
+}
+
+// 提交 - 工作表
+export const postWorkDataWorkRecordApi = (data) => {
+  return request.tryPost('/work/workData/workRecord', data)
+}
+
+// 工作接收
+export const postWorkDataReceiveApi = (data) => {
+  return request.tryPost('/work/workData/receive', data)
+}
+
+// 工作到场
+export const postWorkDataArriveApi = (data) => {
+  return request.tryPost('/work/workData/arrive', data)
+}
+
+// 工作评价
+export const postWorkDataEvaluateApi = (data) => {
+  return request.tryPost('/work/workData/evaluate', data)
+}
+
+// 获取超期年检列表
+export const getAnnualPlanOverdueListApi = (data) => {
+  return request.tryGet('/annual/annualPlan/overdue/list', data)
+}
+
+// 获取超期维保列表
+export const getWorkDataOverdueListApi = (data) => {
+  return request.tryGet('/work/workData/overdue/list', data)
+}
+
+// 获取年检日期
+export const getAnnualPlanGetDateApi = (data) => {
+  return request.tryGet('/annual/annualPlan/getDate', data)
+}
+
+// 获取年检列表
+export const getAnnualPlanListApi = (data) => {
+  return request.tryGet('/annual/annualPlan/list', data)
+}
+
+// 查询电梯及工作人员关联信息列表
+export const getElevatorUserListApi = (data) => {
+  return request.tryGet('/elevator/elevatorUser/list', data)
+}
+
+// 批量关注或取消关注电梯
+export const postElevatorUserConcernApi = (data) => {
+  return request.tryPost('/elevator/elevatorUser/concern', data)
+}
+
+// 获取电梯体检详细信息
+export const getElevatorExaminationApi = (data) => {
+  return request.tryGet(`/elevator/elevatorExamination/${data.id}`)
+}
+
+// 获取单个电梯体检详细信息
+export const getElevatorExaminationInfoApi = (data) => {
+  return request.tryGet('/elevator/elevatorExamination/info', data)
+}
+
+// 根据日期获取电梯年检信息
+export const getAnnualPlanGetByDateApi = (data) => {
+  return request.tryGet(`/annual/annualPlan/getByDate/${data.planDate}`, data)
+}
+
+// 根据电梯编号查询物业维保等人员信息
+export const getThirdPartyUserApi = (data) => {
+  return request.tryGet(
+    `/elevator/thirdPartyUser/getThirdPartyUser/${data.elevatorNum}`,
+    data
+  )
+}
+
+// 修改密码
+export const putProfileUpdatePwdApi = (data) => {
+  return request.tryPut('/system/user/profile/updatePwd', data, {
+    encrypt: true
+  })
+}
+
+// 工作记录附件(图片、视频等)上传
+export const postWorkDataRecordUploadApi = (data, id) => {
+  return request.tryPost(`/work/workData/record/upload/${id}`, data)
+}
+
+// 工作签名
+export const postWorkDataSignApi = (data) => {
+  return request.tryPost('/work/workData/sign', data)
+}
+
+// 工作签名完成
+export const postWorkDataCompleteApi = (data) => {
+  return request.tryPost('/work/workData/complete', data)
+}
+
+// 创建维修单
+export const postWorkDataRepairCreateApi = (data) => {
+  return request.tryPost('/work/workData/repair/create', data)
+}
+
+// 维修单审核
+export const postWorkDataRepairAuditApi = (data) => {
+  return request.tryPost('/work/workData/repair/audit', data)
+}
+
+// 获取大屏电梯列表
+export const getElevatorLargeScreenListApi = (data) => {
+  return request.tryGet('/elevator/largeScreen/list', data)
+}

+ 31 - 0
elevator-ui/bak/src/api/expert.js

@@ -0,0 +1,31 @@
+import request from 'utils/request'
+
+// 查询专家咨询列表
+export const getExpertConsultListApi = (data) => {
+  return request.tryGet('/elevator/expertConsult/list', data)
+}
+
+// 获取专家咨询详细信息
+export const getExpertConsultInfoApi = (data) => {
+  return request.tryGet(`/elevator/expertConsult/${data.id}`, data)
+}
+
+// 新建专家咨询
+export const postExpertConsultApi = (data) => {
+  return request.tryPost('/elevator/expertConsult', data)
+}
+
+// 接收专家咨询工单
+export const postExpertConsultReceiveApi = (data) => {
+  return request.tryPost('/elevator/expertConsult/receive', data)
+}
+
+// 完成专家咨询工单
+export const postExpertConsultCompleteApi = (data) => {
+  return request.tryPost('/elevator/expertConsult/complete', data)
+}
+
+// 审核专家咨询工单
+export const postExpertConsultAuditApi = (data) => {
+  return request.tryPost('/elevator/expertConsult/audit', data)
+}

+ 21 - 0
elevator-ui/bak/src/api/system.js

@@ -0,0 +1,21 @@
+import request from 'utils/request'
+
+// 获取token
+export const postAuthLoginApi = (data = {}) => {
+  return request.tryPost('/auth/login', data, { encrypt: true })
+}
+
+// 获取用户信息
+export const getUserInfoApi = (data) => {
+  return request.tryGet('/system/user/getInfo', data)
+}
+
+// 获取用户部门树
+export const getUserDeptTreeApi = (data) => {
+  return request.tryGet('/system/user/deptTree', data)
+}
+
+// 电梯微信用户关联绑定
+export const postElevatorWechatUserBindApi = (data) => {
+  return request.tryPost('/elevator/elevatorWechatUser/bind', data)
+}

+ 141 - 0
elevator-ui/bak/src/auto-import.d.ts

@@ -0,0 +1,141 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+export {}
+declare global {
+  const EffectScope: typeof import('vue')['EffectScope']
+  const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
+  const computed: typeof import('vue')['computed']
+  const createApp: typeof import('vue')['createApp']
+  const createPinia: typeof import('pinia')['createPinia']
+  const customRef: typeof import('vue')['customRef']
+  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
+  const defineComponent: typeof import('vue')['defineComponent']
+  const defineStore: typeof import('pinia')['defineStore']
+  const effectScope: typeof import('vue')['effectScope']
+  const getActivePinia: typeof import('pinia')['getActivePinia']
+  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
+  const getCurrentScope: typeof import('vue')['getCurrentScope']
+  const guessSerializerType: typeof import('@uni-helper/uni-use')['guessSerializerType']
+  const h: typeof import('vue')['h']
+  const inject: typeof import('vue')['inject']
+  const isProxy: typeof import('vue')['isProxy']
+  const isReactive: typeof import('vue')['isReactive']
+  const isReadonly: typeof import('vue')['isReadonly']
+  const isRef: typeof import('vue')['isRef']
+  const mapActions: typeof import('pinia')['mapActions']
+  const mapGetters: typeof import('pinia')['mapGetters']
+  const mapState: typeof import('pinia')['mapState']
+  const mapStores: typeof import('pinia')['mapStores']
+  const mapWritableState: typeof import('pinia')['mapWritableState']
+  const markRaw: typeof import('vue')['markRaw']
+  const nextTick: typeof import('vue')['nextTick']
+  const onActivated: typeof import('vue')['onActivated']
+  const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
+  const onBackPress: typeof import('@dcloudio/uni-app')['onBackPress']
+  const onBeforeMount: typeof import('vue')['onBeforeMount']
+  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
+  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
+  const onDeactivated: typeof import('vue')['onDeactivated']
+  const onError: typeof import('@dcloudio/uni-app')['onError']
+  const onErrorCaptured: typeof import('vue')['onErrorCaptured']
+  const onHide: typeof import('@dcloudio/uni-app')['onHide']
+  const onLaunch: typeof import('@dcloudio/uni-app')['onLaunch']
+  const onLoad: typeof import('@dcloudio/uni-app')['onLoad']
+  const onMounted: typeof import('vue')['onMounted']
+  const onNavigationBarButtonTap: typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']
+  const onNavigationBarSearchInputChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']
+  const onNavigationBarSearchInputClicked: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']
+  const onNavigationBarSearchInputConfirmed: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']
+  const onNavigationBarSearchInputFocusChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']
+  const onPageNotFound: typeof import('@dcloudio/uni-app')['onPageNotFound']
+  const onPageScroll: typeof import('@dcloudio/uni-app')['onPageScroll']
+  const onPullDownRefresh: typeof import('@dcloudio/uni-app')['onPullDownRefresh']
+  const onReachBottom: typeof import('@dcloudio/uni-app')['onReachBottom']
+  const onReady: typeof import('@dcloudio/uni-app')['onReady']
+  const onRenderTracked: typeof import('vue')['onRenderTracked']
+  const onRenderTriggered: typeof import('vue')['onRenderTriggered']
+  const onResize: typeof import('@dcloudio/uni-app')['onResize']
+  const onScopeDispose: typeof import('vue')['onScopeDispose']
+  const onServerPrefetch: typeof import('vue')['onServerPrefetch']
+  const onShareAppMessage: typeof import('@dcloudio/uni-app')['onShareAppMessage']
+  const onShareTimeline: typeof import('@dcloudio/uni-app')['onShareTimeline']
+  const onShow: typeof import('@dcloudio/uni-app')['onShow']
+  const onTabItemTap: typeof import('@dcloudio/uni-app')['onTabItemTap']
+  const onThemeChange: typeof import('@dcloudio/uni-app')['onThemeChange']
+  const onUnhandledRejection: typeof import('@dcloudio/uni-app')['onUnhandledRejection']
+  const onUnload: typeof import('@dcloudio/uni-app')['onUnload']
+  const onUnmounted: typeof import('vue')['onUnmounted']
+  const onUpdated: typeof import('vue')['onUpdated']
+  const provide: typeof import('vue')['provide']
+  const reactive: typeof import('vue')['reactive']
+  const readonly: typeof import('vue')['readonly']
+  const ref: typeof import('vue')['ref']
+  const resolveComponent: typeof import('vue')['resolveComponent']
+  const setActivePinia: typeof import('pinia')['setActivePinia']
+  const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
+  const shallowReactive: typeof import('vue')['shallowReactive']
+  const shallowReadonly: typeof import('vue')['shallowReadonly']
+  const shallowRef: typeof import('vue')['shallowRef']
+  const storeToRefs: typeof import('pinia')['storeToRefs']
+  const toRaw: typeof import('vue')['toRaw']
+  const toRef: typeof import('vue')['toRef']
+  const toRefs: typeof import('vue')['toRefs']
+  const toValue: typeof import('vue')['toValue']
+  const triggerRef: typeof import('vue')['triggerRef']
+  const tryOnBackPress: typeof import('@uni-helper/uni-use')['tryOnBackPress']
+  const tryOnHide: typeof import('@uni-helper/uni-use')['tryOnHide']
+  const tryOnInit: typeof import('@uni-helper/uni-use')['tryOnInit']
+  const tryOnLoad: typeof import('@uni-helper/uni-use')['tryOnLoad']
+  const tryOnReady: typeof import('@uni-helper/uni-use')['tryOnReady']
+  const tryOnScopeDispose: typeof import('@uni-helper/uni-use')['tryOnScopeDispose']
+  const tryOnShow: typeof import('@uni-helper/uni-use')['tryOnShow']
+  const tryOnUnload: typeof import('@uni-helper/uni-use')['tryOnUnload']
+  const unref: typeof import('vue')['unref']
+  const useActionSheet: typeof import('@uni-helper/uni-use')['useActionSheet']
+  const useAttrs: typeof import('vue')['useAttrs']
+  const useClipboardData: typeof import('@uni-helper/uni-use')['useClipboardData']
+  const useCssModule: typeof import('vue')['useCssModule']
+  const useCssVars: typeof import('vue')['useCssVars']
+  const useDownloadFile: typeof import('@uni-helper/uni-use')['useDownloadFile']
+  const useGlobalData: typeof import('@uni-helper/uni-use')['useGlobalData']
+  const useInterceptor: typeof import('@uni-helper/uni-use')['useInterceptor']
+  const useLoading: typeof import('@uni-helper/uni-use')['useLoading']
+  const useModal: typeof import('@uni-helper/uni-use')['useModal']
+  const useNetwork: typeof import('@uni-helper/uni-use')['useNetwork']
+  const useOnline: typeof import('@uni-helper/uni-use')['useOnline']
+  const usePage: typeof import('@uni-helper/uni-use')['usePage']
+  const usePageScroll: typeof import('@uni-helper/uni-use')['usePageScroll']
+  const usePages: typeof import('@uni-helper/uni-use')['usePages']
+  const usePreferredDark: typeof import('@uni-helper/uni-use')['usePreferredDark']
+  const usePreferredLanguage: typeof import('@uni-helper/uni-use')['usePreferredLanguage']
+  const usePrevPage: typeof import('@uni-helper/uni-use')['usePrevPage']
+  const usePrevRoute: typeof import('@uni-helper/uni-use')['usePrevRoute']
+  const useProvider: typeof import('@uni-helper/uni-use')['useProvider']
+  const useRequest: typeof import('@uni-helper/uni-use')['useRequest']
+  const useRoute: typeof import('@uni-helper/uni-use')['useRoute']
+  const useRouter: typeof import('@uni-helper/uni-use')['useRouter']
+  const useScanCode: typeof import('@uni-helper/uni-use')['useScanCode']
+  const useScreenBrightness: typeof import('@uni-helper/uni-use')['useScreenBrightness']
+  const useSelectorQuery: typeof import('@uni-helper/uni-use')['useSelectorQuery']
+  const useSlots: typeof import('vue')['useSlots']
+  const useSocket: typeof import('@uni-helper/uni-use')['useSocket']
+  const useStorage: typeof import('@uni-helper/uni-use')['useStorage']
+  const useStorageAsync: typeof import('@uni-helper/uni-use')['useStorageAsync']
+  const useStorageSync: typeof import('@uni-helper/uni-use')['useStorageSync']
+  const useToast: typeof import('@uni-helper/uni-use')['useToast']
+  const useUploadFile: typeof import('@uni-helper/uni-use')['useUploadFile']
+  const useVisible: typeof import('@uni-helper/uni-use')['useVisible']
+  const watch: typeof import('vue')['watch']
+  const watchEffect: typeof import('vue')['watchEffect']
+  const watchPostEffect: typeof import('vue')['watchPostEffect']
+  const watchSyncEffect: typeof import('vue')['watchSyncEffect']
+}
+// for type re-export
+declare global {
+  // @ts-ignore
+  export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
+  import('vue')
+}

BIN
elevator-ui/bak/src/components/.DS_Store


+ 39 - 0
elevator-ui/bak/src/components/FloatFixedAddBtn/FloatFixedAddBtn.vue

@@ -0,0 +1,39 @@
+<script setup>
+import { useUserStore } from '@/store/user'
+
+defineProps({
+  bottom: {
+    type: [String, Number],
+    default: '100'
+  }
+})
+const emit = defineEmits(['click'])
+
+const hasBtnPermission = computed(() => {
+  const { isWeibao } = storeToRefs(useUserStore())
+  return isWeibao.value
+})
+</script>
+
+<template>
+  <view
+    v-show="hasBtnPermission"
+    w-120
+    h-120
+    rounded-full
+    right-50
+    z-100
+    shadow
+    f-c-c
+    flex-col
+    position-fixed
+    bg="#4395D5"
+    :style="{
+      bottom: bottom + 'rpx'
+    }"
+    @click="emit('click')"
+  >
+    <up-icon name="plus" size="36" :color="`${'#FFFFFF'}`" />
+    <text color="#FFFFFF" mt-4>新建</text>
+  </view>
+</template>

+ 729 - 0
elevator-ui/bak/src/components/HandwrittenSignature/HandwrittenSignature.vue

@@ -0,0 +1,729 @@
+<template>
+	<view class="lime-signature" v-if="show" :style="[canvasStyle, styles]" ref="limeSignature">
+		<!-- #ifndef APP-VUE || APP-NVUE -->
+		<canvas v-if="useCanvas2d" class="lime-signature__canvas" :id="canvasId" type="2d"
+			:disableScroll="disableScroll" @touchstart="touchStart" @touchmove="touchMove"
+			@touchend="touchEnd"></canvas>
+		<canvas v-else :disableScroll="disableScroll" class="lime-signature__canvas" :canvas-id="canvasId"
+			:id="canvasId" :width="canvasWidth" :height="canvasHeight" @touchstart="touchStart" @touchmove="touchMove"
+			@touchend="touchEnd" @mousedown="touchStart" @mousemove="touchMove" @mouseup="touchEnd"></canvas>
+		<canvas v-if="showOffscreen" class="offscreen" canvas-id="offscreen" id="offscreen"
+			:style="'width:' + offscreenSize[0] + 'px;height:' + offscreenSize[1] + 'px'" :width="offscreenSize[0]"
+			:height="offscreenSize[1]">
+		</canvas>
+		<view v-if="showMask" class="mask" @touchstart="touchStart" @touchmove.stop.prevent="touchMove"
+			@touchend="touchEnd"></view>
+		<!-- #endif -->
+		<!-- #ifdef APP-VUE -->
+		<view :id="canvasId" :disableScroll="disableScroll" :rparam="param" :change:rparam="sign.update"
+			:rclear="rclear" :change:rclear="sign.clear" :rundo="rundo" :rredo="rredo" :change:rredo="sign.redo"
+			:change:rundo="sign.undo" :rsave="rsave" :rmask="rmask" :change:rsave="sign.save" :change:rmask="sign.mask"
+			:rdestroy="rdestroy" :change:rdestroy="sign.destroy" :rempty="rempty" :change:rempty="sign.isEmpty">
+		</view>
+		<!-- #endif -->
+		<!-- #ifdef APP-NVUE -->
+		<web-view src="/uni_modules/lime-signature/hybrid/html/index.html" class="lime-signature__canvas" ref="webview"
+			@pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage"></web-view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	// #ifndef APP-NVUE
+	import {
+		canIUseCanvas2d,
+		wrapEvent,
+		requestAnimationFrame,
+		sleep,
+		isTransparent
+	} from './utils'
+	import {
+		Signature
+	} from './signature.js'
+	// import {Signature} from '@signature';
+	import {
+		uniContext,
+		createImage,
+		toDataURL
+	} from './context'
+	// #endif
+	import props from './props';
+	import {
+		base64ToPath,
+		getRect
+	} from './utils'
+import { nextTick } from 'vue';
+
+	/**
+	 * LimeSignature 手写板签名
+	 * @description 手写板签名插件:一款能跑在uniapp各端中的签名插件,支持横屏、背景色、笔画颜色、笔画大小等功能,可生成有内容的区域,减小图片尺寸,节省空间。
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=4354
+	 * @property {Number} penSize 画笔大小
+	 * @property {Number} minLineWidth 线条最小宽
+	 * @property {Number} maxLineWidth 线条最大宽 
+	 * @property {String} penColor 画笔颜色 
+	 * @property {String} backgroundColor 背景颜色,不填则为透明
+	 * @property {type} 指定 canvas 类型
+	 * @value 2d canvas 2d 
+	 * @value '' 非 canvas 2d 旧接口,微信不再维护
+	 * @property {Boolean} openSmooth 模拟笔锋 
+	 * @property {Number} beforeDelay 延时初始化,在放在弹窗里可以使用 (毫秒)  
+	 * @property {Number} maxHistoryLength 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能 
+	 * @property {Boolean} landscape 横屏,使用后在最后生成图片时会图片旋转90度
+	 * @property {Boolean} disableScroll 当在写字时,禁止屏幕滚动以及下拉刷新,nvue无效
+	 * @property {Boolean} boundingBox 只生成内容区域,即未画部分不生成,有性能的损耗
+	 */
+	export default {
+		props,
+		data() {
+			return {
+				canvasWidth: null,
+				canvasHeight: null,
+				offscreenWidth: null,
+				offscreenHeight: null,
+				useCanvas2d: true,
+				show: true,
+				offscreenStyles: '',
+				showMask: false,
+				showOffscreen: false,
+				isPC: false,
+				// #ifdef APP-PLUS
+				rclear: 0,
+				rdestroy: 0,
+				rundo: 0,
+				rredo: 0,
+				rsave: JSON.stringify({
+					n: 0,
+					fileType: 'png',
+					quality: 1,
+					destWidth: 0,
+					destHeight: 0,
+				}),
+				rmask: JSON.stringify({
+					n: 0,
+					destWidth: 0,
+					destHeight: 0,
+				}),
+				rempty: 0,
+				risEmpty: true,
+				toDataURL: null,
+				tempFilePath: [],
+				// #endif
+			}
+		},
+		computed: {
+			canvasId() {
+				// #ifdef VUE2
+				return `lime-signature${this._uid}`
+				// #endif
+				// #ifdef VUE3
+				return `lime-signature${this._.uid}`
+				// #endif
+			},
+			offscreenId() {
+				return this.canvasId + 'offscreen'
+			},
+			offscreenSize() {
+				const {
+					offscreenWidth,
+					offscreenHeight
+				} = this
+				return this.landscape ? [offscreenHeight, offscreenWidth] : [offscreenWidth, offscreenHeight]
+			},
+			canvasStyle() {
+				const {
+					canvasWidth,
+					canvasHeight,
+					backgroundColor
+				} = this
+				return {
+					width: canvasWidth && (canvasWidth + 'px'),
+					height: canvasHeight && (canvasHeight + 'px'),
+					background: backgroundColor
+				}
+			},
+			param() {
+				const {
+					penColor,
+					penSize,
+					backgroundColor,
+					backgroundImage,
+					landscape,
+					boundingBox,
+					openSmooth,
+					minLineWidth,
+					maxLineWidth,
+					minSpeed,
+					maxWidthDiffRate,
+					maxHistoryLength,
+					disableScroll,
+					disabled
+				} = this
+				return JSON.parse(JSON.stringify({
+					penColor,
+					penSize,
+					backgroundColor,
+					backgroundImage,
+					landscape,
+					boundingBox,
+					openSmooth,
+					minLineWidth,
+					maxLineWidth,
+					minSpeed,
+					maxWidthDiffRate,
+					maxHistoryLength,
+					disableScroll,
+					disabled
+				}))
+			}
+		},
+		// #ifdef APP-NVUE
+		watch: {
+			param(v) {
+				this.$refs.webview.evalJS(`update(${JSON.stringify(v)})`)
+			}
+		},
+		// #endif
+		// #ifndef APP-PLUS
+		created() {
+			const {
+				platform
+			} = uni.getSystemInfoSync()
+			this.isPC = /windows|mac/.test(platform)
+			this.useCanvas2d = this.type == '2d' && canIUseCanvas2d() && !this.isPC
+			// #ifndef H5
+			this.showMask = this.isPC
+			// #endif
+
+		},
+		// #endif
+		// #ifndef APP-PLUS
+		async mounted() {
+			if (this.beforeDelay) {
+				await sleep(this.beforeDelay)
+			}
+			const config = await this.getContext()
+			this.signature = new Signature(config)
+			this.canvasEl = this.signature.canvas.get('el')
+			this.offscreenWidth = this.canvasWidth = this.signature.canvas.get('width')
+			this.offscreenHeight = this.canvasHeight = this.signature.canvas.get('height')
+
+			this.stopWatch = this.$watch('param', (v) => {
+				this.signature.pen.setOption(v)
+			}, {
+				immediate: true
+			})
+		},
+		// #endif
+		// #ifndef APP-PLUS
+		// #ifdef VUE3
+		beforeUnmount() {
+			this.stopWatch && this.stopWatch()
+			this.signature.destroy()
+			this.signature = null
+			this.show = false;
+			// #ifdef APP-VUE || APP-NVUE
+			this.rdestroy++
+			// #endif
+		},
+		// #endif
+		// #ifdef VUE2
+		beforeDestroy() {
+			this.stopWatch && this.stopWatch()
+			this.signature.destroy()
+			this.show = false;
+			this.signature = null
+			// #ifdef APP-VUE || APP-NVUE
+			this.rdestroy++
+			// #endif
+		},
+		// #endif
+		// #endif
+		methods: {
+			// #ifdef MP-QQ
+			// toJSON() { return this },
+			// #endif
+			// #ifdef APP-PLUS
+			onPageFinish() {
+				this.$refs.webview.evalJS(`update(${JSON.stringify(this.param)})`)
+			},
+			onMessage(e = {}) {
+				const {
+					detail: {
+						data: [res]
+					}
+				} = e
+				if (res.event?.save) {
+					this.toDataURL = res.event.save
+				}
+				if (res.event?.changeSize) {
+					const {
+						width,
+						height
+					} = res.event.changeSize
+				}
+				if (res.event.hasOwnProperty('isEmpty')) {
+					this.risEmpty = res.event.isEmpty
+				}
+				if (res.event?.file) {
+					this.tempFilePath.push(res.event.file)
+					if (this.tempFilePath.length > 7) {
+						this.tempFilePath.shift()
+					}
+					return
+				}
+				if (res.event?.success) {
+					if (res.event.success) {
+						this.tempFilePath.push(res.event.success)
+						if (this.tempFilePath.length > 8) {
+							this.tempFilePath.shift()
+						}
+						this.toDataURL = this.tempFilePath.join('')
+						this.tempFilePath = []
+					} else {
+						this.$emit('fail', 'canvas no data')
+					}
+					return
+				}
+			},
+			// #endif
+			redo() {
+				// #ifdef APP-VUE || APP-NVUE
+				this.rredo += 1
+				// #endif
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`redo()`)
+				// #endif
+				// #ifndef APP-VUE
+				if (this.signature)
+					this.signature.redo()
+				// #endif
+			},
+			restore() {
+				this.redo()
+			},
+			undo() {
+				// #ifdef APP-VUE || APP-NVUE
+				this.rundo += 1
+				// #endif
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`undo()`)
+				// #endif
+				// #ifndef APP-VUE
+				if (this.signature)
+					this.signature.undo()
+				// #endif
+			},
+			clear() {
+				// #ifdef APP-VUE || APP-NVUE
+				this.rclear += 1
+				// #endif
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`clear()`)
+				// #endif
+				// #ifndef APP-VUE
+				if (this.signature)
+					this.signature.clear()
+				// #endif
+			},
+			isEmpty() {
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`isEmpty()`)
+				// #endif
+				// #ifdef APP-VUE || APP-NVUE
+				this.rempty += 1
+				// #endif
+				// #ifndef APP-VUE || APP-NVUE
+				return this.signature.isEmpty()
+				// #endif
+			},
+			async canvasToMaskPath(param = {}) {
+				const isEmpty = this.isEmpty()
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`mask(${JSON.stringify(param)})`)
+				// #endif
+				// #ifdef APP-VUE || APP-NVUE
+				const stopURLWatch = this.$watch('toDataURL', (v, n) => {
+					if (v && v !== n) {
+						// if(param.pathType == 'url') {
+						base64ToPath(v).then(res => {
+							param.success({
+								tempFilePath: res,
+								isEmpty: this.risEmpty
+							})
+						})
+						// } else {
+						// 	param.success({tempFilePath: v,isEmpty: this.risEmpty })
+						// }
+						this.toDataURL = ''
+					}
+					stopURLWatch && stopURLWatch()
+				})
+				const {
+					fileType,
+					quality
+				} = param
+				const rmask = JSON.parse(this.rmask)
+				rmask.n++
+				rmask.destWidth = param.destWidth ?? 0
+				rmask.destHeight = param.destHeight ?? 0
+				// rmask.fileType = fileType
+				// rmask.quality = quality
+				this.rmask = JSON.stringify(rmask)
+				// #endif
+				// #ifndef APP-VUE || APP-NVUE
+				this.showOffscreen = true
+				
+				let width = this.signature.canvas.get('width')
+				let height = this.signature.canvas.get('height')
+				let {
+					pixelRatio
+				} = uni.getSystemInfoSync()
+				if (this.useCanvas2d) {
+					this.offscreenWidth = width * pixelRatio
+					this.offscreenHeight = height * pixelRatio
+				} else {
+					this.offscreenWidth = width
+					this.offscreenHeight = height
+				}
+				await sleep(100)
+				const context = uni.createCanvasContext('offscreen', this)
+				const size = Math.max(this.offscreenWidth, this.offscreenHeight)
+				const success = (success) => param.success && param.success(success)
+				const fail = (fail) => param.fail && param.fail(fail)
+
+				this.signature.pen.getMaskedImageData((imageData) => {
+					let canvasPutImageData = (options, comp) => {
+						if (uni.canvasPutImageData) {
+							uni.canvasPutImageData(options, comp)
+						} else if (context.putImageData) {
+							context.putImageData(options)
+						}
+					}
+					canvasPutImageData({
+						canvasId: 'offscreen',
+						x: 0,
+						y: 0,
+						width: width,
+						height:height,
+						data: imageData,
+						fail(err) {
+							fail(err)
+						},
+						success: (re) => {
+							toDataURL('offscreen', this, param).then((res) => {
+								context.restore()
+								context.clearRect(0, 0, size, size)
+								this.offscreenWidth = width
+								this.offscreenHeight = height
+								this.showOffscreen = false
+								success({
+									tempFilePath: res,
+									isEmpty
+								})
+							})
+						}
+					}, this)
+					
+				})
+				// #endif
+			},
+			canvasToTempFilePath(param = {}) {
+				
+				const isEmpty = this.isEmpty()
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`save(${JSON.stringify(param)})`)
+				// #endif
+				// #ifdef APP-VUE || APP-NVUE
+				const stopURLWatch = this.$watch('toDataURL', (v, n) => {
+					if (v && v !== n) {
+						if (this.preferToDataURL) {
+							param.success({
+								tempFilePath: v,
+								isEmpty: this.risEmpty
+							})
+						} else {
+							base64ToPath(v).then(res => {
+								param.success({
+									tempFilePath: res,
+									isEmpty: this.risEmpty
+								})
+							})
+						}
+						this.toDataURL = ''
+					}
+					stopURLWatch && stopURLWatch()
+				})
+				const {
+					fileType,
+					quality
+				} = param
+				const rsave = JSON.parse(this.rsave)
+				rsave.n++
+				rsave.fileType = fileType
+				rsave.quality = quality
+				rsave.destWidth = param.destWidth ?? 0
+				rsave.destHeight = param.destHeight ?? 0
+				this.rsave = JSON.stringify(rsave)
+				// #endif
+				// #ifndef APP-VUE || APP-NVUE
+				const useCanvas2d = this.useCanvas2d
+				const success = (success) => param.success && param.success(success)
+				const fail = (err) => param.fail && param.fail(err)
+				const {
+					canvas
+				} = this.signature.canvas.get('el')
+				const {
+					backgroundColor,
+					landscape,
+					boundingBox
+				} = this
+				let width = this.signature.canvas.get('width')
+				let height = this.signature.canvas.get('height')
+				let x = 0
+				let y = 0
+				const devtools = uni.getSystemInfoSync().platform == 'devtools'
+				let preferToDataURL = this.preferToDataURL
+				let scale = 1
+				// #ifdef MP-TOUTIAO
+				scale = devtools ? uni.getSystemInfoSync().pixelRatio : scale
+				// 由于抖音不支持canvasToTempFilePath故优先使用createOffscreenCanvas
+				preferToDataURL = true
+				// #endif
+				const canvasToTempFilePath = async (image) => {
+					const createCanvasContext = () => {
+						const useOffscreen = (useCanvas2d && !!uni.createOffscreenCanvas && preferToDataURL)
+						if (useOffscreen && !devtools) {
+							const offCanvas = uni.createOffscreenCanvas({
+								type: '2d'
+							});
+							offCanvas.width = this.offscreenSize[0] * scale
+							offCanvas.height = this.offscreenSize[1] * scale
+							const context = offCanvas.getContext("2d");
+							return [context, offCanvas]
+						} else {
+							const context = uni.createCanvasContext('offscreen', this)
+							return [context]
+						}
+					}
+
+					if (boundingBox && !this.isPC || landscape || backgroundColor && !isTransparent(backgroundColor)) {
+						
+						this.showOffscreen = true
+						await sleep(100)
+						const [context, offCanvas] = createCanvasContext()
+						context.save()
+						context.setTransform(1, 0, 0, 1, 0, 0)
+						if (landscape) {
+							context.translate(0, width * scale)
+							context.rotate(-Math.PI / 2)
+						}
+						if (backgroundColor && !isTransparent(backgroundColor)) {
+							context.fillStyle = backgroundColor
+							context.fillRect(0, 0, width, height)
+						}
+						if (offCanvas) {
+							const img = canvas.createImage();
+							img.src = image
+							img.onload = () => {
+								context.drawImage(img, 0, 0, width * scale, height * scale);
+								const tempFilePath = offCanvas.toDataURL()
+								this.showOffscreen = false
+								success({
+									tempFilePath,
+									isEmpty
+								})
+							}
+
+						} else {
+							context.drawImage(image, 0, 0, width * scale, height * scale);
+							context.draw(false, () => {
+								toDataURL('offscreen', this, param).then((res) => {
+									const size = Math.max(width, height)
+									context.restore()
+									context.clearRect(0, 0, size, size)
+									this.showOffscreen = false
+									success({
+										tempFilePath: res,
+										isEmpty
+									})
+								})
+							})
+						}
+					} else {
+						success({
+							tempFilePath: image,
+							isEmpty
+						})
+					}
+				}
+				const next = async () => {
+					if (this.offscreenWidth != width || this.offscreenHeight != height) {
+						this.offscreenWidth = width
+						this.offscreenHeight = height
+						await sleep(100)
+					}
+
+					// #ifndef MP-WEIXIN
+					const param = {
+						x,
+						y,
+						width,
+						height,
+						canvas,
+						preferToDataURL
+					}
+					// #endif
+
+					// #ifdef MP-WEIXIN
+					const param = {
+						x,
+						y,
+						width,
+						height,
+						canvas: useCanvas2d ? canvas : null,
+						preferToDataURL
+					}
+					// #endif
+					toDataURL(this.canvasId, this, param).then(canvasToTempFilePath).catch(fail)
+				}
+				// PC端小程序获取不到 ImageData 数据,长度为0
+				if (boundingBox && !this.isPC) {
+					this.signature.getContentBoundingBox(async res => {
+						this.offscreenWidth = width = res.width
+						this.offscreenHeight = height = res.height
+
+						x = res.startX
+						y = res.startY
+
+						next()
+					})
+				} else {
+					next()
+				}
+				// #endif
+			},
+			// #ifndef APP-PLUS
+			getContext() {
+				return getRect(`#${this.canvasId}`, {
+					context: this,
+					type: this.useCanvas2d ? 'fields' : 'boundingClientRect'
+				}).then(res => {
+					if (res) {
+						let {
+							width,
+							height,
+							node: canvas,
+							left,
+							top,
+							right
+						} = res
+						let {
+							pixelRatio
+						} = uni.getSystemInfoSync()
+						let context;
+						if (canvas) {
+							context = canvas.getContext('2d')
+							canvas.width = width * pixelRatio;
+							canvas.height = height * pixelRatio;
+						} else {
+							pixelRatio = 1
+							context = uniContext(this.canvasId, this)
+							canvas = {
+								getContext: (type) => type == '2d' ? context : null,
+								createImage,
+								toDataURL: () => toDataURL(this.canvasId, this),
+								requestAnimationFrame
+							}
+						}
+						// 支付宝小程序 使用stroke有个默认背景色
+						context.clearRect(0, 0, width, height)
+						return {
+							left,
+							top,
+							right,
+							width,
+							height,
+							context,
+							canvas,
+							pixelRatio
+						};
+					}
+				})
+			},
+			getTouch(e) {
+				if (this.isPC && this.canvasRect) {
+					e.touches = e.touches.map(item => {
+						return {
+							...item,
+							x: item.clientX - this.canvasRect.left,
+							y: item.clientY - this.canvasRect.top,
+						}
+					})
+				}
+				return e
+			},
+			touchStart(e) {
+				if (!this.canvasEl) return
+				this.isStart = true
+				// 微信小程序PC端不支持事件,使用这方法模拟一下
+				if (this.isPC) {
+					getRect(`#${this.canvasId}`, {
+						context: this
+					}).then(res => {
+						this.canvasRect = res
+						this.canvasEl.dispatchEvent('touchstart', wrapEvent(this.getTouch(e)))
+					})
+					return
+				}
+				this.canvasEl.dispatchEvent('touchstart', wrapEvent(e))
+			},
+			touchMove(e) {
+				if (!this.canvasEl || !this.isStart && this.canvasEl) return
+				this.canvasEl.dispatchEvent('touchmove', wrapEvent(this.getTouch(e)))
+			},
+			touchEnd(e) {
+				if (!this.canvasEl) return
+				this.isStart = false
+				this.canvasEl.dispatchEvent('touchend', wrapEvent(e))
+			},
+			// #endif
+		}
+	}
+</script>
+<!-- #ifdef APP-VUE -->
+<script module="sign" lang="renderjs">
+	import sign from './render'
+	export default sign
+</script>
+<!-- #endif -->
+<style lang="scss">
+	.lime-signature,
+	.lime-signature__canvas {
+		/* #ifndef APP-NVUE */
+		position: relative;
+		width: 100%;
+		height: 100%;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		flex: 1;
+		/* #endif */
+	}
+
+	.mask {
+		position: absolute;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		top: 0;
+	}
+
+	.offscreen {
+		position: fixed;
+		top: 0;
+		// left: 0;
+		pointer-events:none;
+		// background: rgba(0,255,0,0.5);
+		left: 9999px;
+	}
+</style>

+ 199 - 0
elevator-ui/bak/src/components/HandwrittenSignature/context.js

@@ -0,0 +1,199 @@
+const uniPlatform = uni.getSystemInfoSync().uniPlatform
+
+export const uniContext = (canvasId, context) => {
+	let ctx = uni.createCanvasContext(canvasId, context)
+	if (!ctx.uniDrawImage) {
+		ctx.uniDrawImage = ctx.drawImage
+		ctx.drawImage = (image, ...agrs) => {
+			ctx.uniDrawImage(image.src, ...agrs)
+		}
+	}
+
+	if (!ctx.getImageData) {
+		ctx.getImageData = (x, y, width, height) => {
+			return new Promise((resolve, reject) => {
+				// #ifdef MP || VUE2
+				if (context.proxy) context = context.proxy
+				// #endif
+				uni.canvasGetImageData({
+					canvasId,
+					x,
+					y,
+					width:parseInt(width),
+					height:parseInt(height),
+					success(res) {
+						resolve(res)
+					},
+					fail(error) {
+						reject(error)
+					}
+				}, context)
+			})
+		}
+	} else {
+		ctx._getImageData = ctx.getImageData
+		ctx.getImageData = (x, y, width, height) => {
+			return new Promise((resolve, reject) => {
+				ctx._getImageData({
+					x,
+					y,
+					width: parseInt(width) ,
+					height:parseInt(height),
+					success(res) {
+						resolve(res)
+					},
+					fail(error) {
+						reject(error)
+					}
+				})
+			})
+		}
+	}
+
+	return ctx
+}
+
+class Image {
+	constructor() {
+		this.currentSrc = null
+		this.naturalHeight = 0
+		this.naturalWidth = 0
+		this.width = 0
+		this.height = 0
+		this.tagName = 'IMG'
+	}
+	onerror() {}
+	onload() {}
+	set src(src) {
+		this.currentSrc = src
+		uni.getImageInfo({
+			src,
+			success: (res) => {
+				this.naturalWidth = this.width = res.width
+				this.naturalHeight = this.height = res.height
+				this.onload()
+			},
+			fail: () => {
+				this.onerror()
+			}
+		})
+	}
+	get src() {
+		return this.currentSrc
+	}
+}
+
+export const createImage = () => {
+	return new Image()
+}
+export function useCurrentPage() {
+	const pages = getCurrentPages();
+	return pages[pages.length - 1];
+}
+export const toDataURL = (canvasId, context, options = {}) => {
+	// #ifdef MP-QQ
+	// context = context.$scope
+	// #endif
+	// #ifdef MP-ALIPAY
+	context = ''
+	// #endif
+
+	return new Promise((resolve, reject) => {
+		let {
+			canvas,
+			width,
+			height,
+			destWidth = 0,
+			destHeight = 0,
+			x = 0,
+			y = 0,
+			preferToDataURL
+		} = options
+		const {
+			pixelRatio
+		} = uni.getSystemInfoSync()
+		
+		// #ifdef MP-ALIPAY
+		const isDD = typeof dd != 'undefined'
+		if (!isDD && (!destWidth || !destHeight)) {
+			destWidth = width * pixelRatio;
+			destHeight = height * pixelRatio;
+			width = destWidth;
+			height = destHeight;
+			x = x * pixelRatio
+			y = y * pixelRatio
+		}
+		// #endif
+		const params = {
+			...options,
+			canvasId,
+			id: canvasId,
+			// #ifdef MP-ALIPAY
+			x,
+			y,
+			width,
+			height,
+			destWidth,
+			destHeight,
+			// #endif
+			canvas,
+			success: (res) => {
+				resolve(res.tempFilePath)
+			},
+			fail: (err) => {
+				reject(err)
+			}
+		}
+		// 抖音小程序canvas 2d不支持canvasToTempFilePath
+		if (canvas && canvas.toDataURL && preferToDataURL) {
+			let next = true
+			const devtools = uni.getSystemInfoSync().platform == 'devtools'
+			// #ifdef MP-TOUTIAO
+			next = uni.getSystemInfoSync().platform != 'devtools'
+			if (!next) {
+				console.warn('[lime-signature] 抖音开发工具不支持bbox')
+			}
+			// #endif
+			if ((x || y) && next) {
+				const offCanvas = uni.createOffscreenCanvas({
+					type: '2d'
+				});
+				const ctx = offCanvas.getContext("2d");
+				const destWidth = Math.floor(width * pixelRatio)
+				const destHeight = Math.floor(height * pixelRatio)
+				offCanvas.width = destWidth // canvas.width;
+				offCanvas.height = destHeight // canvas.height;
+				// ctx.scale(pixelRatio, pixelRatio)
+				// ctx.drawImage(canvas, Math.floor(x*pixelRatio), Math.floor(y*pixelRatio), destWidth, destHeight, 0,0, destWidth, destHeight);
+				// 抖音不能在drawImage使用canvas
+				const image = canvas.createImage()
+				image.onload = () => {
+					ctx.drawImage(image, Math.floor(x * pixelRatio), Math.floor(y * pixelRatio),
+						destWidth, destHeight, 0, 0, destWidth, destHeight)
+					const tempFilePath = offCanvas.toDataURL();
+					resolve(tempFilePath)
+					if (params.success) {
+						params.success({
+							tempFilePath
+						})
+					}
+				}
+				image.src = canvas.toDataURL()
+
+			} else {
+				const tempFilePath = canvas.toDataURL()
+				resolve(tempFilePath)
+				if (params.success) {
+					params.success({
+						tempFilePath
+					})
+				}
+			}
+		} else if (canvas && canvas.toTempFilePath) {
+			canvas.toTempFilePath(params)
+		} else {
+			uni.canvasToTempFilePath(params, context)
+		}
+	})
+
+}

+ 64 - 0
elevator-ui/bak/src/components/HandwrittenSignature/props.js

@@ -0,0 +1,64 @@
+export default {
+	styles: String,
+	disableScroll: {
+		type: Boolean,
+		default: true
+	},
+	type: {
+		type: String,
+		default: '2d'
+	},
+	// 画笔颜色
+	penColor: {
+		type: String,
+		default: 'black'
+	},
+	penSize: {
+		type: Number,
+		default: 2
+	},
+	// 画板背景颜色
+	backgroundColor: String,
+	backgroundImage: String,
+	// 笔锋
+	openSmooth: Boolean,
+	// 画笔最小值
+	minLineWidth: {
+		type: Number,
+		default: 2
+	},
+	// 画笔最大值
+	maxLineWidth: {
+		type: Number,
+		default: 6
+	},
+	// 画笔达到最小宽度所需最小速度(px/ms),取值范围1.0-10.0,值越小,画笔越容易变细,笔锋效果会比较明显,可以自行调整查看效果,选出自己满意的值。
+	minSpeed: {
+		type: Number,
+		default: 1.5
+	},
+	// 相邻两线宽度增(减)量最大百分比,取值范围1-100,为了达到笔锋效果,画笔宽度会随画笔速度而改变,如果相邻两线宽度差太大,过渡效果就会很突兀,使用maxWidthDiffRate限制宽度差,让过渡效果更自然。可以自行调整查看效果,选出自己满意的值。
+	maxWidthDiffRate: {
+		type: Number,
+		default: 20
+	},
+	// 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能
+	maxHistoryLength: {
+		type: Number,
+		default: 20
+	},
+	beforeDelay: {
+		type: Number,
+		default: 0
+	},
+	landscape: {
+		type: Boolean
+	},
+	boundingBox: {
+		type: Boolean
+	},
+	disabled: {
+		type: Boolean
+	},
+	preferToDataURL: Boolean
+}

+ 228 - 0
elevator-ui/bak/src/components/HandwrittenSignature/render.js

@@ -0,0 +1,228 @@
+// #ifdef APP-VUE 
+// import { Signature } from '@signature'
+import {
+	Signature
+} from './signature.js'
+import {
+	isTransparent
+} from './utils'
+export default {
+	data() {
+		return {
+			canvasid: null,
+			signature: null,
+			observer: null,
+			options: {},
+			saveCount: 0,
+		}
+	},
+	mounted() {
+		this.$nextTick(this.init)
+	},
+	methods: {
+		init() {
+			const el = this.$refs.limeSignature || this.$ownerInstance.$el;
+			this.canvas = document.createElement('canvas')
+			this.canvas.style = 'width: 100%; height: 100%;'
+			el.appendChild(this.canvas)
+			this.signature = new Signature({
+				el: this.canvas
+			})
+			this.signature.pen.setOption(this.options)
+			const width = this.signature.canvas.get('width')
+			const height = this.signature.canvas.get('height')
+
+			this.emit({
+				changeSize: {
+					width,
+					height
+				}
+			})
+		},
+		redo(v) {
+			if (v && this.signature) {
+				this.signature.redo()
+			}
+		},
+		undo(v) {
+			if (v && this.signature) {
+				this.signature.undo()
+			}
+		},
+		clear(v) {
+			if (v && this.signature) {
+				this.signature.clear()
+			}
+		},
+		destroy() {
+			if (this.canvas) {
+				this.canvas.remove()
+			}
+		},
+		mask(param={}) {
+			if (this.signature) {
+				let {destWidth=0, destHeight=0} = JSON.parse(param)
+				let canvas = document.createElement('canvas')
+				const ctx = canvas.getContext('2d');
+				const pixelRatio = this.signature.canvas.get('pixelRatio')
+				let width = this.signature.canvas.get('width')
+				let height = this.signature.canvas.get('height')
+				let context = this.signature.canvas.get('context')
+				canvas.width = width * pixelRatio
+				canvas.height = height * pixelRatio
+
+				const imageData = context.getImageData(0, 0, width * pixelRatio, height * pixelRatio);
+				for (let i = 0; i < imageData.data.length; i += 4) {
+					// 判断当前像素是否透明
+					const isTransparent = imageData.data[i + 3] === 0;
+				
+					if (isTransparent) {
+						// 将透明像素设置为黑色背景
+						imageData.data[i] = 0;
+						imageData.data[i + 1] = 0;
+						imageData.data[i + 2] = 0;
+					} else {
+						// 将非透明像素设置为白色内容
+						imageData.data[i] = 255;
+						imageData.data[i + 1] = 255;
+						imageData.data[i + 2] = 255;
+					}
+				}
+				ctx.putImageData(imageData, 0, 0);
+				if(destWidth&&destHeight){
+					const _canvas = document.createElement('canvas')
+					_canvas.width = destWidth
+					_canvas.height = destHeight
+					const _context = _canvas.getContext('2d')
+					_context.drawImage(canvas, 0, 0, destWidth, destHeight)
+					canvas.remove()
+					canvas = _canvas	
+				}
+				this.emit({
+					save: canvas.toDataURL()
+				})
+				canvas.remove()
+			}
+
+		},
+		save(param) {
+			let {
+				fileType = 'png', 
+				quality = 1, 
+				n,
+				destWidth = 0,
+				destHeight = 0,
+			} = JSON.parse(param)
+			const type = `image/${fileType}`.replace(/jpg/, 'jpeg');
+			if (n !== this.saveCount) {
+				this.saveCount = n;
+				const {
+					backgroundColor,
+					backgroundImage,
+					landscape,
+					boundingBox
+				} = this.options
+				const flag = landscape || backgroundColor || boundingBox||destWidth&&destHeight
+				const image = this.signature.canvas.get('el').toDataURL(!flag && type, !flag && quality)
+				if (flag) {
+					let canvas = document.createElement('canvas')
+					const pixelRatio = this.signature.canvas.get('pixelRatio')
+					let width = this.signature.canvas.get('width')
+					let height = this.signature.canvas.get('height')
+					let x = 0
+					let y = 0
+
+					const next = () => {
+						const size = [width, height]
+						if (landscape) {
+							size.reverse()
+						}
+						canvas.width =  size[0] * pixelRatio
+						canvas.height = size[1] * pixelRatio
+						const param = [x, y, width, height, 0, 0, width, height].map(item => item * pixelRatio)
+						const context = canvas.getContext('2d')
+						if (landscape) {
+							context.translate(0, width * pixelRatio)
+							context.rotate(-Math.PI / 2)
+						}
+						if (backgroundColor && !isTransparent(backgroundColor)) {
+							context.fillStyle = backgroundColor
+							context.fillRect(0, 0, width * pixelRatio, height * pixelRatio)
+						}
+						const drawImage = () => {
+							// param
+							context.drawImage(this.signature.canvas.get('el'), ...param)
+							if(destWidth&&destHeight){
+								const _canvas = document.createElement('canvas')
+								_canvas.width = destWidth
+								_canvas.height = destHeight
+								const _context = _canvas.getContext('2d')
+								_context.drawImage(canvas, 0, 0, destWidth, destHeight)
+								canvas.remove()
+								canvas = _canvas	
+							}
+							this.emit({
+								save: canvas.toDataURL(type, quality)
+							})
+							canvas.remove()
+						}
+						if (backgroundImage) {
+							const img = new Image();
+							img.onload = () => {
+								context.drawImage(img, ...param)
+								drawImage()
+							}
+							img.src = backgroundImage
+						}
+						if (!backgroundImage) {
+							drawImage()
+						}
+					}
+					if (boundingBox) {
+						const res = this.signature.getContentBoundingBox()
+						width = res.width
+						height = res.height
+						x = res.startX
+						y = res.startY
+						next()
+					} else {
+						next()
+					}
+
+				} else {
+					this.emit({
+						save: image
+					})
+				}
+			}
+		},
+		isEmpty(v) {
+			if (v && this.signature) {
+				const isEmpty = this.signature.isEmpty()
+				this.emit({
+					isEmpty
+				})
+			}
+		},
+		emit(event) {
+			this.$ownerInstance.callMethod('onMessage', {
+				detail: {
+					data: [{
+						event
+					}]
+				}
+			})
+		},
+		update(v) {
+			if (v) {
+				if (this.signature) {
+					this.options = v
+					this.signature.pen.setOption(v)
+				} else {
+					this.options = v
+				}
+			}
+		}
+	}
+}
+// #endif

Різницю між файлами не показано, бо вона завелика
+ 1 - 0
elevator-ui/bak/src/components/HandwrittenSignature/signature.js


+ 181 - 0
elevator-ui/bak/src/components/HandwrittenSignature/utils.js

@@ -0,0 +1,181 @@
+export function compareVersion(v1, v2) {
+	v1 = v1.split('.')
+	v2 = v2.split('.')
+	const len = Math.max(v1.length, v2.length)
+	while (v1.length < len) {
+		v1.push('0')
+	}
+	while (v2.length < len) {
+		v2.push('0')
+	}
+	for (let i = 0; i < len; i++) {
+		const num1 = parseInt(v1[i], 10)
+		const num2 = parseInt(v2[i], 10)
+
+		if (num1 > num2) {
+			return 1
+		} else if (num1 < num2) {
+			return -1
+		}
+	}
+	return 0
+}
+
+function gte(version) {
+	let { SDKVersion } = uni.getSystemInfoSync() 
+  // #ifdef MP-ALIPAY
+  SDKVersion = my.SDKVersion
+  // #endif
+  return compareVersion(SDKVersion, version) >= 0;
+}
+
+export function canIUseCanvas2d() {
+	// #ifdef MP-WEIXIN
+	return gte('2.9.0');
+	// #endif
+	// #ifdef MP-ALIPAY
+	return gte('2.7.0');
+	// #endif
+	// #ifdef MP-TOUTIAO
+	return gte('1.78.0');
+	// #endif
+	return false
+}
+
+
+export const wrapEvent = (e) => {
+  if (!e) return;
+  if (!e.preventDefault) {
+    e.preventDefault = function() {};
+  }
+  return e;
+}
+
+export const requestAnimationFrame = (cb) => {
+	setTimeout(cb, 30)
+}
+
+// #ifdef MP
+export const prefix = () => {
+	// #ifdef MP-TOUTIAO
+	return tt
+	// #endif
+	// #ifdef MP-WEIXIN
+	return wx
+	// #endif
+	// #ifdef MP-BAIDU
+	return swan
+	// #endif
+	// #ifdef MP-ALIPAY
+	return my
+	// #endif
+	// #ifdef MP-QQ
+	return qq
+	// #endif
+	// #ifdef MP-360
+	return qh
+	// #endif
+}
+// #endif
+
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64) {
+	const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
+	return new Promise((resolve, reject) => {
+		// #ifdef MP
+		const p = prefix()
+		const fs = p.getFileSystemManager()
+		//自定义文件名
+		if (!format) {
+			reject(new Error('ERROR_BASE64SRC_PARSE'))
+		}
+		const time = new Date().getTime();
+		const filePath = `${p.env.USER_DATA_PATH}/${time}.${format}`;
+		fs.writeFile({
+			filePath,
+			data: base64.split(',')[1],
+			encoding: 'base64',
+			success() {
+				resolve(filePath)
+			},
+			fail(err) {
+				reject(err)
+			}
+		})
+		// #endif
+		// #ifdef APP-PLUS
+		const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
+		bitmap.loadBase64Data(base64, () => {
+			if (!format) {
+				reject(new Error('ERROR_BASE64SRC_PARSE'))
+			}
+			const time = new Date().getTime();
+			const filePath = `_doc/uniapp_temp/${time}.${format}`
+			bitmap.save(filePath, {},
+				() => {
+					bitmap.clear()
+					resolve(filePath)
+				},
+				(error) => {
+					bitmap.clear()
+					reject(error)
+				})
+		}, (error) => {
+			bitmap.clear()
+			reject(error)
+		})
+		// #endif
+	})
+}
+
+
+export function sleep(delay) {
+	return new Promise(resolve => setTimeout(resolve, delay))
+}
+
+export function getRect(selector, options = {}) {
+	const typeDefault = 'boundingClientRect'
+	const { context, type = typeDefault} = options
+	return new Promise((resolve, reject) => {
+		const dom = uni.createSelectorQuery().in(context).select(selector);
+		const result = (rect) => {
+			if(rect) {
+				 resolve(rect)
+			} else {
+				reject()
+			}
+		}
+		if(type == typeDefault) {
+			dom[type](result).exec()
+		} else {
+			dom[type]({
+				node: true,
+				size: true,
+				rect: true
+			}, result).exec()
+		}
+	});
+};
+
+export function isTransparent(color) {
+  // 判断颜色是否为 transparent
+  if (color === 'transparent') {
+    return true;
+  }
+
+  // 判断颜色是否为 rgba 的 a 为 0
+  if (color.startsWith('rgba')) {
+    const regex = /\d+(\.\d+)?/g;
+    const matches = color.match(regex);
+    if (matches !== null) {
+      const alpha = parseFloat(matches[3]);
+      if (alpha === 0) {
+        return true;
+      }
+    }
+  }
+  return false;
+}

+ 39 - 0
elevator-ui/bak/src/components/UserAvatar/UserAvatar.vue

@@ -0,0 +1,39 @@
+<script setup>
+import { useUserStore } from '@/store/user'
+
+const props = defineProps({
+  size: {
+    type: Number,
+    default: 40
+  },
+  bgColor: {
+    type: String,
+    default: '#96CCF6'
+  }
+})
+
+const emit = defineEmits(['tap'])
+const userStore = useUserStore()
+const { getUserInfo } = storeToRefs(userStore)
+const userInfo = computed(() => getUserInfo.value)
+// 取昵称的第一个字
+const nickName = computed(() => {
+  if (userInfo.value.nickName) {
+    return userInfo.value.nickName[0]
+  }
+  return ''
+})
+const handle = () => {
+  emit('tap')
+}
+</script>
+
+<template>
+  <up-avatar
+    :src="userInfo.avatar"
+    :text="userInfo.avatar ? '' : nickName"
+    :size="size"
+    :bg-color="bgColor"
+    @tap="handle"
+  />
+</template>

+ 23 - 0
elevator-ui/bak/src/composables/setting/env.js

@@ -0,0 +1,23 @@
+export const useGlobSetting = () => {
+  const {
+    VITE_GLOB_ENABLE_ENCRYPT,
+    VITE_GLOB_RSA_PUBLIC_KEY,
+    VITE_GLOB_RSA_PRIVATE_KEY,
+    VITE_GLOB_APP_CLIENT_ID,
+    VITE_GLOB_WEBSOCKET_ENABLE
+  } = import.meta.env
+
+  // Take global configuration
+  const glob = {
+    enableEncrypt: VITE_GLOB_ENABLE_ENCRYPT === 'true',
+    // RSA公钥
+    rsaPublicKey: VITE_GLOB_RSA_PUBLIC_KEY,
+    // RSA私钥
+    rsaPrivateKey: VITE_GLOB_RSA_PRIVATE_KEY,
+    // 客户端key
+    clientId: VITE_GLOB_APP_CLIENT_ID,
+    // 是否开启websocket
+    websocketEnable: VITE_GLOB_WEBSOCKET_ENABLE === 'true'
+  }
+  return glob
+}

+ 9 - 0
elevator-ui/bak/src/composables/use-cdn.js

@@ -0,0 +1,9 @@
+import config from '@/config'
+const { cdn } = config
+
+export const useCdn = (prefix = '') => {
+  const cdnUrl = `${cdn}${prefix}`
+  return {
+    cdnUrl
+  }
+}

+ 50 - 0
elevator-ui/bak/src/composables/use-fetch.js

@@ -0,0 +1,50 @@
+import { deepClone } from 'uview-plus'
+
+export const useFetch = (options = {}) => {
+  const {
+    apiFn,
+    params = {},
+    isList = false,
+    isAutoFetch = false,
+    defaultResult = {},
+    formatResult = (r) => r
+  } = options
+
+  const result = ref(defaultResult)
+  const loading = ref(false)
+
+  if (!apiFn) {
+    throw new Error('apiFn is required')
+  }
+
+  const fetchApi = async (...rest) => {
+    loading.value = true
+    const { error, data, result: response } = await apiFn(...rest)
+    const isOk = !error || isList
+
+    if (!isOk) {
+      loading.value = false
+      return [isOk, error]
+    }
+
+    let formatResultData = deepClone(defaultResult)
+    if (isList) {
+      formatResultData = formatResult(response)
+    } else {
+      formatResultData = formatResult(data)
+    }
+    result.value = formatResultData
+    loading.value = false
+    return [isOk, formatResultData]
+  }
+
+  if (isAutoFetch) {
+    fetchApi(params)
+  }
+
+  return {
+    result,
+    fetchApi,
+    loading
+  }
+}

+ 13 - 0
elevator-ui/bak/src/composables/use-list-scroll.js

@@ -0,0 +1,13 @@
+export const usePageScroll = (scrollChange) => {
+  scrollChange()()
+  // 创建响应式数据 scrollTop
+  const scrollTop = ref(0)
+
+  onPageScroll((e) => {
+    scrollTop.value = e.scrollTop
+  })
+
+  return {
+    scrollTop
+  }
+}

+ 12 - 0
elevator-ui/bak/src/composables/use-page-scroll.js

@@ -0,0 +1,12 @@
+export const usePageScroll = () => {
+  // 创建响应式数据 scrollTop
+  const scrollTop = ref(0)
+
+  onPageScroll((e) => {
+    scrollTop.value = e.scrollTop
+  })
+
+  return {
+    scrollTop
+  }
+}

+ 71 - 0
elevator-ui/bak/src/composables/use-paging.js

@@ -0,0 +1,71 @@
+export const usePaging = (options = {}) => {
+  const { formatResult = (r) => r } = options
+  const { query: enterQuery } = toRefs(options)
+  const { apiFn } = options
+  const paging = ref(null)
+  const dataList = ref([])
+  const dataTotal = ref(0)
+  const query = ref({})
+
+  watchEffect(() => {
+    if (enterQuery) {
+      query.value = {
+        ...query.value,
+        ...enterQuery.value
+      }
+    } else {
+      query.value = {}
+    }
+  })
+
+  const queryHandler = async (pageNum = 1, pageSize = 10, from, enterQuery) => {
+    const params = enterQuery || query.value
+    const { error, result } = await apiFn({
+      pageNum,
+      pageSize,
+      ...params
+    })
+    if (error) {
+      paging.value.complete(false)
+    } else {
+      const { rows, total } = result
+      const list = formatResult(rows)
+      paging.value.complete(list)
+      dataTotal.value = total
+    }
+  }
+
+  const clearHandler = () => {
+    clear()
+    reload()
+  }
+
+  const enterHandler = () => {
+    clearHandler()
+  }
+
+  const reload = () => {
+    paging.value.reload()
+  }
+
+  const clear = () => {
+    paging.value.clear()
+  }
+
+  const scrollToTop = () => {
+    paging.value.scrollToTop()
+  }
+
+  return {
+    paging,
+    query,
+    dataList,
+    dataTotal,
+    queryHandler,
+    clearHandler,
+    enterHandler,
+    reload,
+    clear,
+    scrollToTop
+  }
+}

+ 66 - 0
elevator-ui/bak/src/composables/use-uni-api.js

@@ -0,0 +1,66 @@
+// import uniPages from 'uni-pages?{"type":"style"}'
+import { isObject, isString } from 'utils/data-type'
+import pages from '../pages.json'
+
+export const useUniApi = () => {
+  /**
+   * 跳转页面
+   * @param {Object | String} options 跳转参数
+   */
+  const navigateTo = (options) => {
+    if (!options) {
+      return
+    }
+
+    const { list } = pages.tabBar
+    const paths = list.map((item) => `/${item.pagePath}`)
+
+    if (isObject(options)) {
+      const { url } = options
+      if (paths.includes(url)) {
+        uni.switchTab(options)
+      } else {
+        uni.navigateTo(options)
+      }
+    } else if (isString(options)) {
+      if (paths.includes(options)) {
+        uni.switchTab({
+          url: options
+        })
+      } else {
+        uni.navigateTo({
+          url: options
+        })
+      }
+    }
+  }
+
+  /**
+   * 预览图片
+   * @param {String} url 图片地址
+   */
+  const previewImage = (url) => {
+    if (!url) {
+      return
+    }
+    uni.previewImage({
+      urls: [url]
+    })
+  }
+
+  // 扫码
+  const scanCode = () => {
+    return new Promise((resolve, reject) => {
+      uni.scanCode({
+        success: resolve,
+        fail: reject
+      })
+    })
+  }
+
+  return {
+    navigateTo,
+    previewImage,
+    scanCode
+  }
+}

+ 46 - 0
elevator-ui/bak/src/composables/use-upload.js

@@ -0,0 +1,46 @@
+import { useGlobSetting } from 'hooks/setting/env'
+import config from '@/config'
+import { ACCESS_TOKEN_KEY } from '@/constants/storage'
+
+export const useUpload = () => {
+  const { api, prefix } = config
+  const baseURL = api + prefix
+  const { clientId } = useGlobSetting()
+
+  const uploadFile = (tempFilePaths, cb) => {
+    const [tempFilePath] = tempFilePaths
+    const authorization = uni.getStorageSync(ACCESS_TOKEN_KEY)
+    uni.uploadFile({
+      url: `${baseURL}/resource/oss/upload`,
+      filePath: tempFilePath,
+      name: 'file',
+      formData: {},
+      header: {
+        Authorization: `Bearer ${authorization}`,
+        Clientid: clientId
+      },
+      success: ({ data }) => {
+        let result = {}
+        try {
+          result = JSON.parse(data)
+        } catch (error) {
+          result = {}
+        }
+        if (result.code === 401) {
+          uni.$u.toast('登录失效,请登录后重试')
+          setTimeout(() => {
+            uni.reLaunch({
+              url: '/pages/views/login/index'
+            })
+          }, 1500)
+        } else {
+          cb && cb(result.data)
+        }
+      }
+    })
+  }
+
+  return {
+    uploadFile
+  }
+}

+ 28 - 0
elevator-ui/bak/src/config/index.js

@@ -0,0 +1,28 @@
+// 通过编译时的环境变量,来区分请求域名为测试/生产
+const { MODE } = import.meta.env
+
+// 公用配置
+let config = {
+  // api: 'http://111.172.230.93:9876', // 本地测试
+  api: 'https://api.hbrja.com', // 本地测试
+  // api: 'http://31c84bb0.r36.cpolar.top ', // 内网
+  cdn: 'https://minio.hbrja.com',
+  prefix: ''
+}
+
+// 环境区分
+if (MODE === 'development') {
+  // 测试环境
+  config = {
+    ...config,
+    secretKey: ''
+  }
+} else if (MODE === 'production') {
+  // 生产环境
+  config = {
+    ...config,
+    secretKey: ''
+  }
+}
+
+export default config

+ 25 - 0
elevator-ui/bak/src/constants/state.js

@@ -0,0 +1,25 @@
+// 操作类型
+export const operationTypeMap = {
+  1: '新建',
+  2: '接收',
+  3: '到场',
+  4: '完成',
+  7: '评价',
+  8: '不通过',
+  9: '已终止'
+}
+
+// 1:半月 2:季度 3:半年 4:年度
+export const planTypeMap = {
+  1: '半月',
+  2: '季度',
+  3: '半年',
+  4: '年度'
+}
+
+// 咨询类型 1:救援 2:维修 3:维保
+export const consultTypeMap = {
+  1: '救援',
+  2: '维修',
+  3: '维保'
+}

+ 3 - 0
elevator-ui/bak/src/constants/storage.js

@@ -0,0 +1,3 @@
+export const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN_KEY'
+
+export const USER_LOGIN_ACCOUNT = 'USER_LOGIN_ACCOUNT'

+ 21 - 0
elevator-ui/bak/src/constants/work.js

@@ -0,0 +1,21 @@
+/**
+ * 管理员 tabs
+ * 未完成:stateList: [1,2,3]
+ * 已完成:stateList: [4,7]
+ */
+export const adminTabs = [
+  { name: '未完成', stateList: '1,2,3', state: 3 },
+  { name: '已完成', stateList: '4,7', state: 4 }
+]
+
+/**
+ * 维保角色 tabs
+ * 未接单:stateList: [1]
+ * 进行中:stateList: [2,3]
+ * 已完成:stateList: [4,7]
+ */
+export const maintainTabs = [
+  { name: '未接单', stateList: '1', state: 1 },
+  { name: '进行中', stateList: '2,3', state: 2 },
+  { name: '已完成', stateList: '4,7', state: 4 }
+]

+ 9 - 0
elevator-ui/bak/src/helper/async.js

@@ -0,0 +1,9 @@
+export const toAsyncAwait = (fn, defaultResult = null) => {
+  return fn
+    .then((res) => [true, res])
+    .catch((err) => [false, defaultResult || err])
+}
+
+export const tryFormValidate = (form) => {
+  return toAsyncAwait(form.value.validate())
+}

+ 19 - 0
elevator-ui/bak/src/main.js

@@ -0,0 +1,19 @@
+import 'core-js/actual/array/iterator'
+import 'core-js/actual/promise'
+import 'core-js/actual/object/assign'
+import 'core-js/actual/promise/finally'
+import 'uno.css'
+import { createPinia } from 'pinia'
+import uviewPlus from 'uview-plus'
+import { createSSRApp } from 'vue'
+import App from './App.vue'
+
+export function createApp() {
+  const app = createSSRApp(App)
+  const pinia = createPinia()
+  app.use(pinia)
+  app.use(uviewPlus)
+  return {
+    app
+  }
+}

+ 70 - 0
elevator-ui/bak/src/manifest.json

@@ -0,0 +1,70 @@
+{
+  "name": "",
+  "appid": "",
+  "description": "",
+  "versionName": "1.0.0",
+  "versionCode": "100",
+  "transformPx": false,
+  "app-plus": {
+    "usingComponents": true,
+    "nvueStyleCompiler": "uni-app",
+    "compilerVersion": 3,
+    "splashscreen": {
+      "alwaysShowBeforeRender": true,
+      "waiting": true,
+      "autoclose": true,
+      "delay": 0
+    },
+    "modules": {},
+    "distribute": {
+      "android": {
+        "permissions": [
+          "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+          "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+          "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+          "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+          "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+          "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+          "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+          "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+          "<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\"/>"
+        ]
+      },
+      "ios": {},
+      "sdkConfigs": {}
+    }
+  },
+  "quickapp": {},
+  "mp-weixin": {
+    "appid": "wxff99592fd1b5d95b",
+    "requiredPrivateInfos": ["getLocation", "chooseLocation"],
+    "permission": {
+      "scope.userLocation": {
+        "desc": "你的位置信息将用于小程序位置接口的效果展示"
+      }
+    },
+    "setting": {
+      "urlCheck": false
+    },
+    "usingComponents": true
+  },
+  "mp-alipay": {
+    "usingComponents": true
+  },
+  "mp-baidu": {
+    "usingComponents": true
+  },
+  "mp-toutiao": {
+    "usingComponents": true
+  },
+  "uniStatistics": {
+    "enable": false
+  },
+  "vueVersion": "3"
+}

+ 529 - 0
elevator-ui/bak/src/pages.json

@@ -0,0 +1,529 @@
+{
+  "easycom": {
+    "autoscan": true,
+    "custom": {
+      "^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
+      "^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
+      "^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue"
+    }
+  },
+  "pages": [
+    {
+      "path": "pages/index/index",
+      "style": {
+        "navigationBarTitleText": ""
+      }
+    },
+    {
+      "path": "pages/redirect/index",
+      "style": {
+        "navigationBarTitleText": "加载中"
+      }
+    },
+    {
+      "path": "pages/redirect/elevator",
+      "style": {
+        "navigationBarTitleText": "加载中"
+      }
+    },
+    {
+      "path": "pages/tabbar/message/index",
+      "style": {
+        "navigationBarTitleText": "消息"
+      }
+    },
+    {
+      "path": "pages/tabbar/work/index",
+      "style": {
+        "navigationBarTitleText": "工作"
+      }
+    },
+    {
+      "path": "pages/tabbar/mine/index",
+      "style": {
+        "navigationBarTitleText": "我的",
+        "navigationStyle": "custom"
+      }
+    }
+  ],
+  "subPackages": [
+    // 主要页面
+    {
+      "root": "pages/views",
+      "pages": [
+        {
+          "path": "login/index",
+          "style": {
+            "navigationBarTitleText": "登录"
+          }
+        },
+        {
+          "path": "login/password",
+          "style": {
+            "navigationBarTitleText": "登录"
+          }
+        },
+        {
+          "path": "no-permission/index",
+          "style": {
+            "navigationBarTitleText": "无权限"
+          }
+        },
+        {
+          "path": "web-view/index",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "test/index",
+          "style": {
+            "navigationBarTitleText": "测试页面"
+          }
+        },
+        {
+          "path": "system-message/index",
+          "style": {
+            "navigationBarTitleText": "系统消息"
+          }
+        },
+        {
+          "path": "elevator-info/index",
+          "style": {
+            "navigationBarTitleText": "电梯信息"
+          }
+        },
+        {
+          "path": "choose-elevator/index",
+          "style": {
+            "navigationBarTitleText": "选择电梯"
+          }
+        },
+        {
+          "path": "choose-worker/index",
+          "style": {
+            "navigationBarTitleText": "选择协同人员"
+          }
+        },
+        {
+          "path": "elevator-data/index",
+          "style": {
+            "navigationBarTitleText": "电梯资料"
+          }
+        },
+        {
+          "path": "basic-info/index",
+          "style": {
+            "navigationBarTitleText": "电梯资料"
+          }
+        },
+        {
+          "path": "basic-info/feedback",
+          "style": {
+            "navigationBarTitleText": "乘客意见"
+          }
+        },
+        {
+          "path": "elevator-rescue/index",
+          "style": {
+            "navigationBarTitleText": "救援"
+          }
+        },
+        {
+          "path": "elevator-rescue/record",
+          "style": {
+            "navigationBarTitleText": "救援记录"
+          }
+        },
+        {
+          "path": "elevator-rescue/detail",
+          "style": {
+            "navigationBarTitleText": "救援详情"
+          }
+        },
+        {
+          "path": "elevator-rescue/create",
+          "style": {
+            "navigationBarTitleText": "创建救援"
+          }
+        },
+        {
+          "path": "elevator-rescue/event-detail",
+          "style": {
+            "navigationBarTitleText": "事件详情"
+          }
+        },
+        {
+          "path": "elevator-rescue/undone-detail",
+          "style": {
+            "navigationBarTitleText": "处理工作"
+          }
+        },
+        {
+          "path": "elevator-rescue/not-started",
+          "style": {
+            "navigationBarTitleText": "接单"
+          }
+        },
+        {
+          "path": "elevator-rescue/user-signature",
+          "style": {
+            "navigationBarTitleText": "完成"
+          }
+        },
+        {
+          "path": "elevator-maintenance/index",
+          "style": {
+            "navigationBarTitleText": "维保"
+          }
+        },
+        {
+          "path": "elevator-maintenance/record",
+          "style": {
+            "navigationBarTitleText": "维保记录"
+          }
+        },
+        {
+          "path": "elevator-maintenance/plan",
+          "style": {
+            "navigationBarTitleText": "维保计划"
+          }
+        },
+        {
+          "path": "elevator-maintenance/create",
+          "style": {
+            "navigationBarTitleText": "新建维保"
+          }
+        },
+        {
+          "path": "elevator-maintenance/detail",
+          "style": {
+            "navigationBarTitleText": "工作详情"
+          }
+        },
+        {
+          "path": "elevator-maintenance/work-sheet",
+          "style": {
+            "navigationBarTitleText": "工作表"
+          }
+        },
+        {
+          "path": "elevator-maintenance/evaluation",
+          "style": {
+            "navigationBarTitleText": "评价"
+          }
+        },
+        {
+          "path": "submit-sheet/index",
+          "style": {
+            "navigationBarTitleText": "工作表"
+          }
+        },
+        {
+          "path": "elevator-maintenance/add-record",
+          "style": {
+            "navigationBarTitleText": "添加记录"
+          }
+        },
+        {
+          "path": "elevator-maintenance/user-signature",
+          "style": {
+            "navigationBarTitleText": "签名"
+          }
+        },
+        {
+          "path": "elevator-maintenance/undone-detail",
+          "style": {
+            "navigationBarTitleText": "处理工作"
+          }
+        },
+        {
+          "path": "elevator-maintenance/not-started",
+          "style": {
+            "navigationBarTitleText": "接单"
+          }
+        },
+        {
+          "path": "elevator-maintenance/state-record",
+          "style": {
+            "navigationBarTitleText": "工作"
+          }
+        },
+        {
+          "path": "elevator-maintain/index",
+          "style": {
+            "navigationBarTitleText": "维修"
+          }
+        },
+        {
+          "path": "elevator-maintain/record",
+          "style": {
+            "navigationBarTitleText": "维修记录"
+          }
+        },
+        {
+          "path": "elevator-maintain/create",
+          "style": {
+            "navigationBarTitleText": "新建维修"
+          }
+        },
+        {
+          "path": "elevator-maintain/not-started",
+          "style": {
+            "navigationBarTitleText": "接单"
+          }
+        },
+        {
+          "path": "elevator-maintain/add-record",
+          "style": {
+            "navigationBarTitleText": "接单"
+          }
+        },
+        {
+          "path": "elevator-maintain/undone-detail",
+          "style": {
+            "navigationBarTitleText": "处理工作"
+          }
+        },
+        {
+          "path": "elevator-maintain/event-detail",
+          "style": {
+            "navigationBarTitleText": "事件详情"
+          }
+        },
+        {
+          "path": "elevator-maintain/user-signature",
+          "style": {
+            "navigationBarTitleText": "完成"
+          }
+        },
+        {
+          "path": "running-status/index",
+          "style": {
+            "navigationBarTitleText": "运行状态"
+          }
+        },
+        {
+          "path": "surveillance-camera/choose",
+          "style": {
+            "navigationBarTitleText": "选择电梯"
+          }
+        },
+        {
+          "path": "surveillance-camera/index",
+          "style": {
+            "navigationBarTitleText": "监控"
+          }
+        },
+        {
+          "path": "check-in/index",
+          "style": {
+            "navigationBarTitleText": "签到"
+          }
+        },
+        {
+          "path": "exceed-limit/annual-inspection",
+          "style": {
+            "navigationBarTitleText": "年检超期"
+          }
+        },
+        {
+          "path": "exceed-limit/elevator-maintenance",
+          "style": {
+            "navigationBarTitleText": "维保超期"
+          }
+        },
+        {
+          "path": "passenger-feedback/index",
+          "style": {
+            "navigationBarTitleText": "乘客反馈"
+          }
+        },
+        {
+          "path": "running-chart/index",
+          "style": {
+            "navigationBarTitleText": "运行数据"
+          }
+        },
+        {
+          "path": "elevator-examine/index",
+          "style": {
+            "navigationBarTitleText": "电梯体检报告"
+          }
+        },
+        {
+          "path": "big-screen/index",
+          "style": {
+            "navigationBarTitleText": "监控大屏"
+          }
+        },
+        {
+          "path": "elevator-examine/detail",
+          "style": {
+            "navigationBarTitleText": "基本信息"
+          }
+        },
+        {
+          "path": "history-info/index",
+          "style": {
+            "navigationBarTitleText": "历史信息"
+          }
+        },
+        {
+          "path": "history-work/index",
+          "style": {
+            "navigationBarTitleText": "历史工作"
+          }
+        },
+        {
+          "path": "inspection-plan/index",
+          "style": {
+            "navigationBarTitleText": "年检计划"
+          }
+        },
+        {
+          "path": "elevator-update/index",
+          "style": {
+            "navigationBarTitleText": "添加电梯"
+          }
+        },
+        {
+          "path": "use-help/index",
+          "style": {
+            "navigationBarTitleText": "使用帮助"
+          }
+        },
+        {
+          "path": "user-setting/index",
+          "style": {
+            "navigationBarTitleText": "设置"
+          }
+        },
+        {
+          "path": "update-password/index",
+          "style": {
+            "navigationBarTitleText": "设置"
+          }
+        },
+        {
+          "path": "user-feedback/index",
+          "style": {
+            "navigationBarTitleText": "建议反馈"
+          }
+        },
+        {
+          "path": "about-app/index",
+          "style": {
+            "navigationBarTitleText": "关于"
+          }
+        }
+      ]
+    },
+    // 专家咨询
+    {
+      "root": "pages/expert",
+      "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "order/index",
+          "style": {
+            "navigationBarTitleText": "咨询订单"
+          }
+        },
+        {
+          "path": "mine/index",
+          "style": {
+            "navigationBarTitleText": "我的"
+          }
+        },
+        {
+          "path": "use-help/index",
+          "style": {
+            "navigationBarTitleText": "使用帮助"
+          }
+        },
+        {
+          "path": "order/not-started",
+          "style": {
+            "navigationBarTitleText": "接单"
+          }
+        },
+        {
+          "path": "order/undone-detail",
+          "style": {
+            "navigationBarTitleText": "处理工作"
+          }
+        },
+        {
+          "path": "order/detail",
+          "style": {
+            "navigationBarTitleText": "详情"
+          }
+        },
+        {
+          "path": "apply-order/index",
+          "style": {
+            "navigationBarTitleText": "专家咨询"
+          }
+        },
+        {
+          "path": "apply-order/create",
+          "style": {
+            "navigationBarTitleText": "新建咨询"
+          }
+        },
+        {
+          "path": "apply-order/undone-detail",
+          "style": {
+            "navigationBarTitleText": "处理工作"
+          }
+        },
+        {
+          "path": "apply-order/detail",
+          "style": {
+            "navigationBarTitleText": "详情"
+          }
+        }
+      ]
+    }
+  ],
+  "tabBar": {
+    "color": "#7A7E83",
+    "selectedColor": "#1296db",
+    "borderStyle": "black",
+    "backgroundColor": "#ffffff",
+    "list": [
+      {
+        "pagePath": "pages/tabbar/message/index",
+        "iconPath": "static/tabbar/message-default.png",
+        "selectedIconPath": "static/tabbar/message-active.png",
+        "text": "消息"
+      },
+      {
+        "pagePath": "pages/tabbar/work/index",
+        "iconPath": "static/tabbar/work-default.png",
+        "selectedIconPath": "static/tabbar/work-active.png",
+        "text": "工作"
+      },
+      {
+        "pagePath": "pages/tabbar/mine/index",
+        "iconPath": "static/tabbar/mine-default.png",
+        "selectedIconPath": "static/tabbar/mine-active.png",
+        "text": "我的"
+      }
+    ]
+  },
+
+  "globalStyle": {
+    "navigationBarTextStyle": "black",
+    "navigationBarTitleText": "uni-app",
+    "navigationBarBackgroundColor": "#FFFFFF",
+    "backgroundColor": "#FFFFFF"
+  }
+}

BIN
elevator-ui/bak/src/pages/.DS_Store


+ 31 - 0
elevator-ui/bak/src/pages/components/FloatFixedAddBtn.vue

@@ -0,0 +1,31 @@
+<script setup>
+defineProps({
+  bottom: {
+    type: [String, Number],
+    default: '100'
+  }
+})
+const emit = defineEmits(['click'])
+</script>
+
+<template>
+  <view
+    w-120
+    h-120
+    rounded-full
+    right-50
+    z-100
+    shadow
+    f-c-c
+    flex-col
+    position-fixed
+    bg="#4395D5"
+    :style="{
+      bottom: bottom + 'rpx'
+    }"
+    @click="emit('click')"
+  >
+    <up-icon name="plus" size="36" :color="`${'#FFFFFF'}`" />
+    <text color="#FFFFFF" mt-4>新建</text>
+  </view>
+</template>

+ 73 - 0
elevator-ui/bak/src/pages/components/PositionPicker.vue

@@ -0,0 +1,73 @@
+<script setup>
+import { useVModel } from '@vueuse/core'
+// import StructureCascade from './StructureCascade.vue'
+import StructureCascade from 'components/StructureCascade.vue'
+import { getUserDeptTreeApi } from '@/api/system'
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  }
+})
+const emit = defineEmits(['update:modelValue', 'confirm'])
+const show = useVModel(props, 'modelValue', emit)
+const close = () => {
+  show.value = false
+}
+const strResults = ref([])
+const confirm = () => {
+  emit('confirm', strResults.value)
+  close()
+}
+const open = () => {
+  console.log('open popup')
+}
+
+const treeData = ref([
+  // {
+  //   id: 1,
+  //   name: '一级_1',
+  //   children: [
+  //     {
+  //       id: 2,
+  //       name: '二级_1-1',
+  //       parentId: 1
+  //     },
+  //     {
+  //       id: 3,
+  //       name: '二级_1-2',
+  //       parentId: 1
+  //     }
+  //   ]
+  // }
+])
+
+watch(
+  () => show.value,
+  async (val) => {
+    if (val) {
+      const { data } = await getUserDeptTreeApi()
+      treeData.value = data
+    }
+  }
+)
+</script>
+
+<template>
+  <up-popup :show="show" mode="top" @close="close" @open="open">
+    <view w-full h-auto>
+      <StructureCascade
+        ref="structureCascade"
+        v-model="strResults"
+        top-title="组织架构"
+        value-field="id"
+        label-field="label"
+        :tree-data="treeData"
+      />
+      <view w-full px-20>
+        <up-button text="确认" size="small" type="primary" @tap="confirm" />
+      </view>
+    </view>
+  </up-popup>
+</template>

+ 447 - 0
elevator-ui/bak/src/pages/components/StructureCascade.vue

@@ -0,0 +1,447 @@
+<script>
+export default {
+  name: 'StructureCascade',
+  props: {
+    value: {
+      type: Array,
+      default: () => []
+    },
+    treeData: {
+      type: Array,
+      default: () => []
+    },
+    isRadio: {
+      type: Boolean,
+      default: true
+    },
+    selectMax: {
+      type: Number,
+      default: 0
+    },
+    topTitle: {
+      type: String,
+      default: '组织架构'
+    },
+    valueField: {
+      type: String,
+      default: 'id'
+    },
+    labelField: {
+      type: String,
+      default: 'name'
+    },
+    checkIcon: {
+      type: String,
+      default: ''
+    },
+    moreIcon: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      listData: [],
+      scrollLeft: 0,
+      navTreeList: [],
+      selectedList: [],
+      defaultColor: '#1677ff'
+    }
+  },
+  computed: {
+    navLen() {
+      // 处理动态增减导航项时小程序无法渲染样式问题
+      return this.navTreeList.length
+    }
+  },
+  watch: {
+    topTitle: {
+      handler() {
+        this.navTreeList = [
+          {
+            [this.valueField]: 0,
+            [this.labelField]: this.topTitle
+          }
+        ]
+      },
+      deep: true,
+      immediate: true
+    },
+    treeData: {
+      handler(val) {
+        if (!this.isEmpty(val)) {
+          this.initList()
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    initList() {
+      if (this.isEmpty(this.value)) {
+        this.listData = this.deepCopy(this.treeData)
+      } else {
+        if (this.isRadio) {
+          const topList = this.getParentNodes(this.treeData, this.value[0])
+          if (topList.length) {
+            this.navTreeList.push(...topList)
+            const length = this.navTreeList.length
+            this.listData = this.deepCopy(this.navTreeList[length - 1].children)
+            this.scrollLeft = length * 100
+          } else {
+            this.listData = this.deepCopy(this.treeData)
+          }
+        } else {
+          this.listData = this.deepCopy(this.treeData)
+        }
+        this.selectedList = this.deepCopy(this.value)
+      }
+      this.updateChecked()
+    },
+    updateChecked() {
+      const key = this.valueField
+      const keys = (this.selectedList || []).map((o) => o[key])
+      this.listData.forEach((item) => {
+        if (keys.includes(item[key])) {
+          this.$set(item, 'checked', true)
+        } else {
+          this.$set(item, 'checked', false)
+        }
+      })
+    },
+    handleToSearch(keyword) {
+      if (keyword) {
+        this.listData = this.deepCopy(this.getFilterData(keyword))
+      } else {
+        this.listData = this.deepCopy(this.treeData)
+      }
+    },
+    // 跳转到子项
+    handleToChild(item) {
+      if (!this.isEmpty(item.children)) {
+        this.listData = this.deepCopy(item.children)
+        this.navTreeList.push({
+          [this.valueField]: item[this.valueField],
+          [this.labelField]: item[this.labelField]
+        })
+        this.scrollLeft += 100
+        this.updateChecked()
+      } else {
+        this.handleChoose(item)
+      }
+    },
+    // 导航切换
+    handleNavgiateToDept(index) {
+      if (index >= this.navTreeList.length - 1) {
+        return
+      }
+      this.navTreeList.splice(index + 1)
+      this.calculateFun(this.treeData, this.navTreeList)
+    },
+    calculateFun(tree, arr) {
+      arr = arr.slice(1)
+      if (arr.length) {
+        for (const item of tree) {
+          if (item[this.valueField] === arr[0][this.valueField]) {
+            this.calculateFun(item.children, arr)
+            break
+          }
+        }
+      } else {
+        this.listData = this.deepCopy(tree)
+        this.updateChecked()
+      }
+    },
+    // 切换选中
+    handleChoose(item) {
+      this.$set(item, 'checked', !item.checked)
+      if (this.isRadio) {
+        if (item.checked) {
+          this.selectedList = [
+            {
+              [this.valueField]: item[this.valueField],
+              [this.labelField]: item[this.labelField]
+            }
+          ]
+          this.updateChecked()
+        } else {
+          this.selectedList = []
+        }
+      } else {
+        if (item.checked) {
+          // 勾选
+          if (
+            this.selectMax !== 0 &&
+            this.selectedList.length >= this.selectMax
+          ) {
+            this.selectedList.splice(this.selectMax - 1)
+          }
+          this.selectedList.push({
+            [this.valueField]: item[this.valueField],
+            [this.labelField]: item[this.labelField]
+          })
+          this.updateChecked()
+        } else {
+          // 取消勾选
+          const findIndex = this.selectedList.findIndex(
+            (o) => o[this.valueField] === item[this.valueField]
+          )
+          if (findIndex !== -1) {
+            this.selectedList.splice(findIndex, 1)
+          }
+        }
+      }
+      this.$nextTick(() => {
+        // this.$emit('input', this.selectedList)
+        this.$emit('update:modelValue', this.selectedList)
+      })
+    },
+    isEmpty(data) {
+      if (data === null) {
+        return true
+      }
+      if (data === undefined) {
+        return true
+      }
+      if (Object.prototype.toString.call(data) === '[object Array]') {
+        return data.length === 0
+      }
+      if (Object.prototype.toString.call(data) === '[object Object]') {
+        return Object.keys(data).length === 0
+      }
+      if (typeof data === 'string') {
+        return data.trim() === ''
+      }
+      return false
+    },
+    deepCopy(source) {
+      if (typeof source === 'object') {
+        const sourceCopy = Array.isArray(source) ? [] : {}
+        for (const item in source) {
+          if (!source[item]) {
+            sourceCopy[item] = source[item]
+          } else {
+            sourceCopy[item] =
+              typeof source[item] === 'object'
+                ? this.deepCopy(source[item])
+                : source[item]
+          }
+        }
+        return sourceCopy
+      }
+      return source
+    },
+    // 获取目标节点aim的所有祖宗节点 不包括aim本身
+    getParentNodes(tree, aim) {
+      const temp = [] //结果对象
+      const valueField = this.valueField
+      const forFn = function (arr, key) {
+        for (let i = 0; i < arr.length; i++) {
+          const item = arr[i]
+          if (item[valueField] === key) {
+            forFn(tree, item.parentId)
+            if (item[valueField] !== aim[valueField]) {
+              temp.push(item)
+            }
+            break
+          } else {
+            if (item.children) {
+              forFn(item.children, key)
+            }
+          }
+        }
+      }
+      forFn(tree, aim[this.valueField])
+      return temp
+    },
+    // 根据检索条件返回一维数组
+    getFilterData(keyword, data = this.treeData) {
+      const result = []
+      const valueField = this.valueField
+      const labelField = this.labelField
+      const fn = function (data) {
+        if (Array.isArray(data)) {
+          data.forEach((item) => {
+            if (item[labelField].includes(keyword)) {
+              result.push({
+                [valueField]: item[valueField],
+                [labelField]: item[labelField]
+              })
+            }
+            if (item.children) {
+              fn(item.children)
+            }
+          })
+        }
+      }
+      fn(data)
+      return result
+    }
+  }
+}
+</script>
+
+<template>
+  <view class="container">
+    <view class="header">
+      <scroll-view
+        class="nav-tree"
+        scroll-x
+        enable-flex="true"
+        :scroll-left="scrollLeft"
+      >
+        <view
+          v-for="(item, index) in navTreeList"
+          :key="item[valueField]"
+          class="nav-tree-item"
+        >
+          <text
+            :class="{ special: index < navLen - 1 }"
+            class="text"
+            @click="handleNavgiateToDept(index)"
+          >
+            {{ item[labelField] }}
+          </text>
+          <!-- <text v-if="index < navLen - 1" class="right-text">></text> -->
+          <view v-if="index < navLen - 1" class="right-text">
+            <up-icon name="arrow-right" />
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+    <view class="content">
+      <view class="dept-list">
+        <view
+          v-for="item in listData"
+          :key="item[valueField]"
+          class="dept-list-item"
+          @click="handleToChild(item)"
+        >
+          <up-icon
+            v-if="item.checked"
+            name="checkmark-circle-fill"
+            :size="18"
+            :color="defaultColor"
+            @click.stop="handleChoose(item)"
+          />
+          <view v-else class="checkbox" @click.stop="handleChoose(item)" />
+          <view class="text">{{ item[labelField] }}</view>
+
+          <up-icon v-if="!isEmpty(item.children)" name="arrow-right" />
+        </view>
+      </view>
+    </view>
+    <view class="bottom-gap" />
+  </view>
+</template>
+
+<style lang="scss" scoped>
+.container {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+
+  .header {
+    width: 100%;
+    padding: 30rpx 36rpx;
+    background-color: white;
+
+    .nav-tree {
+      width: 678rpx;
+      height: 40rpx;
+      display: flex;
+      white-space: nowrap;
+
+      .nav-tree-item {
+        flex-shrink: 0;
+        font-size: 28rpx;
+        color: #333333;
+        display: flex;
+        align-items: center;
+
+        .special {
+          color: #1677ff;
+        }
+
+        .right-text {
+          color: #333333;
+          margin: 0 10rpx;
+        }
+
+        .right-icon {
+          width: 36rpx;
+          height: 36rpx;
+          margin: 0 10rpx;
+        }
+      }
+    }
+  }
+
+  .content {
+    padding: 0 36rpx;
+    background-color: white;
+
+    .dept-list {
+      display: flex;
+      flex-direction: column;
+
+      .dept-list-item {
+        font-size: 28rpx;
+        padding: 30rpx 0;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        border-top: 1rpx solid #eeeeee;
+
+        .checkbox {
+          width: 36rpx;
+          height: 36rpx;
+          text-align: center;
+          border: 4rpx solid #a5adba;
+          border-radius: 50%;
+          display: inline-flex;
+          align-items: center;
+          justify-content: center;
+          box-sizing: border-box;
+
+          &.active {
+            border-color: #1677ff;
+            background-color: #1677ff;
+          }
+        }
+
+        .check-icon {
+          width: 36rpx;
+          height: 36rpx;
+        }
+
+        .text {
+          flex: 1;
+          margin-left: 36rpx;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+        }
+
+        .more-icon {
+          width: 40rpx;
+          height: 40rpx;
+          margin-right: 15rpx;
+        }
+
+        .more-text {
+          color: #919191;
+        }
+      }
+    }
+  }
+
+  .bottom-gap {
+    width: 100%;
+    height: 200rpx;
+  }
+}
+</style>

BIN
elevator-ui/bak/src/pages/expert/apply-order/.DS_Store


+ 35 - 0
elevator-ui/bak/src/pages/expert/apply-order/add-record.vue

@@ -0,0 +1,35 @@
+<script setup>
+const content = ref('')
+
+const instance = getCurrentInstance().proxy
+const eventChannel = instance.getOpenerEventChannel()
+const submit = () => {
+  console.log('提交')
+  uni.navigateBack()
+
+  eventChannel.emit('done', {})
+}
+</script>
+
+<template>
+  <view px-28 mt-20>
+    <up-textarea
+      v-model="content"
+      placeholder="请输入..."
+      maxlength="300"
+      count
+      height="150"
+    />
+  </view>
+
+  <view position="fixed" w-full bottom-36 f-c-c>
+    <up-button
+      type="primary"
+      text="完成"
+      :custom-style="{
+        width: '650rpx'
+      }"
+      @click="submit"
+    />
+  </view>
+</template>

+ 245 - 0
elevator-ui/bak/src/pages/expert/apply-order/create.vue

@@ -0,0 +1,245 @@
+<script setup>
+import dayjs from 'dayjs'
+import { getElevatorInfoApi } from '@/api/elevator'
+import { postExpertConsultApi } from '@/api/expert'
+import { tryFormValidate } from '@/helper/async'
+
+const elevatorList = ref([])
+const elevatorInfo = ref({})
+const getElevatorInfo = async () => {
+  const { error, data } = await getElevatorInfoApi(options.value)
+  if (error) {
+    return
+  }
+  elevatorInfo.value = data
+  elevatorList.value = [
+    {
+      id: data.id,
+      name: data.elevatorName
+    }
+  ]
+}
+
+const showAction = ref(false)
+const actionSelect = (e) => {
+  console.log('actionSelect', e)
+}
+const actions = [
+  { name: '半月', value: 1 },
+  { name: '季度', value: 2 },
+  { name: '半年', value: 3 },
+  { name: '年度', value: 4 }
+]
+const hideKeyboard = () => {
+  uni.hideKeyboard()
+}
+
+// 线上援助 线下援助
+const helpGroups = ref([
+  {
+    name: '线上援助',
+    value: 1
+  },
+  {
+    name: '线下援助',
+    value: 2
+  }
+])
+const expectModeChange = (name) => {
+  const { value } = helpGroups.value.find((item) => item.name === name)
+  model.value.expectMode = value
+  model.value.expectModeName = name
+}
+
+// consultType 维保类型
+const consultTypeGroups = ref([
+  {
+    name: '救援',
+    value: 1
+  },
+  {
+    name: '维修',
+    value: 2
+  },
+  {
+    name: '维保',
+    value: 3
+  }
+])
+const consultTypeChange = (name) => {
+  const { value } = consultTypeGroups.value.find((item) => item.name === name)
+  model.value.consultType = value
+  model.value.consultTypeName = name
+}
+
+const showCalendar = ref(false)
+const calendarConfirm = ([time]) => {
+  console.log('calendarConfirm', time)
+  showCalendar.value = false
+  model.value.expectTime = time
+}
+const calendarClose = () => {
+  showCalendar.value = false
+  form.value.validateField('expectTime')
+}
+
+const form = ref(null)
+const model = ref({
+  // 1.救援 2.维修 3.维保
+  consultType: 3,
+  consultTypeName: '维保',
+  expectMode: 2,
+  expectModeName: '线下援助',
+  expectTime: dayjs().format('YYYY-MM-DD'),
+  consultDesc: ''
+})
+const rules = ref({
+  expectTime: {
+    type: 'string',
+    required: true,
+    message: '请选择期望时间',
+    trigger: ['change']
+  }
+})
+
+const submit = async () => {
+  const [isOk] = await tryFormValidate(form)
+  if (isOk) {
+    console.log(model.value)
+    const { error } = await postExpertConsultApi({
+      ...model.value,
+      elevatorList: elevatorList.value
+    })
+    if (!error) {
+      uni.$u.toast('提交成功')
+      uni.navigateBack({
+        delta: 2
+      })
+    } else {
+      uni.$u.toast('提交失败,请稍后重试')
+    }
+  } else {
+    uni.$u.toast('校验失败')
+  }
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+  getElevatorInfo()
+})
+</script>
+
+<template>
+  <!-- 基本信息 -->
+  <view my-20 px-30>
+    <view text-40 text-bold mt-20>{{ elevatorInfo.elevatorName }}</view>
+    <view f-s-c mt-8>
+      <up-icon name="map-fill" :size="24" :color="`${'#579BF7'}`" />
+      <text text-28 font-400 ml-4>{{ elevatorInfo.elevatorAddress }}</text>
+    </view>
+  </view>
+
+  <view px-28 mt-20>
+    <up-form
+      ref="form"
+      label-width="80"
+      label-position="left"
+      :model="model"
+      :rules="rules"
+    >
+      <up-form-item label="工作选项" prop="workType" border-bottom>
+        <text>专家咨询</text>
+      </up-form-item>
+      <up-form-item label="工作类型" prop="consultType" border-bottom>
+        <up-radio-group
+          v-model="model.consultTypeName"
+          placement="row"
+          @change="consultTypeChange"
+        >
+          <up-radio
+            v-for="(item, index) in consultTypeGroups"
+            :key="index"
+            :custom-style="{ marginRight: '16px' }"
+            :label="item.name"
+            :name="item.name"
+          />
+        </up-radio-group>
+      </up-form-item>
+      <up-form-item label="援助方式" prop="expectMode" border-bottom>
+        <up-radio-group
+          v-model="model.expectModeName"
+          placement="row"
+          @change="expectModeChange"
+        >
+          <up-radio
+            v-for="(item, index) in helpGroups"
+            :key="index"
+            :custom-style="{ marginRight: '16px' }"
+            :label="item.name"
+            :name="item.name"
+          />
+        </up-radio-group>
+      </up-form-item>
+      <up-form-item
+        label="期望时间"
+        prop="expectTime"
+        label-width="80"
+        border-bottom
+        @click="
+          () => {
+            showCalendar = true
+            hideKeyboard()
+          }
+        "
+      >
+        <up-input
+          v-model="model.expectTime"
+          disabled
+          disabled-color="#ffffff"
+          placeholder="请选择期望时间"
+          border="none"
+        />
+        <template #right>
+          <up-icon name="arrow-right" />
+        </template>
+      </up-form-item>
+      <up-form-item
+        label="工作描述:"
+        prop="consultDesc"
+        label-position="top"
+        :border="`${'none'}`"
+      >
+        <up-textarea
+          v-model="model.consultDesc"
+          placeholder="请输入工作描述"
+          maxlength="300"
+          count
+          height="150"
+        />
+      </up-form-item>
+    </up-form>
+
+    <up-button
+      type="primary"
+      text="确认创建"
+      custom-style="margin-top: 30px"
+      @click="submit"
+    />
+  </view>
+
+  <up-action-sheet
+    :show="showAction"
+    :actions="actions"
+    title="维保选项"
+    @close="showAction = false"
+    @select="actionSelect"
+  />
+
+  <up-calendar
+    :show="showCalendar"
+    confirm-disabled-text="请选择日期"
+    @confirm="calendarConfirm"
+    @close="calendarClose"
+  />
+</template>

+ 211 - 0
elevator-ui/bak/src/pages/expert/apply-order/detail.vue

@@ -0,0 +1,211 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import {
+  getExpertConsultInfoApi,
+  postExpertConsultAuditApi
+} from '@/api/expert'
+import { planTypeMap, consultTypeMap } from '@/constants/state'
+const { previewImage } = useUniApi()
+
+const dataDetail = ref({})
+const getWorkDataInfo = async () => {
+  const { error, data } = await getExpertConsultInfoApi(options.value)
+  if (!error) {
+    dataDetail.value = data
+  }
+}
+onMounted(() => {
+  getWorkDataInfo()
+})
+
+const operationStateMap = {
+  2: '接收',
+  3: '到场',
+  4: '完成',
+  7: '评价'
+}
+const actionRecord = ref([])
+watchEffect(() => {
+  const { operationRecords } = dataDetail.value
+
+  if (operationRecords) {
+    actionRecord.value = operationRecords.map((item) => {
+      const { operationState } = item
+      return {
+        ...item,
+        operationStateStr: operationStateMap[operationState]
+      }
+    })
+  }
+})
+
+// 电梯名称 工作选项 工作状态 创建时间 发布人员 期望时间 工作描述 被困人数 受伤人数 备注 其它原因
+const cellData = ref([
+  { key: '电梯名称', value: '', field: 'elevatorName' },
+  {
+    key: '工作类型',
+    value: '',
+    format: (item) => {
+      const { consultType } = item
+      return consultType ? `${consultTypeMap[consultType]}` : ''
+    }
+  },
+  {
+    key: '工作状态',
+    format: (item) => {
+      const { state } = item
+      if (state === 2) {
+        return '已接收'
+      } else if (state === 3) {
+        return '已到场'
+      } else if (state === 4) {
+        return '已完成'
+      } else if (state === 7) {
+        return '已完成'
+      }
+    }
+  },
+  { key: '创建时间', value: '', field: 'createdTime' },
+  // { key: '发布人员', value: '', field: 'publisher' },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '工作描述', value: '', field: 'consultDesc' }
+  // { key: '备注', value: '', field: '' },
+  // { key: '其他原因', value: '', field: '' }
+])
+
+const workRecord = ref([
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[维保签名]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[维保签名]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // }
+])
+
+const toDone = async () => {
+  uni.showModal({
+    title: '提示',
+    content: '确认审核完成工作吗?',
+    success: async (res) => {
+      if (res.confirm) {
+        const { id } = options.value
+        const { error } = await postExpertConsultAuditApi({
+          id
+        })
+        if (error) {
+          return uni.$u.toast('系统繁忙,请稍后重试')
+        }
+        uni.$u.toast('审核通过')
+        setTimeout(() => {
+          uni.navigateBack({
+            delta: 1
+          })
+        }, 1500)
+      } else if (res.cancel) {
+        console.log('用户点击取消')
+      }
+    }
+  })
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view class="maintenance-detail" px-28>
+    <up-cell-group :border="false">
+      <up-cell
+        v-for="(item, index) in cellData"
+        :key="index"
+        :title="item.key"
+        :value="item.format ? item.format(dataDetail) : dataDetail[item.field]"
+      />
+    </up-cell-group>
+
+    <!-- 操作记录 -->
+    <view v-if="false" mt-32>
+      <view text-32 font-500>操作记录</view>
+      <up-cell-group :border="false">
+        <up-cell
+          v-for="(item, index) in actionRecord"
+          :key="index"
+          :title="item.operator"
+          :label="item.operationStateStr"
+          :value="item.operationTime"
+        />
+      </up-cell-group>
+    </view>
+
+    <!-- 工作记录 -->
+    <view v-if="false" mt-32>
+      <view text-32 font-500>工作记录</view>
+      <up-cell
+        title="[工作表]"
+        :label="planTypeMap[dataDetail.planType]"
+        :value="dataDetail.finishTime"
+        :is-link="true"
+        :url="`/pages/views/elevator-maintenance/work-sheet?id=${dataDetail.id}`"
+      />
+
+      <up-cell-group v-if="workRecord.length" :border="false">
+        <up-cell
+          v-for="(item, index) in workRecord"
+          :key="index"
+          :title="item.title"
+          :value="item.time"
+        >
+          <template #label>
+            <up-image
+              :show-loading="true"
+              :src="item.label"
+              width="120px"
+              height="120px"
+              @tap="previewImage(item.label)"
+            />
+          </template>
+        </up-cell>
+      </up-cell-group>
+    </view>
+    <view w-full h-136 flex-none />
+  </view>
+
+  <!-- 按钮 -->
+  <view
+    v-if="!dataDetail.auditState && dataDetail.state === 4"
+    position="fixed"
+    w-full
+    bottom-36
+    f-c-c
+  >
+    <up-button
+      type="primary"
+      text="审核完成"
+      :custom-style="{
+        width: '650rpx'
+      }"
+      @click="toDone"
+    />
+  </view>
+</template>

+ 46 - 0
elevator-ui/bak/src/pages/expert/apply-order/evaluation.vue

@@ -0,0 +1,46 @@
+<script setup>
+import { postWorkDataEvaluateApi } from '@/api/elevator'
+
+const content = ref('')
+
+const submit = async () => {
+  console.log('提交')
+  const { id: workId } = options.value
+  await postWorkDataEvaluateApi({
+    workId,
+    operationContent: content.value
+  })
+  uni.$u.toast('评价成功')
+  setTimeout(() => {
+    uni.navigateBack()
+  }, 1500)
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view px-28 mt-20>
+    <up-textarea
+      v-model="content"
+      placeholder="请输入..."
+      maxlength="300"
+      count
+      height="150"
+    />
+  </view>
+
+  <view position="fixed" w-full bottom-36 f-c-c>
+    <up-button
+      type="primary"
+      text="完成"
+      :custom-style="{
+        width: '650rpx'
+      }"
+      @click="submit"
+    />
+  </view>
+</template>

+ 189 - 0
elevator-ui/bak/src/pages/expert/apply-order/index.vue

@@ -0,0 +1,189 @@
+<script setup>
+import { adminTabs, maintainTabs } from 'constants/work'
+import dayjs from 'dayjs'
+import { useUniApi } from 'hooks/use-uni-api'
+import { getExpertConsultListApi } from '@/api/expert'
+import { usePaging } from '@/composables/use-paging'
+import { consultTypeMap } from '@/constants/state'
+import FloatFixedAddBtn from '@/pages/components/FloatFixedAddBtn.vue'
+import { useUserStore } from '@/store/user'
+const { navigateTo } = useUniApi()
+
+const tabs = ref([])
+const currentTab = ref(0)
+const currentState = ref(0)
+const handleTab = (e) => {
+  console.log('handleTab', e)
+  const { index, stateList, state } = e
+  currentTab.value = index
+  currentState.value = state
+  query.value.stateList = stateList
+  reload()
+}
+
+const query = ref({
+  // 1 救援 2维修 3 维保
+  stateList: '',
+  elevatorNum: '',
+  elevatorName: ''
+})
+const { paging, dataList, reload, queryHandler, clearHandler, enterHandler } =
+  usePaging({
+    apiFn: getExpertConsultListApi,
+    query
+  })
+
+const addHandler = () => {
+  const { id, elevatorNum, enterType } = options.value
+  if (enterType === 'hasId') {
+    navigateTo(
+      `/pages/expert/apply-order/create?id=${id}&elevatorNum=${elevatorNum}`
+    )
+  } else {
+    navigateTo(
+      '/pages/views/choose-elevator/index?redirect=/pages/expert/apply-order/create'
+    )
+  }
+}
+
+// 期望时间 关联电梯 电梯地址 维保人员 物业是否评价 工作描述
+const keyValue = reactive([
+  {
+    key: '工作类型',
+    value: '',
+    field: '',
+    format: (item) => {
+      const { consultType } = item
+      return consultType ? `${consultTypeMap[consultType]}` : ''
+    }
+  },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '关联电梯', value: '', field: 'elevatorName' },
+  { key: '电梯地址', value: '', field: 'elevatorAddress' },
+  // { key: '维保人员', value: '', field: 'collaborator' },
+  { key: '工作描述', value: '', field: 'consultDesc' }
+])
+const toDetail = ({ id, elevatorNum }) => {
+  const queryStr = `id=${id}&elevatorNum=${elevatorNum}`
+  switch (currentTab.value) {
+    case 0:
+      // navigateTo(`/pages/views/elevator-maintenance/not-started?${queryStr}`)
+      navigateTo(`/pages/expert/apply-order/undone-detail?${queryStr}`)
+      break
+    case 1:
+      navigateTo(`/pages/expert/apply-order/detail?${queryStr}`)
+      break
+  }
+}
+
+const { isWeibao } = storeToRefs(useUserStore())
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+
+  // const { elevatorNum } = options.value
+  // query.value.elevatorNum = elevatorNum
+
+  // if (isWeibao.value) {
+  //   tabs.value = maintainTabs
+  //   const [firstTab] = maintainTabs
+  //   query.value.stateList = firstTab.stateList
+  // } else {
+  //   tabs.value = adminTabs
+  //   const [firstTab] = adminTabs
+  //   query.value.stateList = firstTab.stateList
+  // }
+
+  // 用于查看状态和详细信息
+  tabs.value = adminTabs
+  const [firstTab] = adminTabs
+  query.value.stateList = firstTab.stateList
+})
+</script>
+
+<template>
+  <view class="elevator-rescue" w-full h-100vh flex-col f-s-c bg="#F8F8F8">
+    <view w-full>
+      <up-tabs
+        :current="currentTab"
+        :list="tabs"
+        :scrollable="false"
+        :item-style="{
+          width: '50%',
+          height: '80rpx'
+        }"
+        :active-style="{
+          color: '#3c9cff',
+          fontWeight: 'bold'
+        }"
+        @click="handleTab"
+      />
+    </view>
+
+    <view f-c-c mt-8 w-full flex-none bg-white>
+      <up-input
+        v-model="query.elevatorName"
+        placeholder="请输入电梯名称"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        :border="`${'none'}`"
+        :custom-style="{
+          height: '80rpx',
+          padding: '0 20rpx'
+        }"
+        @clear="
+          () => {
+            query.elevatorName = ''
+            clearHandler()
+          }
+        "
+        @confirm="enterHandler"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <z-paging
+        ref="paging"
+        v-model="dataList"
+        :fixed="false"
+        @query="queryHandler"
+      >
+        <up-card
+          v-for="(item, index) in dataList"
+          :key="index"
+          :title="`发布人员:${item.publisher || '未知'}`"
+          @click="toDetail(item)"
+        >
+          <template #body>
+            <view>
+              <view px-30 text-36 font-500 f-c-c justify-between>
+                <text v-if="[1].includes(item.state)">未接单</text>
+                <text v-else-if="[2, 3].includes(item.state)">进行中</text>
+                <template v-else>
+                  <text>已完成</text>
+                  <text text-28 text="#909399">{{ item.finishTime }}</text>
+                </template>
+              </view>
+
+              <up-cell
+                v-for="(val, idx) in keyValue"
+                :key="idx"
+                :title="val.key"
+                :value="val.format ? val.format(item) : item[val.field]"
+              />
+            </view>
+          </template>
+        </up-card>
+      </z-paging>
+    </view>
+  </view>
+
+  <FloatFixedAddBtn @click="addHandler" />
+</template>
+
+<style>
+.elevator-rescue .u-tabs {
+  background-color: #fff !important;
+}
+</style>

+ 144 - 0
elevator-ui/bak/src/pages/expert/apply-order/not-started.vue

@@ -0,0 +1,144 @@
+<script setup>
+import { postWorkDataReceiveApi } from '@/api/elevator'
+import { getExpertConsultInfoApi } from '@/api/expert'
+import { useCdn } from '@/composables/use-cdn'
+const { cdnUrl } = useCdn('/elevator/2024/11/images')
+
+const noAgreeHandler = () => {
+  uni.navigateBack({
+    delta: 1
+  })
+}
+const agreeHandler = async () => {
+  const { id: workId } = options.value
+  const { latitude: lat, longitude: lng } = locations.value
+  const { error, result } = await postWorkDataReceiveApi({
+    workId,
+    lat,
+    lng
+  })
+  if (error) {
+    return uni.$u.toast(result.msg)
+  }
+  uni.$u.toast('接单成功')
+  setTimeout(() => {
+    uni.navigateBack({
+      delta: 1
+    })
+  }, 1500)
+}
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+
+const locations = ref({
+  latitude: 30.59276,
+  longitude: 114.30525
+})
+const getLocation = () => {
+  uni.getLocation({
+    type: 'gcj02',
+    success: ({ latitude, longitude }) => {
+      locations.value = {
+        latitude,
+        longitude
+      }
+    }
+  })
+}
+onMounted(() => {
+  getLocation()
+})
+
+const dataDetail = ref({})
+const getWorkDataInfo = async () => {
+  const { error, data } = await getExpertConsultInfoApi(options.value)
+  if (!error) {
+    dataDetail.value = data
+  }
+}
+onMounted(() => {
+  getWorkDataInfo()
+})
+
+const marker = ref({
+  id: 1,
+  latitude: 30.59276,
+  longitude: 114.30525,
+  iconPath: `${cdnUrl}/position.png`,
+  width: 35,
+  height: 35,
+  callout: {
+    height: 50,
+    padding: 5,
+    fontSize: 20,
+    borderWidth: 1,
+    borderRadius: 10,
+    bgColor: '#ffffff',
+    display: 'ALWAYS',
+    textAlign: 'center',
+    anchorX: 0,
+    anchorY: 0,
+    content: ''
+  }
+})
+watchEffect(() => {
+  const { latitude, longitude } = locations.value
+  marker.value.latitude = latitude
+  marker.value.longitude = longitude
+
+  const { elevatorName } = dataDetail.value
+  marker.value.callout.content = elevatorName
+})
+</script>
+
+<template>
+  <view w-full h-100vh relative bg="#FFFFFF">
+    <map
+      id="map"
+      class="map"
+      style="width: 100%; height: 100%"
+      :markers="[marker]"
+      :show-location="true"
+      :latitude="locations.latitude"
+      :longitude="locations.longitude"
+    ></map>
+
+    <cover-view
+      absolute
+      w-200
+      h-80
+      lh-80
+      text-center
+      bottom-150
+      left-80
+      rounded-40
+      border-solid
+      border-1
+      border-gray
+      bg="#FFF"
+      @tap="noAgreeHandler"
+    >
+      不接单
+    </cover-view>
+    <cover-view
+      absolute
+      w-200
+      h-80
+      lh-80
+      text-center
+      bottom-150
+      rounded-40
+      border-solid
+      border-1
+      border-gray
+      bg="#4395D5"
+      color="#FFFFFF"
+      right-80
+      @tap="agreeHandler"
+    >
+      接单
+    </cover-view>
+  </view>
+</template>

+ 128 - 0
elevator-ui/bak/src/pages/expert/apply-order/plan.vue

@@ -0,0 +1,128 @@
+<script setup>
+import dayjs from 'dayjs'
+import { getMaintainPlanListApi } from '@/api/elevator'
+import { operationTypeMap, planTypeMap } from '@/constants/state'
+
+// 3 年前的年月日
+const startDate = dayjs().subtract(3, 'year').format('YYYY-MM-DD')
+const endDate = dayjs().format('YYYY-MM-DD')
+const change = (value) => {
+  console.log(value)
+  const { fulldate } = value
+  // query.value.startTime = `${fulldate} 00:00:00`
+  // query.value.endTime = `${fulldate} 23:59:59`
+  query.value.planTime = fulldate
+  query.value.pageNum = 1
+  dataList.value = []
+  hasMore.value = true
+  getList()
+}
+
+const keyValue = reactive([
+  { key: '电梯编号', value: '', field: 'elevatorNum' },
+  {
+    key: '完成状态',
+    value: '',
+    field: 'finishState',
+    format: (item) => (item.finishState ? '是' : '否')
+  },
+  { key: '计划日期', value: '', field: 'planTime' },
+  {
+    key: '计划类型',
+    value: '',
+    field: 'planType',
+    format: (item) => planTypeMap[item.planType]
+  },
+  {
+    key: '当前状态',
+    value: '',
+    field: 'state',
+    format: (item) => {
+      return operationTypeMap[item.state] || '未知'
+    }
+  }
+])
+
+const dataList = ref([])
+const hasMore = ref(true)
+const query = ref({
+  pageNum: 1,
+  pageSize: 10,
+  startTime: '',
+  endTime: ''
+})
+const getList = async () => {
+  let list = []
+  const { error, result } = await getMaintainPlanListApi({
+    ...options.value,
+    ...query.value
+  })
+
+  if (error) {
+    return
+  }
+  list = result.rows
+  dataList.value.push(...list)
+  hasMore.value = list.length == query.value.pageSize
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+  // const start = `${dayjs().format('YYYY-MM-DD')} 00:00:00`
+  // const end = `${dayjs().format('YYYY-MM-DD')} 23:59:59`
+  query.value = {
+    ...query.value,
+    planTime: dayjs().format('YYYY-MM-DD')
+    // startTime: start,
+    // endTime: end
+  }
+  await getList()
+})
+
+onReachBottom(() => {
+  if (!hasMore.value) {
+    return
+  }
+  query.value.pageNum++
+  getList()
+})
+</script>
+
+<template>
+  <view>
+    <uni-calendar
+      :insert="true"
+      :lunar="true"
+      :start-date="startDate"
+      :end-date="endDate"
+      :is-extend-day="false"
+      @change="change"
+    />
+
+    <view mt-30>
+      <up-card
+        v-for="(item, index) in dataList"
+        :key="index"
+        :title="item.elevatorNum"
+      >
+        <template #body>
+          <view>
+            <up-cell
+              v-for="(val, idx) in keyValue"
+              :key="idx"
+              :title="val.key"
+              :value="val.format ? val.format(item) : item[val.field]"
+            />
+          </view>
+        </template>
+      </up-card>
+
+      <up-loadmore
+        v-if="!hasMore && dataList.length"
+        :line="true"
+        status="nomore"
+      />
+    </view>
+  </view>
+</template>

+ 184 - 0
elevator-ui/bak/src/pages/expert/apply-order/record.vue

@@ -0,0 +1,184 @@
+<script setup>
+import { adminTabs, maintainTabs } from 'constants/work'
+import dayjs from 'dayjs'
+import { useUniApi } from 'hooks/use-uni-api'
+import { getWorkDataListApi } from '@/api/elevator'
+import { usePaging } from '@/composables/use-paging'
+import { planTypeMap } from '@/constants/state'
+import { useUserStore } from '@/store/user'
+const { navigateTo } = useUniApi()
+
+const tabs = ref([])
+const currentTab = ref(0)
+const handleTab = (e) => {
+  console.log('handleTab', e)
+  const { index, stateList } = e
+  currentTab.value = index
+  query.value.stateList = stateList
+  reload()
+}
+
+const query = ref({
+  // 1 救援 2维修 3 维保
+  workType: 3,
+  stateList: '',
+  elevatorNum: '',
+  elevatorName: ''
+})
+const { paging, dataList, reload, queryHandler, clearHandler, enterHandler } =
+  usePaging({
+    apiFn: getWorkDataListApi,
+    query
+  })
+
+const addHandler = () => {
+  const { id, elevatorNum, enterType } = options.value
+  if (enterType === 'hasId') {
+    navigateTo(
+      `/pages/views/elevator-maintenance/create?id=${id}&elevatorNum=${elevatorNum}`
+    )
+  } else {
+    navigateTo(
+      '/pages/views/choose-elevator/index?redirect=/pages/views/elevator-maintenance/create'
+    )
+  }
+}
+
+// 期望时间 关联电梯 电梯地址 维保人员 物业是否评价 工作描述
+const keyValue = reactive([
+  {
+    key: '维保类型',
+    value: '',
+    field: '',
+    format: (item) => {
+      const { planType } = item
+      return planType ? `${planTypeMap[planType]}维保` : ''
+    }
+  },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '关联电梯', value: '', field: 'elevatorName' },
+  { key: '电梯地址', value: '', field: 'elevatorAddress' },
+  { key: '维保人员', value: '', field: 'collaborator' },
+  {
+    key: '物业是否评价',
+    value: '',
+    field: '',
+    format: (item) => {
+      return item.isEvaluation ? '是' : '否'
+    }
+  },
+  { key: '工作描述', value: '', field: 'description' }
+])
+const toDetail = ({ id, elevatorNum }) => {
+  navigateTo(
+    `/pages/views/elevator-maintenance/detail?id=${id}&elevatorNum=${elevatorNum}`
+  )
+}
+
+const { isWeibao } = storeToRefs(useUserStore())
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+
+  const { elevatorNum } = options.value
+  query.value.elevatorNum = elevatorNum
+
+  if (isWeibao.value) {
+    tabs.value = maintainTabs
+    const [firstTab] = maintainTabs
+    query.value.stateList = firstTab.stateList
+  } else {
+    tabs.value = adminTabs
+    const [firstTab] = adminTabs
+    query.value.stateList = firstTab.stateList
+  }
+})
+</script>
+
+<template>
+  <view class="elevator-rescue" w-full h-100vh flex-col f-s-c bg="#F8F8F8">
+    <view w-full>
+      <up-tabs
+        :current="currentTab"
+        :list="tabs"
+        :scrollable="false"
+        :item-style="{
+          width: '50%',
+          height: '80rpx'
+        }"
+        :active-style="{
+          color: '#3c9cff',
+          fontWeight: 'bold'
+        }"
+        @click="handleTab"
+      />
+    </view>
+
+    <view f-c-c mt-8 w-full flex-none bg-white>
+      <up-input
+        placeholder="请输入电梯名称"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        :border="`${'none'}`"
+        :custom-style="{
+          height: '80rpx',
+          padding: '0 20rpx'
+        }"
+        @clear="
+          () => {
+            query.elevatorName = ''
+            clearHandler()
+          }
+        "
+        @confirm="enterHandler"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <scroll-view :scroll-y="true" h-full>
+        <z-paging
+          ref="paging"
+          v-model="dataList"
+          :fixed="false"
+          @query="queryHandler"
+        >
+          <up-card
+            v-for="(item, index) in dataList"
+            :key="index"
+            :title="`发布人员:${item.publisher || '未知'}`"
+            @click="toDetail(item)"
+          >
+            <template #body>
+              <view>
+                <view px-30 text-36 font-500 f-c-c justify-between>
+                  <text v-if="[1].includes(item.state)">未接单</text>
+                  <text v-else-if="[2, 3].includes(item.state)">进行中</text>
+                  <template v-else>
+                    <text>已完成</text>
+                    <text text-28 text="#909399">{{ item.finishTime }}</text>
+                  </template>
+                </view>
+
+                <up-cell
+                  v-for="(val, idx) in keyValue"
+                  :key="idx"
+                  :title="val.key"
+                  :value="val.format ? val.format(item) : item[val.field]"
+                />
+              </view>
+            </template>
+          </up-card>
+        </z-paging>
+      </scroll-view>
+    </view>
+  </view>
+
+  <FloatFixedAddBtn @click="addHandler" />
+</template>
+
+<style>
+.elevator-rescue .u-tabs {
+  background-color: #fff !important;
+}
+</style>

+ 165 - 0
elevator-ui/bak/src/pages/expert/apply-order/report.vue

@@ -0,0 +1,165 @@
+<script setup>
+import { useFetch } from 'hooks/use-fetch'
+import { useUniApi } from 'hooks/use-uni-api'
+import { getMaintainRecordListApi } from '@/api/elevator'
+const { navigateTo } = useUniApi()
+
+// const tabs = reactive([{ name: '未完成' }, { name: '已完成' }])
+const tabs = reactive([
+  { name: '未接单' },
+  { name: '进行中' },
+  { name: '已完成' }
+])
+const handleTab = (e) => {
+  console.log('handleTab', e)
+}
+
+const addHandler = () => {
+  console.log('addHandler 维保')
+}
+
+const paging = ref(null)
+// 期望时间 关联电梯 电梯地址 维保人员 物业是否评价 工作描述
+const keyValue = reactive([
+  { key: '期望时间', value: '2021-12-12', field: '' },
+  { key: '关联电梯', value: '某某某电梯', field: '' },
+  { key: '电梯地址', value: '某某某地址', field: '' },
+  { key: '维保人员', value: '某某某', field: '' },
+  { key: '物业是否评价', value: '是', field: '' },
+  { key: '工作描述', value: '领导派发', field: '' }
+])
+const queryHandler = (e) => {
+  console.log('queryHandler', e)
+}
+const refreshHandler = (e) => {
+  console.log('refreshHandler', e)
+  setTimeout(() => {
+    paging.value.complete()
+  }, 1500)
+}
+const toDetail = ({ id }) => {
+  navigateTo(`/pages/views/elevator-maintenance/detail?id=${id}`)
+}
+
+const dataList = ref([])
+const query = ref({
+  pageNum: 1,
+  pageSize: 10,
+  elevatorName: ''
+})
+const resetParams = () => {
+  query.value = {
+    pageNum: 1,
+    pageSize: 10,
+    elevatorName: ''
+  }
+  dataList.value = []
+}
+const clearHandler = () => {
+  resetParams()
+  getList()
+}
+const { result, fetchApi } = useFetch({
+  apiFn: getMaintainRecordListApi,
+  isList: true
+})
+const getList = async () => {
+  await fetchApi({
+    ...options.value,
+    ...query.value
+  })
+  dataList.value.push(...result.value.rows)
+}
+const enterHandler = () => {
+  dataList.value = []
+  query.value.pageNum = 1
+  getList()
+}
+const options = ref({})
+onLoad(async (e) => {
+  dataList.value = []
+  options.value = e
+  await getList()
+})
+</script>
+
+<template>
+  <view class="elevator-rescue" w-full h-100vh flex-col f-s-c bg="#F8F8F8">
+    <view w-full>
+      <up-tabs
+        :list="tabs"
+        :scrollable="false"
+        :item-style="{
+          width: '50%',
+          height: '80rpx'
+        }"
+        :active-style="{
+          color: '#3c9cff',
+          fontWeight: 'bold'
+        }"
+        @click="handleTab"
+      />
+    </view>
+
+    <view f-c-c mt-8 w-full flex-none bg-white>
+      <up-input
+        v-model="query.elevatorName"
+        placeholder="请输入电梯名称"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        :border="`${'none'}`"
+        :custom-style="{
+          height: '80rpx',
+          padding: '0 20rpx'
+        }"
+        @clear="clearHandler"
+        @confirm="enterHandler"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <scroll-view :scroll-y="true" h-full>
+        <z-paging
+          ref="paging"
+          v-model="dataList"
+          :fixed="false"
+          refresher-only
+          @query="queryHandler"
+          @onRefresh="refreshHandler"
+        >
+          <up-card
+            v-for="(item, index) in dataList"
+            :key="index"
+            :title="`发布人员:${item.userName}`"
+            @click="toDetail(item)"
+          >
+            <template #body>
+              <view>
+                <view px-30 text-36 font-500 f-c-c justify-between>
+                  <text>未接单/已到场/已签名/已完成</text>
+                </view>
+                <up-cell title="是否超期" value="超期" />
+                <up-cell title="维保类型" value="半月维保" />
+                <up-cell
+                  v-for="(val, idx) in keyValue"
+                  :key="idx"
+                  :title="val.key"
+                  :value="val.value"
+                />
+              </view>
+            </template>
+          </up-card>
+        </z-paging>
+      </scroll-view>
+    </view>
+  </view>
+
+  <FloatFixedAddBtn @click="addHandler" />
+</template>
+
+<style>
+.elevator-rescue .u-tabs {
+  background-color: #fff !important;
+}
+</style>

+ 134 - 0
elevator-ui/bak/src/pages/expert/apply-order/state-record.vue

@@ -0,0 +1,134 @@
+<script setup>
+import dayjs from 'dayjs'
+import { useUniApi } from 'hooks/use-uni-api'
+import { getWorkDataListApi } from '@/api/elevator'
+import { usePaging } from '@/composables/use-paging'
+import { planTypeMap } from '@/constants/state'
+const { navigateTo } = useUniApi()
+
+const query = ref({
+  // 1 救援 2维修 3 维保
+  workType: 3,
+  stateList: '',
+  elevatorName: ''
+})
+const { paging, dataList, queryHandler, clearHandler, enterHandler } =
+  usePaging({
+    apiFn: getWorkDataListApi,
+    query
+  })
+
+// 期望时间 关联电梯 电梯地址 维保人员 物业是否评价 工作描述
+const keyValue = reactive([
+  {
+    key: '维保类型',
+    value: '',
+    field: '',
+    format: (item) => {
+      const { planType } = item
+      return planType ? `${planTypeMap[planType]}维保` : ''
+    }
+  },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '关联电梯', value: '', field: 'elevatorName' },
+  { key: '电梯地址', value: '', field: 'elevatorAddress' },
+  { key: '维保人员', value: '', field: 'collaborator' },
+  {
+    key: '物业是否评价',
+    value: '',
+    field: '',
+    format: (item) => {
+      return item.isEvaluation ? '是' : '否'
+    }
+  },
+  { key: '工作描述', value: '', field: 'description' }
+])
+const toDetail = ({ id, elevatorNum }) => {
+  const queryStr = `id=${id}&elevatorNum=${elevatorNum}`
+  const { stateType } = options.value
+  if (stateType === '0') {
+    navigateTo(`/pages/views/elevator-maintenance/not-started?${queryStr}`)
+  } else {
+    navigateTo(`/pages/views/elevator-maintenance/detail?${queryStr}`)
+  }
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  // 未完成=1 已完成=4,7
+  options.value = e
+  const { stateType, stateList } = e
+  query.value.stateList = stateList
+  if (stateType === '1') {
+    uni.setNavigationBarTitle({
+      title: '已完成工作'
+    })
+  } else {
+    uni.setNavigationBarTitle({
+      title: '未完成工作'
+    })
+  }
+})
+</script>
+
+<template>
+  <view class="elevator-rescue" w-full h-100vh flex-col f-s-c bg="#F8F8F8">
+    <view f-c-c mt-8 w-full flex-none bg-white>
+      <up-input
+        v-model="query.elevatorName"
+        placeholder="请输入电梯名称"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        :border="`${'none'}`"
+        :custom-style="{
+          height: '80rpx',
+          padding: '0 20rpx'
+        }"
+        @clear="
+          () => {
+            query.elevatorName = ''
+            clearHandler()
+          }
+        "
+        @confirm="enterHandler"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <z-paging
+        ref="paging"
+        v-model="dataList"
+        :fixed="false"
+        @query="queryHandler"
+      >
+        <up-card
+          v-for="(item, index) in dataList"
+          :key="index"
+          :title="`发布人员:${item.publisher || '未知'}`"
+          @click="toDetail(item)"
+        >
+          <template #body>
+            <view>
+              <view px-30 text-36 font-500 f-c-c justify-between>
+                <text v-if="[1].includes(item.state)">未接单</text>
+                <text v-else-if="[2, 3].includes(item.state)">进行中</text>
+                <template v-else>
+                  <text>已完成</text>
+                  <text text-28 text="#909399">{{ item.finishTime }}</text>
+                </template>
+              </view>
+
+              <up-cell
+                v-for="(val, idx) in keyValue"
+                :key="idx"
+                :title="val.key"
+                :value="val.format ? val.format(item) : item[val.field]"
+              />
+            </view>
+          </template>
+        </up-card>
+      </z-paging>
+    </view>
+  </view>
+</template>

+ 215 - 0
elevator-ui/bak/src/pages/expert/apply-order/undone-detail.vue

@@ -0,0 +1,215 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import {
+  getExpertConsultInfoApi,
+  postExpertConsultAuditApi
+} from '@/api/expert'
+const { previewImage } = useUniApi()
+
+const dataDetail = ref({})
+const getWorkDataInfo = async () => {
+  const { error, data } = await getExpertConsultInfoApi(options.value)
+  if (!error) {
+    dataDetail.value = data
+  }
+}
+onMounted(() => {
+  getWorkDataInfo()
+})
+
+const operationStateMap = {
+  2: '接收',
+  3: '到场',
+  4: '完成',
+  7: '评价'
+}
+const planTypeMap = {
+  1: '半月',
+  2: '季度',
+  3: '半年',
+  4: '年度'
+}
+const consultTypeMap = {
+  1: '救援',
+  2: '维修',
+  3: '维保'
+}
+const actionRecord = ref([])
+watchEffect(() => {
+  const { operationRecords } = dataDetail.value
+
+  if (operationRecords) {
+    actionRecord.value = operationRecords.map((item) => {
+      const { operationState } = item
+      return {
+        ...item,
+        operationStateStr: operationStateMap[operationState]
+      }
+    })
+  }
+})
+
+// 电梯名称 工作选项 工作状态 创建时间 发布人员 期望时间 工作描述 被困人数 受伤人数 备注 其它原因
+const cellData = ref([
+  { key: '电梯名称', value: '', field: 'elevatorName' },
+  {
+    key: '工作选项',
+    value: '',
+    format: (item) => {
+      const { consultType } = item
+      return consultTypeMap[consultType]
+    }
+  },
+  {
+    key: '工作状态',
+    format: (item) => {
+      const { state } = item
+      if (state === 2) {
+        return '已接收'
+      } else if (state === 3) {
+        return '已到场'
+      } else if (state === 4) {
+        return '已完成'
+      } else if (state === 7) {
+        return '已完成'
+      }
+    }
+  },
+  { key: '创建时间', value: '', field: 'createdTime' },
+  // { key: '发布人员', value: '', field: 'publisher' },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '工作描述', value: '', field: 'consultDesc' }
+  // { key: '备注', value: '', field: '' },
+  // { key: '其他原因', value: '', field: '' }
+])
+
+const workRecord = ref([
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[维保签名]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[维保签名]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // }
+])
+
+const toDone = async () => {
+  uni.showModal({
+    title: '提示',
+    content: '确认审核完成工作吗?',
+    success: async (res) => {
+      if (res.confirm) {
+        const { id } = options.value
+        const { error } = await postExpertConsultAuditApi({
+          id
+        })
+        if (error) {
+          return uni.$u.toast('系统繁忙,请稍后重试')
+        }
+        uni.$u.toast('审核通过')
+        setTimeout(() => {
+          uni.navigateBack({
+            delta: 1
+          })
+        }, 1500)
+      } else if (res.cancel) {
+        console.log('用户点击取消')
+      }
+    }
+  })
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view class="maintenance-detail" px-28>
+    <up-cell-group :border="false">
+      <up-cell
+        v-for="(item, index) in cellData"
+        :key="index"
+        :title="item.key"
+        :value="item.format ? item.format(dataDetail) : dataDetail[item.field]"
+      />
+    </up-cell-group>
+
+    <!-- 操作记录 -->
+    <view v-if="false" mt-32>
+      <view text-32 font-500>操作记录</view>
+      <up-cell-group :border="false">
+        <up-cell
+          v-for="(item, index) in actionRecord"
+          :key="index"
+          :title="item.operator"
+          :label="item.operationStateStr"
+          :value="item.operationTime"
+        />
+      </up-cell-group>
+    </view>
+
+    <!-- 工作记录 -->
+    <view v-if="false" mt-32>
+      <view text-32 font-500>工作记录</view>
+      <up-cell
+        title="[工作表]"
+        :label="planTypeMap[dataDetail.planType]"
+        :value="dataDetail.finishTime"
+        :is-link="true"
+        :url="`/pages/views/elevator-maintenance/work-sheet?id=${dataDetail.id}`"
+      />
+
+      <up-cell-group v-if="workRecord.length" :border="false">
+        <up-cell
+          v-for="(item, index) in workRecord"
+          :key="index"
+          :title="item.title"
+          :value="item.time"
+        >
+          <template #label>
+            <up-image
+              :show-loading="true"
+              :src="item.label"
+              width="120px"
+              height="120px"
+              @tap="previewImage(item.label)"
+            />
+          </template>
+        </up-cell>
+      </up-cell-group>
+    </view>
+    <view w-full h-136 flex-none />
+  </view>
+
+  <!-- 按钮 -->
+  <!-- <view v-if="dataDetail.auditState" position="fixed" w-full bottom-36 f-c-c>
+    <up-button
+      type="primary"
+      text="审核完成"
+      :custom-style="{
+        width: '650rpx'
+      }"
+      @click="toDone"
+    />
+  </view> -->
+</template>

+ 409 - 0
elevator-ui/bak/src/pages/expert/apply-order/undone-detail_bak.vue

@@ -0,0 +1,409 @@
+<script setup>
+import {
+  postWorkDataArriveApi,
+  getWorkDataInfoApi,
+  putWorkDataApi
+} from '@/api/elevator'
+import { useUniApi } from '@/composables/use-uni-api'
+import { useUpload } from '@/composables/use-upload'
+const { navigateTo } = useUniApi()
+const { uploadFile } = useUpload()
+
+const dataDetail = ref({})
+const getWorkDataInfo = async () => {
+  const { error, data } = await getWorkDataInfoApi(options.value)
+  if (!error) {
+    dataDetail.value = data
+  }
+}
+onMounted(() => {
+  getWorkDataInfo()
+})
+
+const operationStateMap = {
+  2: '接收',
+  3: '到场',
+  4: '完成',
+  7: '评价'
+}
+const planTypeMap = {
+  1: '半月',
+  2: '季度',
+  3: '半年',
+  4: '年度'
+}
+
+const actionRecord = ref([])
+watchEffect(() => {
+  const { status, operationRecords } = dataDetail.value
+  if ([4, 7].includes(status)) {
+    cellData.value[2].value = '已完成'
+  }
+
+  if (operationRecords) {
+    actionRecord.value = operationRecords.map((item) => {
+      const { operationState } = item
+      return {
+        ...item,
+        operationStateStr: operationStateMap[operationState]
+      }
+    })
+  }
+})
+
+// 拍照 视频 扫一扫 记录 工作表
+const apps = [
+  {
+    name: '拍照',
+    type: 'camera',
+    background: '#88C3F3',
+    icon: 'camera'
+  },
+  {
+    name: '视频',
+    type: 'video',
+    background: '#668FE6',
+    icon: 'video'
+  },
+  {
+    name: '扫一扫',
+    type: 'scan',
+    background: '#DC635E',
+    icon: 'scan'
+  },
+  {
+    name: '记录',
+    type: 'record',
+    background: '#EF8833',
+    icon: 'edit'
+  },
+  {
+    name: '工作表',
+    type: 'sheet',
+    background: '#DD7A92',
+    icon: 'form'
+  }
+]
+
+const workSheetModal = ref({
+  name: '工作表',
+  show: false,
+  actions: [
+    { name: '半月维保', value: '1' },
+    { name: '季度维保', value: '2' },
+    { name: '半年维保', value: '3' },
+    { name: '年度维保', value: '4' }
+  ]
+})
+const workSheetActionSelect = (item) => {
+  console.log('workSheetActionSelect:', item)
+  const { value: type } = item
+  const { id } = options.value
+  navigateTo(`/pages/views/submit-sheet/index?id=${id}&type=${type}`)
+}
+
+const locations = ref({
+  latitude: 30.59276,
+  longitude: 114.30525
+})
+const getLocation = () => {
+  uni.getLocation({
+    type: 'gcj02',
+    success: ({ latitude, longitude }) => {
+      locations.value = {
+        latitude,
+        longitude
+      }
+    }
+  })
+}
+
+const options = ref({})
+onLoad((e) => {
+  options.value = e
+})
+onMounted(() => {
+  getLocation()
+})
+
+const handleAppItem = (item) => {
+  console.log(`handleItem: ${item.name}`)
+  const { type } = item
+  switch (type) {
+    case 'camera':
+      uni.chooseImage({
+        success: ({ tempFilePaths }) => {
+          uploadFile(tempFilePaths)
+        }
+      })
+      break
+    case 'video':
+      uni.chooseVideo({
+        success: ({ tempFilePath }) => {
+          uploadFile([tempFilePath])
+        }
+      })
+      break
+    case 'scan':
+      uni.scanCode({
+        success: (res) => {
+          console.log('scanCode success, res=', res)
+        },
+        fail: () => {
+          uni.$u.toast('无内容')
+        }
+      })
+      break
+    case 'record':
+      navigateTo({
+        url: '/pages/views/elevator-maintenance/add-record',
+        events: {
+          done: () => getWorkDataInfo()
+        }
+      })
+      break
+    case 'sheet':
+      workSheetModal.value.show = true
+      break
+    default:
+      break
+  }
+}
+
+const timesModal = ref({
+  show: false,
+  content: '1'
+})
+const openTimesDialog = () => {
+  timesModal.value.show = true
+}
+const confirmTimesDialog = async () => {
+  console.log('confirmTimesDialog', timesModal.value)
+  const { id } = options.value
+  const { content } = timesModal.value
+  const { workType } = dataDetail.value
+  await putWorkDataApi({
+    id,
+    workType,
+    maintenanceNum: content
+  })
+  uni.$u.toast('修改成功')
+  timesModal.value.show = false
+}
+
+const currStep = ref(0)
+const undoneSteps = ref([
+  { name: '接单', value: 2 },
+  { name: '到场', value: 3 },
+  { name: '待签名', value: 4 },
+  { name: '待评价', value: 7 }
+])
+watchEffect(() => {
+  const { state, maintenanceNum } = dataDetail.value
+  currStep.value = undoneSteps.value.findIndex((item) => item.value === state)
+
+  timesModal.value.content = maintenanceNum || '1'
+})
+const setStepItemStyle = (index) => {
+  return {
+    width: '80rpx',
+    height: '80rpx',
+    backgroundColor: currStep.value >= index ? '#4395D5' : '#B2DAEF'
+  }
+}
+const handleStepItem = (item, index) => {
+  // currStep.value = index
+  switch (index) {
+    case 1:
+      openArriveModal()
+      break
+    case 2:
+      openSignature()
+      break
+    case 3:
+      openEvaluate()
+      break
+  }
+}
+const arrivalModal = ref({
+  show: false
+})
+const openArriveModal = () => {
+  console.log('openArriveModal')
+  uni.showModal({
+    title: '提示',
+    content: '确认到场工作吗?',
+    success: async (res) => {
+      if (res.confirm) {
+        console.log('用户点击确定')
+        const { latitude: lat, longitude: lng } = locations.value
+        const { error } = await postWorkDataArriveApi({
+          workId: options.value.id,
+          lat,
+          lng
+        })
+        if (error) {
+          return uni.$u.toast('系统繁忙,请稍后重试')
+        }
+        uni.$u.toast('到场成功')
+        getWorkDataInfo()
+      } else if (res.cancel) {
+        console.log('用户点击取消')
+      }
+    }
+  })
+  // arrivalModal.value.show = true
+  // 调用接口
+  // 刷新记录列表
+}
+const confirmArrival = () => {
+  console.log('confirmArrival')
+  arrivalModal.value.show = false
+}
+const openSignature = () => {
+  console.log('openSignature')
+  const { id } = options.value
+  navigateTo(`/pages/views/elevator-maintenance/user-signature?id=${id}`)
+}
+const openEvaluate = () => {
+  console.log('openEvaluate')
+  const { id } = options.value
+  navigateTo(`/pages/views/elevator-maintenance/evaluation?id=${id}`)
+}
+</script>
+
+<template>
+  <view class="undone-detail" bg="#ECECEC">
+    <!-- 操作功能 -->
+    <view mt-20 bg="#FFFFFF">
+      <up-grid :border="false" col="5">
+        <up-grid-item
+          v-for="(item, index) in apps"
+          :key="index"
+          @click="handleAppItem(item)"
+        >
+          <view
+            w-90
+            h-90
+            rounded-34
+            f-c-c
+            relative
+            :style="{
+              background: item.background
+            }"
+          >
+            <up-icon
+              :name="item.icon"
+              :size="30"
+              custom-prefix="custom-icon"
+              :color="`${'#FFFFFF'}`"
+            />
+          </view>
+          <text text-28 font-400 mt-8 mb-30>{{ item.name }}</text>
+        </up-grid-item>
+      </up-grid>
+    </view>
+
+    <!-- 地图定位 -->
+    <view w-full h-300 mt-20 bg="#FFFFFF">
+      <map
+        id="map"
+        class="map"
+        style="width: 100%; height: 100%"
+        :show-location="true"
+        :latitude="locations.latitude"
+        :longitude="locations.longitude"
+      ></map>
+    </view>
+
+    <!-- 操作流程 -->
+    <view py-30 bg="#FFFFFF">
+      <up-steps :current="currStep">
+        <up-steps-item
+          v-for="(item, index) in undoneSteps"
+          :key="index"
+          :title="item.name"
+          :item-style="setStepItemStyle(index)"
+          @tap="handleStepItem(item, index)"
+        >
+          <template #icon>
+            <up-icon name="checkmark" size="18" :color="`${'#FFF'}`" />
+          </template>
+        </up-steps-item>
+      </up-steps>
+
+      <view mt-20 px-28>
+        <up-cell-group :border="false">
+          <up-cell
+            v-for="(item, index) in actionRecord"
+            :key="index"
+            :title="item.operator"
+            :label="item.operationStateStr"
+            :value="item.operationTime"
+          />
+        </up-cell-group>
+      </view>
+    </view>
+
+    <!-- 操作记录 -->
+    <view px-28 mt-20 bg="#FFFFFF">
+      <!-- <view text-32 font-500>操作记录</view> -->
+      <up-cell-group :border="false">
+        <up-cell
+          title="[维保记录]"
+          :label="`本次维保为第${dataDetail.maintenanceNum}次维保,为半月维保`"
+          :is-link="true"
+          @click="openTimesDialog"
+        />
+      </up-cell-group>
+    </view>
+  </view>
+
+  <up-modal
+    title="维保记录"
+    :show="timesModal.show"
+    close-on-click-overlay
+    :show-cancel-button="true"
+    @close="timesModal.show = false"
+    @cancel="timesModal.show = false"
+    @confirm="confirmTimesDialog"
+  >
+    <view f-s-c>
+      <view text-28>
+        本次维保为{{ planTypeMap[dataDetail.planType] }}维保,维保次数为
+      </view>
+      <up-input
+        v-model="timesModal.content"
+        type="number"
+        placeholder="请输入"
+      />
+    </view>
+  </up-modal>
+
+  <up-modal
+    title="确认到场工作吗?"
+    :show="arrivalModal.show"
+    close-on-click-overlay
+    :show-cancel-button="true"
+    @close="arrivalModal.show = false"
+    @cancel="arrivalModal.show = false"
+    @confirm="confirmArrival"
+  />
+
+  <up-action-sheet
+    :show="workSheetModal.show"
+    cancel-text="取消"
+    :actions="workSheetModal.actions"
+    @close="workSheetModal.show = false"
+    @select="workSheetActionSelect"
+  />
+</template>
+
+<style>
+.undone-detail .u-steps {
+  grid-template-columns: repeat(auto-fit, minmax(60px, 1fr)) !important;
+}
+.undone-detail .u-steps-item__line {
+  top: 40rpx !important;
+}
+</style>

+ 65 - 0
elevator-ui/bak/src/pages/expert/apply-order/user-signature.vue

@@ -0,0 +1,65 @@
+<script setup>
+const signatureRef = ref(null)
+// const instance = getCurrentInstance()
+const clear = () => {
+  signatureRef.value.clear()
+}
+const undo = () => {
+  signatureRef.value.undo()
+}
+const save = () => {
+  const isEmpty = signatureRef.value.isEmpty()
+  if (isEmpty) {
+    uni.$u.toast('签名不能为空')
+    return
+  }
+  signatureRef.value.canvasToTempFilePath({
+    success: ({ tempFilePath }) => {
+      uni.saveImageToPhotosAlbum({
+        filePath: tempFilePath,
+        success: () => {
+          uni.$u.toast('保存成功')
+        },
+        fail: () => {
+          uni.$u.toast('保存失败')
+        }
+      })
+    }
+  })
+}
+</script>
+
+<template>
+  <view
+    z-1
+    left-0
+    top-0
+    right-0
+    bottom-0
+    m-auto
+    text-40
+    text-center
+    color-gray
+    my-12
+  >
+    请在下方签名
+  </view>
+  <view w-746 h-600 mx-auto relative border-1 border-dotted border-gray f-c-c>
+    <HandwrittenSignature
+      ref="signatureRef"
+      style="width: 746rpx; height: 596rpx"
+      disable-scroll
+      :open-smooth="true"
+      background-color="transparent"
+      uid="0"
+      pen-color="black"
+      :pen-size="10"
+      :min-line-width="2"
+    />
+  </view>
+  <view mt-20>
+    <button @click="clear">清空</button>
+    <button @click="undo">撤消</button>
+    <button @click="save">保存</button>
+  </view>
+</template>

+ 138 - 0
elevator-ui/bak/src/pages/expert/apply-order/work-sheet.vue

@@ -0,0 +1,138 @@
+<script setup>
+import { deepClone } from 'uview-plus'
+import {
+  getWorkCarRoofInfoApi,
+  getWorkElevatorCabinInfoApi,
+  getWorkMachineRoomInfoApi,
+  getWorkPitInfoApi
+} from '@/api/elevator'
+import {
+  record0,
+  record1,
+  record2,
+  record3
+} from '@/pages/views/submit-sheet/constants'
+
+const stateMap = {
+  1: '正常',
+  2: '润滑',
+  3: '调整',
+  4: '更换',
+  5: '异常待修复',
+  6: '无此项'
+}
+
+const tabs = [
+  { name: '轿厢' },
+  { name: '轿顶' },
+  { name: '机房' },
+  { name: '底坑' }
+]
+const currTabIdx = ref(0)
+const handleTab = async ({ index }) => {
+  currTabIdx.value = index
+  let list = []
+  switch (index) {
+    case 0:
+      await getWorkElevatorCabinInfo()
+      if (Object.keys(fieldDetail.value).length) {
+        list = deepClone(record0)
+      }
+      break
+    case 1:
+      await getWorkCarRoofInfo()
+      if (Object.keys(fieldDetail.value).length) {
+        list = deepClone(record1)
+      }
+      break
+    case 2:
+      await getWorkMachineRoomInfo()
+      if (Object.keys(fieldDetail.value).length) {
+        list = deepClone(record2)
+      }
+      break
+    case 3:
+      await getWorkPitInfo()
+      if (Object.keys(fieldDetail.value).length) {
+        list = deepClone(record3)
+      }
+      break
+
+    default:
+      break
+  }
+  record.value = list.map((item) => {
+    const { field } = item
+    return {
+      ...item,
+      stateStr: stateMap[fieldDetail.value[`${field}State`]],
+      remarkStr: fieldDetail.value[`${field}Remark`]
+    }
+  })
+}
+
+const record = ref([])
+const fieldDetail = ref({})
+const getWorkElevatorCabinInfo = async () => {
+  const { data } = await getWorkElevatorCabinInfoApi(options.value)
+  fieldDetail.value = data || {}
+}
+const getWorkCarRoofInfo = async () => {
+  const { data } = await getWorkCarRoofInfoApi(options.value)
+  fieldDetail.value = data || {}
+}
+const getWorkMachineRoomInfo = async () => {
+  const { data } = await getWorkMachineRoomInfoApi(options.value)
+  fieldDetail.value = data || {}
+}
+const getWorkPitInfo = async () => {
+  const { data } = await getWorkPitInfoApi(options.value)
+  fieldDetail.value = data || {}
+}
+
+const options = ref({})
+onLoad((e) => {
+  options.value = e
+})
+onMounted(() => {
+  handleTab({ index: 0 })
+})
+</script>
+
+<template>
+  <view f-s-c flex-col h-100vh>
+    <view w-full h-90>
+      <up-tabs
+        :list="tabs"
+        :current="currTabIdx"
+        :item-style="{
+          width: '25%',
+          height: '90rpx'
+        }"
+        @change="handleTab"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden>
+      <scroll-view v-if="record.length" :scroll-y="true" h-full>
+        <up-cell-group :border="false">
+          <up-cell
+            v-for="(item, index) in record"
+            :key="index"
+            :title="item.title"
+          >
+            <template #label>
+              <view color-gray mt-8>{{ item.subtitle }}</view>
+              <view mt-8>{{ item.stateStr }}</view>
+              <view mt-8>备注:{{ item.remarkStr }}</view>
+            </template>
+          </up-cell>
+        </up-cell-group>
+      </scroll-view>
+
+      <view v-else f-c-c h-full>
+        <up-empty mode="data" />
+      </view>
+    </view>
+  </view>
+</template>

+ 32 - 0
elevator-ui/bak/src/pages/expert/index.vue

@@ -0,0 +1,32 @@
+<script setup>
+import MineTab from './mine/index.vue'
+import OrderTab from './order/index.vue'
+
+const currTab = ref(0)
+const tabChange = (value) => {
+  currTab.value = value
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view class="expert-index">
+    <OrderTab v-show="currTab === 0" :options="options" />
+    <MineTab v-show="currTab === 1" :options="options" />
+
+    <up-tabbar :value="currTab" @change="tabChange">
+      <up-tabbar-item text="订单" icon="file-text-fill" />
+      <up-tabbar-item text="我的" icon="account-fill" />
+    </up-tabbar>
+  </view>
+</template>
+
+<style>
+.expert-index .u-tabbar .u-icon .u-icon__icon {
+  font-size: 54rpx !important;
+}
+</style>

+ 56 - 0
elevator-ui/bak/src/pages/expert/mine/index.vue

@@ -0,0 +1,56 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import { useUserStore } from '@/store/user'
+const { userInfo } = useUserStore()
+
+const mainFuncList = reactive([
+  {
+    name: 'question-circle',
+    title: '使用帮助',
+    path: '/pages/expert/use-help/index'
+  },
+  {
+    name: 'setting',
+    title: '设置',
+    path: '/pages/views/user-setting/index'
+  }
+])
+
+const { navigateTo, previewImage } = useUniApi()
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view h-100vh flex-col bg="#F6F4F5">
+    <view bg-blue w-full min-h-400 class="hold-table">
+      <view mt-180 px-40 f-s-c>
+        <UserAvatar :size="60" @tap="previewImage(userInfo.avatar)" />
+        <view ml-40>
+          <view f-s-c text-white>
+            <view>{{ userInfo.nickName }}</view>
+            <view ml-12 text-24>{{ userInfo.phonenumber }}</view>
+          </view>
+          <view text-white mt-8>{{ userInfo.deptName }}</view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 主要功能 -->
+    <view w-650 mx-auto mt--70 rounded-12 bg-white relative z-2 min-h-150 py-30>
+      <up-grid col="2">
+        <up-grid-item
+          v-for="(item, index) in mainFuncList"
+          :key="index"
+          @tap="navigateTo(item.path)"
+        >
+          <up-icon :name="item.name" :size="32" :color="`${'#6C6C6C'}`" />
+          <view text="#6C6C6C" mt-8>{{ item.title }}</view>
+        </up-grid-item>
+      </up-grid>
+    </view>
+  </view>
+</template>

BIN
elevator-ui/bak/src/pages/expert/order/.DS_Store


+ 35 - 0
elevator-ui/bak/src/pages/expert/order/add-record.vue

@@ -0,0 +1,35 @@
+<script setup>
+const content = ref('')
+
+const instance = getCurrentInstance().proxy
+const eventChannel = instance.getOpenerEventChannel()
+const submit = () => {
+  console.log('提交')
+  uni.navigateBack()
+
+  eventChannel.emit('done', {})
+}
+</script>
+
+<template>
+  <view px-28 mt-20>
+    <up-textarea
+      v-model="content"
+      placeholder="请输入..."
+      maxlength="300"
+      count
+      height="150"
+    />
+  </view>
+
+  <view position="fixed" w-full bottom-36 f-c-c>
+    <up-button
+      type="primary"
+      text="完成"
+      :custom-style="{
+        width: '650rpx'
+      }"
+      @click="submit"
+    />
+  </view>
+</template>

+ 196 - 0
elevator-ui/bak/src/pages/expert/order/create.vue

@@ -0,0 +1,196 @@
+<script setup>
+import dayjs from 'dayjs'
+import { getElevatorInfoApi, postWorkDataDistributionApi } from '@/api/elevator'
+import { tryFormValidate } from '@/helper/async'
+
+const elevatorList = ref([])
+const elevatorInfo = ref({})
+const getElevatorInfo = async () => {
+  const { error, data } = await getElevatorInfoApi(options.value)
+  if (error) {
+    return
+  }
+  elevatorInfo.value = data
+  elevatorList.value = [
+    {
+      id: data.id,
+      name: data.elevatorName
+    }
+  ]
+}
+
+const showAction = ref(false)
+const actionSelect = (e) => {
+  console.log('actionSelect', e)
+}
+const actions = [
+  { name: '半月', value: '' },
+  { name: '季度', value: '' },
+  { name: '半年', value: '' },
+  { name: '年度', value: '' }
+]
+const hideKeyboard = () => {
+  uni.hideKeyboard()
+}
+
+const showCalendar = ref(false)
+const calendarConfirm = ([time]) => {
+  console.log('calendarConfirm', time)
+  showCalendar.value = false
+  model.value.expectTime = time
+}
+const calendarClose = () => {
+  showCalendar.value = false
+  form.value.validateField('expectTime')
+}
+
+const form = ref(null)
+const model = ref({
+  // 1.救援 2.维修 3.维保
+  workType: 3,
+  useType: '',
+  expectTime: dayjs().format('YYYY-MM-DD'),
+  description: ''
+})
+const rules = ref({
+  expectTime: {
+    type: 'string',
+    required: true,
+    message: '请选择期望时间',
+    trigger: ['change']
+  }
+})
+
+const submit = async () => {
+  const [isOk] = await tryFormValidate(form)
+  if (isOk) {
+    console.log(model.value)
+    const { error } = await postWorkDataDistributionApi({
+      ...model.value,
+      elevatorList: elevatorList.value
+    })
+    if (!error) {
+      uni.$u.toast('提交成功')
+      uni.navigateBack({
+        delta: 2
+      })
+    } else {
+      uni.$u.toast('提交失败,请稍后重试')
+    }
+  } else {
+    uni.$u.toast('校验失败')
+  }
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+  getElevatorInfo()
+})
+</script>
+
+<template>
+  <!-- 基本信息 -->
+  <view my-20 px-30>
+    <view text-40 text-bold mt-20>{{ elevatorInfo.elevatorName }}</view>
+    <view f-s-c mt-8>
+      <up-icon name="map-fill" :size="24" :color="`${'#579BF7'}`" />
+      <text text-28 font-400 ml-4>{{ elevatorInfo.elevatorAddress }}</text>
+    </view>
+  </view>
+
+  <view px-28 mt-20>
+    <up-form
+      ref="form"
+      label-width="80"
+      label-position="left"
+      :model="model"
+      :rules="rules"
+    >
+      <up-form-item label="工作选项" prop="workType" border-bottom>
+        <text>维保</text>
+      </up-form-item>
+      <up-form-item
+        label="维保选项"
+        prop="useType"
+        border-bottom
+        @click="
+          () => {
+            showAction = true
+            hideKeyboard()
+          }
+        "
+      >
+        <up-input
+          v-model="model.workType"
+          disabled
+          disabled-color="#ffffff"
+          placeholder="请选择维保选项"
+          border="none"
+        />
+        <template #right>
+          <up-icon name="arrow-right" />
+        </template>
+      </up-form-item>
+      <up-form-item
+        label="期望时间"
+        prop="expectTime"
+        label-width="80"
+        border-bottom
+        @click="
+          () => {
+            showCalendar = true
+            hideKeyboard()
+          }
+        "
+      >
+        <up-input
+          v-model="model.expectTime"
+          disabled
+          disabled-color="#ffffff"
+          placeholder="请选择期望时间"
+          border="none"
+        />
+        <template #right>
+          <up-icon name="arrow-right" />
+        </template>
+      </up-form-item>
+      <up-form-item
+        label="工作描述:"
+        prop="description"
+        label-position="top"
+        :border="`${'none'}`"
+      >
+        <up-textarea
+          v-model="model.description"
+          placeholder="请输入工作描述"
+          maxlength="300"
+          count
+          height="150"
+        />
+      </up-form-item>
+    </up-form>
+
+    <up-button
+      type="primary"
+      text="确认创建"
+      custom-style="margin-top: 30px"
+      @click="submit"
+    />
+  </view>
+
+  <up-action-sheet
+    :show="showAction"
+    :actions="actions"
+    title="维保选项"
+    @close="showAction = false"
+    @select="actionSelect"
+  />
+
+  <up-calendar
+    :show="showCalendar"
+    confirm-disabled-text="请选择日期"
+    @confirm="calendarConfirm"
+    @close="calendarClose"
+  />
+</template>

+ 163 - 0
elevator-ui/bak/src/pages/expert/order/detail.vue

@@ -0,0 +1,163 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import { getExpertConsultInfoApi } from '@/api/expert'
+import { planTypeMap, consultTypeMap } from '@/constants/state'
+const { previewImage } = useUniApi()
+
+const dataDetail = ref({})
+const getWorkDataInfo = async () => {
+  const { error, data } = await getExpertConsultInfoApi(options.value)
+  if (!error) {
+    dataDetail.value = data
+  }
+}
+onMounted(() => {
+  getWorkDataInfo()
+})
+
+const operationStateMap = {
+  2: '接收',
+  3: '到场',
+  4: '完成',
+  7: '评价'
+}
+const actionRecord = ref([])
+watchEffect(() => {
+  const { operationRecords } = dataDetail.value
+
+  if (operationRecords) {
+    actionRecord.value = operationRecords.map((item) => {
+      const { operationState } = item
+      return {
+        ...item,
+        operationStateStr: operationStateMap[operationState]
+      }
+    })
+  }
+})
+
+// 电梯名称 工作选项 工作状态 创建时间 发布人员 期望时间 工作描述 被困人数 受伤人数 备注 其它原因
+const cellData = ref([
+  { key: '电梯名称', value: '', field: 'elevatorName' },
+  {
+    key: '工作类型',
+    value: '',
+    format: (item) => {
+      const { consultType } = item
+      return consultTypeMap[consultType]
+    }
+  },
+  {
+    key: '工作状态',
+    format: (item) => {
+      const { state } = item
+      if (state === 2) {
+        return '已接收'
+      } else if (state === 3) {
+        return '已到场'
+      } else if (state === 4) {
+        return '已完成'
+      } else if (state === 7) {
+        return '已完成'
+      }
+    }
+  },
+  { key: '创建时间', value: '', field: 'createdTime' },
+  // { key: '发布人员', value: '', field: 'publisher' },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '工作描述', value: '', field: 'consultDesc' }
+  // { key: '备注', value: '', field: '' },
+  // { key: '其他原因', value: '', field: '' }
+])
+
+const workRecord = ref([
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[维保签名]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[维保签名]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // }
+])
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view class="maintenance-detail" px-28>
+    <up-cell-group :border="false">
+      <up-cell
+        v-for="(item, index) in cellData"
+        :key="index"
+        :title="item.key"
+        :value="item.format ? item.format(dataDetail) : dataDetail[item.field]"
+      />
+    </up-cell-group>
+
+    <!-- 操作记录 -->
+    <view v-if="false" mt-32>
+      <view text-32 font-500>操作记录</view>
+      <up-cell-group :border="false">
+        <up-cell
+          v-for="(item, index) in actionRecord"
+          :key="index"
+          :title="item.operator"
+          :label="item.operationStateStr"
+          :value="item.operationTime"
+        />
+      </up-cell-group>
+    </view>
+
+    <!-- 工作记录 -->
+    <view v-if="false" mt-32>
+      <view text-32 font-500>工作记录</view>
+      <up-cell
+        title="[工作表]"
+        :label="planTypeMap[dataDetail.planType]"
+        :value="dataDetail.finishTime"
+        :is-link="true"
+        :url="`/pages/views/elevator-maintenance/work-sheet?id=${dataDetail.id}`"
+      />
+
+      <up-cell-group v-if="workRecord.length" :border="false">
+        <up-cell
+          v-for="(item, index) in workRecord"
+          :key="index"
+          :title="item.title"
+          :value="item.time"
+        >
+          <template #label>
+            <up-image
+              :show-loading="true"
+              :src="item.label"
+              width="120px"
+              height="120px"
+              @tap="previewImage(item.label)"
+            />
+          </template>
+        </up-cell>
+      </up-cell-group>
+    </view>
+  </view>
+</template>

+ 46 - 0
elevator-ui/bak/src/pages/expert/order/evaluation.vue

@@ -0,0 +1,46 @@
+<script setup>
+import { postWorkDataEvaluateApi } from '@/api/elevator'
+
+const content = ref('')
+
+const submit = async () => {
+  console.log('提交')
+  const { id: workId } = options.value
+  await postWorkDataEvaluateApi({
+    workId,
+    operationContent: content.value
+  })
+  uni.$u.toast('评价成功')
+  setTimeout(() => {
+    uni.navigateBack()
+  }, 1500)
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view px-28 mt-20>
+    <up-textarea
+      v-model="content"
+      placeholder="请输入..."
+      maxlength="300"
+      count
+      height="150"
+    />
+  </view>
+
+  <view position="fixed" w-full bottom-36 f-c-c>
+    <up-button
+      type="primary"
+      text="完成"
+      :custom-style="{
+        width: '650rpx'
+      }"
+      @click="submit"
+    />
+  </view>
+</template>

+ 227 - 0
elevator-ui/bak/src/pages/expert/order/index.vue

@@ -0,0 +1,227 @@
+<script setup>
+import { maintainTabs } from 'constants/work'
+import dayjs from 'dayjs'
+import { useUniApi } from 'hooks/use-uni-api'
+import { getExpertConsultListApi } from '@/api/expert'
+import { usePaging } from '@/composables/use-paging'
+import { consultTypeMap } from '@/constants/state'
+const { navigateTo } = useUniApi()
+
+const props = defineProps({
+  options: {
+    type: Object,
+    default: () => {}
+  }
+})
+const { options } = toRefs(props)
+
+const tabs = ref([])
+const currentTab = ref(0)
+const currentState = ref(0)
+const handleTab = (e) => {
+  console.log('handleTab', e)
+  const { index, stateList, state } = e
+  currentTab.value = index
+  currentState.value = state
+  query.value.stateList = stateList
+  reload()
+}
+
+const query = ref({
+  stateList: '',
+  elevatorNum: '',
+  elevatorName: ''
+})
+const { paging, dataList, reload, queryHandler, clearHandler, enterHandler } =
+  usePaging({
+    apiFn: getExpertConsultListApi,
+    query
+  })
+
+// 期望时间 关联电梯 电梯地址 维保人员 物业是否评价 工作描述
+const keyValue = reactive([
+  {
+    key: '工作类型',
+    value: '',
+    field: '',
+    format: (item) => {
+      const { consultType } = item
+      return consultType ? `${consultTypeMap[consultType]}` : ''
+    }
+  },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '关联电梯', value: '', field: 'elevatorName' },
+  { key: '电梯地址', value: '', field: 'elevatorAddress' },
+  // { key: '维保人员', value: '', field: 'collaborator' },
+  { key: '工作描述', value: '', field: 'consultDesc' }
+])
+const toDetail = ({ id, elevatorNum }) => {
+  const queryStr = `id=${id}&elevatorNum=${elevatorNum}`
+  switch (currentTab.value) {
+    case 0:
+      navigateTo(`/pages/expert/order/not-started?${queryStr}`)
+      break
+    case 1:
+      navigateTo(`/pages/expert/order/undone-detail?${queryStr}`)
+      break
+    case 2:
+      navigateTo(`/pages/expert/order/detail?${queryStr}`)
+      break
+    default:
+      break
+  }
+}
+
+onReady(() => {
+  const { elevatorNum } = options.value
+  query.value.elevatorNum = elevatorNum || ''
+})
+
+onLoad(async (e) => {
+  options.value = e
+
+  tabs.value = maintainTabs
+  const [firstTab] = maintainTabs
+  query.value.stateList = firstTab.stateList
+})
+
+const statusBarHeight = computed(() => {
+  const systemInfo = uni.getSystemInfoSync()
+  return systemInfo.statusBarHeight || 0
+})
+const navigationBarHeight = computed(() => {
+  let menuButtonBoundingClientRectTop = 48
+  const capsuleHeight = 32
+  if (uni.canIUse('getMenuButtonBoundingClientRect')) {
+    menuButtonBoundingClientRectTop = uni.getMenuButtonBoundingClientRect().top
+  }
+  return (
+    capsuleHeight +
+    (menuButtonBoundingClientRectTop - statusBarHeight.value) * 2
+  )
+})
+const navbarHeight = computed(() => {
+  return statusBarHeight.value + navigationBarHeight.value
+})
+const contentHeight = computed(() => {
+  const { screenHeight, safeArea } = uni.getWindowInfo()
+  const { bottom } = safeArea
+  const bottomHeight = screenHeight - bottom + 50
+  const h = screenHeight - bottomHeight
+  return h
+})
+</script>
+
+<template>
+  <up-navbar title="咨询列表">
+    <template #left>
+      <!-- <view w-64 h-64 f-c-c border-solid border="#F8F8F8" border-1 rounded-full>
+        <up-icon name="home" :size="`${20}`" />
+      </view> -->
+    </template>
+  </up-navbar>
+
+  <view
+    w-full
+    bg-white
+    position="fixed"
+    left-0
+    right-0
+    m-x-auto
+    z-10
+    :style="{
+      top: `${navbarHeight}px`
+    }"
+  >
+    <up-tabs
+      :current="currentTab"
+      :list="tabs"
+      :scrollable="false"
+      :item-style="{
+        width: '50%',
+        height: '80rpx'
+      }"
+      :active-style="{
+        color: '#3c9cff',
+        fontWeight: 'bold'
+      }"
+      @click="handleTab"
+    />
+  </view>
+
+  <view
+    w-full
+    flex-col
+    f-s-c
+    bg="#F8F8F8"
+    :style="{ height: `${contentHeight}px` }"
+  >
+    <view w-full flex-none :style="{ height: `${navbarHeight + 40}px` }" />
+
+    <view f-c-c mt-8 w-full flex-none bg-white px-32>
+      <up-input
+        v-model="query.elevatorName"
+        placeholder="请输入电梯名称"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        :border="`${'none'}`"
+        :custom-style="{
+          height: '80rpx',
+          padding: '0 20rpx'
+        }"
+        @clear="
+          () => {
+            query.elevatorName = ''
+            clearHandler()
+          }
+        "
+        @confirm="enterHandler"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <z-paging
+        ref="paging"
+        v-model="dataList"
+        :fixed="false"
+        @query="queryHandler"
+      >
+        <up-card
+          v-for="(item, index) in dataList"
+          :key="index"
+          :title="`发布人员:${item.publisher || '未知'}`"
+          @click="toDetail(item)"
+        >
+          <template #body>
+            <view>
+              <view px-30 text-36 font-500 f-c-c justify-between>
+                <text v-if="[1].includes(item.state)" text-red>未接单</text>
+                <text v-else-if="[2, 3].includes(item.state)" text-amber>
+                  进行中
+                </text>
+                <template v-else>
+                  <text text-green>已完成</text>
+                  <text text-28 text="#909399">{{ item.finishTime }}</text>
+                </template>
+              </view>
+
+              <up-cell
+                v-for="(val, idx) in keyValue"
+                :key="idx"
+                :title="val.key"
+                :value="val.format ? val.format(item) : item[val.field]"
+              />
+
+              <view v-if="[1].includes(item.state)" mt-20>
+                <up-button size="normal" @click="toDetail(item)">
+                  去接单
+                </up-button>
+              </view>
+            </view>
+          </template>
+        </up-card>
+      </z-paging>
+    </view>
+  </view>
+</template>

+ 145 - 0
elevator-ui/bak/src/pages/expert/order/not-started.vue

@@ -0,0 +1,145 @@
+<script setup>
+import { getExpertConsultInfoApi } from '@/api/expert'
+import { postExpertConsultReceiveApi } from '@/api/expert'
+import { useCdn } from '@/composables/use-cdn'
+const { cdnUrl } = useCdn('/elevator/2024/11/images')
+
+const noAgreeHandler = () => {
+  uni.navigateBack({
+    delta: 1
+  })
+}
+const agreeHandler = async () => {
+  const { id } = options.value
+  const { latitude: lat, longitude: lng } = locations.value
+  const { error, result } = await postExpertConsultReceiveApi({
+    id,
+    // consultType: 3,
+    lat,
+    lng
+  })
+  if (error) {
+    return uni.$u.toast(result.msg)
+  }
+  uni.$u.toast('接单成功')
+  setTimeout(() => {
+    uni.navigateBack({
+      delta: 1
+    })
+  }, 1500)
+}
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+
+const locations = ref({
+  latitude: 30.59276,
+  longitude: 114.30525
+})
+const getLocation = () => {
+  uni.getLocation({
+    type: 'gcj02',
+    success: ({ latitude, longitude }) => {
+      locations.value = {
+        latitude,
+        longitude
+      }
+    }
+  })
+}
+onMounted(() => {
+  getLocation()
+})
+
+const dataDetail = ref({})
+const getWorkDataInfo = async () => {
+  const { error, data } = await getExpertConsultInfoApi(options.value)
+  if (!error) {
+    dataDetail.value = data
+  }
+}
+onMounted(() => {
+  getWorkDataInfo()
+})
+
+const marker = ref({
+  id: 1,
+  latitude: 30.59276,
+  longitude: 114.30525,
+  iconPath: `${cdnUrl}/position.png`,
+  width: 35,
+  height: 35,
+  callout: {
+    height: 50,
+    padding: 5,
+    fontSize: 20,
+    borderWidth: 1,
+    borderRadius: 10,
+    bgColor: '#ffffff',
+    display: 'ALWAYS',
+    textAlign: 'center',
+    anchorX: 0,
+    anchorY: 0,
+    content: ''
+  }
+})
+watchEffect(() => {
+  const { latitude, longitude } = locations.value
+  marker.value.latitude = latitude
+  marker.value.longitude = longitude
+
+  const { elevatorName } = dataDetail.value
+  marker.value.callout.content = elevatorName
+})
+</script>
+
+<template>
+  <view w-full h-100vh relative bg="#FFFFFF">
+    <map
+      id="map"
+      class="map"
+      style="width: 100%; height: 100%"
+      :markers="[marker]"
+      :show-location="true"
+      :latitude="locations.latitude"
+      :longitude="locations.longitude"
+    ></map>
+
+    <cover-view
+      absolute
+      w-200
+      h-80
+      lh-80
+      text-center
+      bottom-150
+      left-80
+      rounded-40
+      border-solid
+      border-1
+      border-gray
+      bg="#FFF"
+      @tap="noAgreeHandler"
+    >
+      不接单
+    </cover-view>
+    <cover-view
+      absolute
+      w-200
+      h-80
+      lh-80
+      text-center
+      bottom-150
+      rounded-40
+      border-solid
+      border-1
+      border-gray
+      bg="#4395D5"
+      color="#FFFFFF"
+      right-80
+      @tap="agreeHandler"
+    >
+      接单
+    </cover-view>
+  </view>
+</template>

+ 128 - 0
elevator-ui/bak/src/pages/expert/order/plan.vue

@@ -0,0 +1,128 @@
+<script setup>
+import dayjs from 'dayjs'
+import { getMaintainPlanListApi } from '@/api/elevator'
+import { operationTypeMap, planTypeMap } from '@/constants/state'
+
+// 3 年前的年月日
+const startDate = dayjs().subtract(3, 'year').format('YYYY-MM-DD')
+const endDate = dayjs().format('YYYY-MM-DD')
+const change = (value) => {
+  console.log(value)
+  const { fulldate } = value
+  // query.value.startTime = `${fulldate} 00:00:00`
+  // query.value.endTime = `${fulldate} 23:59:59`
+  query.value.planTime = fulldate
+  query.value.pageNum = 1
+  dataList.value = []
+  hasMore.value = true
+  getList()
+}
+
+const keyValue = reactive([
+  { key: '电梯编号', value: '', field: 'elevatorNum' },
+  {
+    key: '完成状态',
+    value: '',
+    field: 'finishState',
+    format: (item) => (item.finishState ? '是' : '否')
+  },
+  { key: '计划日期', value: '', field: 'planTime' },
+  {
+    key: '计划类型',
+    value: '',
+    field: 'planType',
+    format: (item) => planTypeMap[item.planType]
+  },
+  {
+    key: '当前状态',
+    value: '',
+    field: 'state',
+    format: (item) => {
+      return operationTypeMap[item.state] || '未知'
+    }
+  }
+])
+
+const dataList = ref([])
+const hasMore = ref(true)
+const query = ref({
+  pageNum: 1,
+  pageSize: 10,
+  startTime: '',
+  endTime: ''
+})
+const getList = async () => {
+  let list = []
+  const { error, result } = await getMaintainPlanListApi({
+    ...options.value,
+    ...query.value
+  })
+
+  if (error) {
+    return
+  }
+  list = result.rows
+  dataList.value.push(...list)
+  hasMore.value = list.length == query.value.pageSize
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+  // const start = `${dayjs().format('YYYY-MM-DD')} 00:00:00`
+  // const end = `${dayjs().format('YYYY-MM-DD')} 23:59:59`
+  query.value = {
+    ...query.value,
+    planTime: dayjs().format('YYYY-MM-DD')
+    // startTime: start,
+    // endTime: end
+  }
+  await getList()
+})
+
+onReachBottom(() => {
+  if (!hasMore.value) {
+    return
+  }
+  query.value.pageNum++
+  getList()
+})
+</script>
+
+<template>
+  <view>
+    <uni-calendar
+      :insert="true"
+      :lunar="true"
+      :start-date="startDate"
+      :end-date="endDate"
+      :is-extend-day="false"
+      @change="change"
+    />
+
+    <view mt-30>
+      <up-card
+        v-for="(item, index) in dataList"
+        :key="index"
+        :title="item.elevatorNum"
+      >
+        <template #body>
+          <view>
+            <up-cell
+              v-for="(val, idx) in keyValue"
+              :key="idx"
+              :title="val.key"
+              :value="val.format ? val.format(item) : item[val.field]"
+            />
+          </view>
+        </template>
+      </up-card>
+
+      <up-loadmore
+        v-if="!hasMore && dataList.length"
+        :line="true"
+        status="nomore"
+      />
+    </view>
+  </view>
+</template>

+ 184 - 0
elevator-ui/bak/src/pages/expert/order/record.vue

@@ -0,0 +1,184 @@
+<script setup>
+import { adminTabs, maintainTabs } from 'constants/work'
+import dayjs from 'dayjs'
+import { useUniApi } from 'hooks/use-uni-api'
+import { getWorkDataListApi } from '@/api/elevator'
+import { usePaging } from '@/composables/use-paging'
+import { planTypeMap } from '@/constants/state'
+import { useUserStore } from '@/store/user'
+const { navigateTo } = useUniApi()
+
+const tabs = ref([])
+const currentTab = ref(0)
+const handleTab = (e) => {
+  console.log('handleTab', e)
+  const { index, stateList } = e
+  currentTab.value = index
+  query.value.stateList = stateList
+  reload()
+}
+
+const query = ref({
+  // 1 救援 2维修 3 维保
+  workType: 3,
+  stateList: '',
+  elevatorNum: '',
+  elevatorName: ''
+})
+const { paging, dataList, reload, queryHandler, clearHandler, enterHandler } =
+  usePaging({
+    apiFn: getWorkDataListApi,
+    query
+  })
+
+const addHandler = () => {
+  const { id, elevatorNum, enterType } = options.value
+  if (enterType === 'hasId') {
+    navigateTo(
+      `/pages/views/elevator-maintenance/create?id=${id}&elevatorNum=${elevatorNum}`
+    )
+  } else {
+    navigateTo(
+      '/pages/views/choose-elevator/index?redirect=/pages/views/elevator-maintenance/create'
+    )
+  }
+}
+
+// 期望时间 关联电梯 电梯地址 维保人员 物业是否评价 工作描述
+const keyValue = reactive([
+  {
+    key: '维保类型',
+    value: '',
+    field: '',
+    format: (item) => {
+      const { planType } = item
+      return planType ? `${planTypeMap[planType]}维保` : ''
+    }
+  },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '关联电梯', value: '', field: 'elevatorName' },
+  { key: '电梯地址', value: '', field: 'elevatorAddress' },
+  { key: '维保人员', value: '', field: 'collaborator' },
+  {
+    key: '物业是否评价',
+    value: '',
+    field: '',
+    format: (item) => {
+      return item.isEvaluation ? '是' : '否'
+    }
+  },
+  { key: '工作描述', value: '', field: 'description' }
+])
+const toDetail = ({ id, elevatorNum }) => {
+  navigateTo(
+    `/pages/views/elevator-maintenance/detail?id=${id}&elevatorNum=${elevatorNum}`
+  )
+}
+
+const { isWeibao } = storeToRefs(useUserStore())
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+
+  const { elevatorNum } = options.value
+  query.value.elevatorNum = elevatorNum
+
+  if (isWeibao.value) {
+    tabs.value = maintainTabs
+    const [firstTab] = maintainTabs
+    query.value.stateList = firstTab.stateList
+  } else {
+    tabs.value = adminTabs
+    const [firstTab] = adminTabs
+    query.value.stateList = firstTab.stateList
+  }
+})
+</script>
+
+<template>
+  <view class="elevator-rescue" w-full h-100vh flex-col f-s-c bg="#F8F8F8">
+    <view w-full>
+      <up-tabs
+        :current="currentTab"
+        :list="tabs"
+        :scrollable="false"
+        :item-style="{
+          width: '50%',
+          height: '80rpx'
+        }"
+        :active-style="{
+          color: '#3c9cff',
+          fontWeight: 'bold'
+        }"
+        @click="handleTab"
+      />
+    </view>
+
+    <view f-c-c mt-8 w-full flex-none bg-white>
+      <up-input
+        placeholder="请输入电梯名称"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        :border="`${'none'}`"
+        :custom-style="{
+          height: '80rpx',
+          padding: '0 20rpx'
+        }"
+        @clear="
+          () => {
+            query.elevatorName = ''
+            clearHandler()
+          }
+        "
+        @confirm="enterHandler"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <scroll-view :scroll-y="true" h-full>
+        <z-paging
+          ref="paging"
+          v-model="dataList"
+          :fixed="false"
+          @query="queryHandler"
+        >
+          <up-card
+            v-for="(item, index) in dataList"
+            :key="index"
+            :title="`发布人员:${item.publisher || '未知'}`"
+            @click="toDetail(item)"
+          >
+            <template #body>
+              <view>
+                <view px-30 text-36 font-500 f-c-c justify-between>
+                  <text v-if="[1].includes(item.state)">未接单</text>
+                  <text v-else-if="[2, 3].includes(item.state)">进行中</text>
+                  <template v-else>
+                    <text>已完成</text>
+                    <text text-28 text="#909399">{{ item.finishTime }}</text>
+                  </template>
+                </view>
+
+                <up-cell
+                  v-for="(val, idx) in keyValue"
+                  :key="idx"
+                  :title="val.key"
+                  :value="val.format ? val.format(item) : item[val.field]"
+                />
+              </view>
+            </template>
+          </up-card>
+        </z-paging>
+      </scroll-view>
+    </view>
+  </view>
+
+  <FloatFixedAddBtn @click="addHandler" />
+</template>
+
+<style>
+.elevator-rescue .u-tabs {
+  background-color: #fff !important;
+}
+</style>

+ 165 - 0
elevator-ui/bak/src/pages/expert/order/report.vue

@@ -0,0 +1,165 @@
+<script setup>
+import { useFetch } from 'hooks/use-fetch'
+import { useUniApi } from 'hooks/use-uni-api'
+import { getMaintainRecordListApi } from '@/api/elevator'
+const { navigateTo } = useUniApi()
+
+// const tabs = reactive([{ name: '未完成' }, { name: '已完成' }])
+const tabs = reactive([
+  { name: '未接单' },
+  { name: '进行中' },
+  { name: '已完成' }
+])
+const handleTab = (e) => {
+  console.log('handleTab', e)
+}
+
+const addHandler = () => {
+  console.log('addHandler 维保')
+}
+
+const paging = ref(null)
+// 期望时间 关联电梯 电梯地址 维保人员 物业是否评价 工作描述
+const keyValue = reactive([
+  { key: '期望时间', value: '2021-12-12', field: '' },
+  { key: '关联电梯', value: '某某某电梯', field: '' },
+  { key: '电梯地址', value: '某某某地址', field: '' },
+  { key: '维保人员', value: '某某某', field: '' },
+  { key: '物业是否评价', value: '是', field: '' },
+  { key: '工作描述', value: '领导派发', field: '' }
+])
+const queryHandler = (e) => {
+  console.log('queryHandler', e)
+}
+const refreshHandler = (e) => {
+  console.log('refreshHandler', e)
+  setTimeout(() => {
+    paging.value.complete()
+  }, 1500)
+}
+const toDetail = ({ id }) => {
+  navigateTo(`/pages/views/elevator-maintenance/detail?id=${id}`)
+}
+
+const dataList = ref([])
+const query = ref({
+  pageNum: 1,
+  pageSize: 10,
+  elevatorName: ''
+})
+const resetParams = () => {
+  query.value = {
+    pageNum: 1,
+    pageSize: 10,
+    elevatorName: ''
+  }
+  dataList.value = []
+}
+const clearHandler = () => {
+  resetParams()
+  getList()
+}
+const { result, fetchApi } = useFetch({
+  apiFn: getMaintainRecordListApi,
+  isList: true
+})
+const getList = async () => {
+  await fetchApi({
+    ...options.value,
+    ...query.value
+  })
+  dataList.value.push(...result.value.rows)
+}
+const enterHandler = () => {
+  dataList.value = []
+  query.value.pageNum = 1
+  getList()
+}
+const options = ref({})
+onLoad(async (e) => {
+  dataList.value = []
+  options.value = e
+  await getList()
+})
+</script>
+
+<template>
+  <view class="elevator-rescue" w-full h-100vh flex-col f-s-c bg="#F8F8F8">
+    <view w-full>
+      <up-tabs
+        :list="tabs"
+        :scrollable="false"
+        :item-style="{
+          width: '50%',
+          height: '80rpx'
+        }"
+        :active-style="{
+          color: '#3c9cff',
+          fontWeight: 'bold'
+        }"
+        @click="handleTab"
+      />
+    </view>
+
+    <view f-c-c mt-8 w-full flex-none bg-white>
+      <up-input
+        v-model="query.elevatorName"
+        placeholder="请输入电梯名称"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        :border="`${'none'}`"
+        :custom-style="{
+          height: '80rpx',
+          padding: '0 20rpx'
+        }"
+        @clear="clearHandler"
+        @confirm="enterHandler"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <scroll-view :scroll-y="true" h-full>
+        <z-paging
+          ref="paging"
+          v-model="dataList"
+          :fixed="false"
+          refresher-only
+          @query="queryHandler"
+          @onRefresh="refreshHandler"
+        >
+          <up-card
+            v-for="(item, index) in dataList"
+            :key="index"
+            :title="`发布人员:${item.userName}`"
+            @click="toDetail(item)"
+          >
+            <template #body>
+              <view>
+                <view px-30 text-36 font-500 f-c-c justify-between>
+                  <text>未接单/已到场/已签名/已完成</text>
+                </view>
+                <up-cell title="是否超期" value="超期" />
+                <up-cell title="维保类型" value="半月维保" />
+                <up-cell
+                  v-for="(val, idx) in keyValue"
+                  :key="idx"
+                  :title="val.key"
+                  :value="val.value"
+                />
+              </view>
+            </template>
+          </up-card>
+        </z-paging>
+      </scroll-view>
+    </view>
+  </view>
+
+  <FloatFixedAddBtn @click="addHandler" />
+</template>
+
+<style>
+.elevator-rescue .u-tabs {
+  background-color: #fff !important;
+}
+</style>

+ 134 - 0
elevator-ui/bak/src/pages/expert/order/state-record.vue

@@ -0,0 +1,134 @@
+<script setup>
+import dayjs from 'dayjs'
+import { useUniApi } from 'hooks/use-uni-api'
+import { getWorkDataListApi } from '@/api/elevator'
+import { usePaging } from '@/composables/use-paging'
+import { planTypeMap } from '@/constants/state'
+const { navigateTo } = useUniApi()
+
+const query = ref({
+  // 1 救援 2维修 3 维保
+  workType: 3,
+  stateList: '',
+  elevatorName: ''
+})
+const { paging, dataList, queryHandler, clearHandler, enterHandler } =
+  usePaging({
+    apiFn: getWorkDataListApi,
+    query
+  })
+
+// 期望时间 关联电梯 电梯地址 维保人员 物业是否评价 工作描述
+const keyValue = reactive([
+  {
+    key: '维保类型',
+    value: '',
+    field: '',
+    format: (item) => {
+      const { planType } = item
+      return planType ? `${planTypeMap[planType]}维保` : ''
+    }
+  },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '关联电梯', value: '', field: 'elevatorName' },
+  { key: '电梯地址', value: '', field: 'elevatorAddress' },
+  { key: '维保人员', value: '', field: 'collaborator' },
+  {
+    key: '物业是否评价',
+    value: '',
+    field: '',
+    format: (item) => {
+      return item.isEvaluation ? '是' : '否'
+    }
+  },
+  { key: '工作描述', value: '', field: 'description' }
+])
+const toDetail = ({ id, elevatorNum }) => {
+  const queryStr = `id=${id}&elevatorNum=${elevatorNum}`
+  const { stateType } = options.value
+  if (stateType === '0') {
+    navigateTo(`/pages/views/elevator-maintenance/not-started?${queryStr}`)
+  } else {
+    navigateTo(`/pages/views/elevator-maintenance/detail?${queryStr}`)
+  }
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  // 未完成=1 已完成=4,7
+  options.value = e
+  const { stateType, stateList } = e
+  query.value.stateList = stateList
+  if (stateType === '1') {
+    uni.setNavigationBarTitle({
+      title: '已完成工作'
+    })
+  } else {
+    uni.setNavigationBarTitle({
+      title: '未完成工作'
+    })
+  }
+})
+</script>
+
+<template>
+  <view class="elevator-rescue" w-full h-100vh flex-col f-s-c bg="#F8F8F8">
+    <view f-c-c mt-8 w-full flex-none bg-white>
+      <up-input
+        v-model="query.elevatorName"
+        placeholder="请输入电梯名称"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        :border="`${'none'}`"
+        :custom-style="{
+          height: '80rpx',
+          padding: '0 20rpx'
+        }"
+        @clear="
+          () => {
+            query.elevatorName = ''
+            clearHandler()
+          }
+        "
+        @confirm="enterHandler"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <z-paging
+        ref="paging"
+        v-model="dataList"
+        :fixed="false"
+        @query="queryHandler"
+      >
+        <up-card
+          v-for="(item, index) in dataList"
+          :key="index"
+          :title="`发布人员:${item.publisher || '未知'}`"
+          @click="toDetail(item)"
+        >
+          <template #body>
+            <view>
+              <view px-30 text-36 font-500 f-c-c justify-between>
+                <text v-if="[1].includes(item.state)">未接单</text>
+                <text v-else-if="[2, 3].includes(item.state)">进行中</text>
+                <template v-else>
+                  <text>已完成</text>
+                  <text text-28 text="#909399">{{ item.finishTime }}</text>
+                </template>
+              </view>
+
+              <up-cell
+                v-for="(val, idx) in keyValue"
+                :key="idx"
+                :title="val.key"
+                :value="val.format ? val.format(item) : item[val.field]"
+              />
+            </view>
+          </template>
+        </up-card>
+      </z-paging>
+    </view>
+  </view>
+</template>

+ 203 - 0
elevator-ui/bak/src/pages/expert/order/undone-detail.vue

@@ -0,0 +1,203 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import {
+  getExpertConsultInfoApi,
+  postExpertConsultCompleteApi
+} from '@/api/expert'
+import { planTypeMap, consultTypeMap } from '@/constants/state'
+const { previewImage } = useUniApi()
+
+const dataDetail = ref({})
+const getWorkDataInfo = async () => {
+  const { error, data } = await getExpertConsultInfoApi(options.value)
+  if (!error) {
+    dataDetail.value = data
+  }
+}
+onMounted(() => {
+  getWorkDataInfo()
+})
+
+const operationStateMap = {
+  2: '接收',
+  3: '到场',
+  4: '完成',
+  7: '评价'
+}
+const actionRecord = ref([])
+watchEffect(() => {
+  const { operationRecords } = dataDetail.value
+
+  if (operationRecords) {
+    actionRecord.value = operationRecords.map((item) => {
+      const { operationState } = item
+      return {
+        ...item,
+        operationStateStr: operationStateMap[operationState]
+      }
+    })
+  }
+})
+
+// 电梯名称 工作选项 工作状态 创建时间 发布人员 期望时间 工作描述 被困人数 受伤人数 备注 其它原因
+const cellData = ref([
+  { key: '电梯名称', value: '', field: 'elevatorName' },
+  {
+    key: '工作选项',
+    value: '',
+    format: (item) => {
+      const { consultType } = item
+      return consultTypeMap[consultType]
+    }
+  },
+  {
+    key: '工作状态',
+    format: (item) => {
+      const { state } = item
+      if (state === 2) {
+        return '已接收'
+      } else if (state === 3) {
+        return '已到场'
+      } else if (state === 4) {
+        return '已完成'
+      } else if (state === 7) {
+        return '已完成'
+      }
+    }
+  },
+  { key: '创建时间', value: '', field: 'createdTime' },
+  // { key: '发布人员', value: '', field: 'publisher' },
+  { key: '期望时间', value: '', field: 'expectTime' },
+  { key: '工作描述', value: '', field: 'consultDesc' }
+  // { key: '备注', value: '', field: '' },
+  // { key: '其他原因', value: '', field: '' }
+])
+
+const workRecord = ref([
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[维保签名]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[维保签名]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // },
+  // {
+  //   title: '[图片]',
+  //   label: 'https://img.yzcdn.cn/vant/cat.jpeg',
+  //   time: '2024-01-01 13:00:00'
+  // }
+])
+
+const toDone = async () => {
+  uni.showModal({
+    title: '提示',
+    content: '确认已完成工作吗?',
+    success: async (res) => {
+      if (res.confirm) {
+        const { id } = options.value
+        const { error } = await postExpertConsultCompleteApi({
+          id
+        })
+        if (error) {
+          return uni.$u.toast('系统繁忙,请稍后重试')
+        }
+        uni.$u.toast('完成工作')
+        setTimeout(() => {
+          uni.navigateBack({
+            delta: 1
+          })
+        }, 1500)
+      } else if (res.cancel) {
+        console.log('用户点击取消')
+      }
+    }
+  })
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view class="maintenance-detail" px-28>
+    <up-cell-group :border="false">
+      <up-cell
+        v-for="(item, index) in cellData"
+        :key="index"
+        :title="item.key"
+        :value="item.format ? item.format(dataDetail) : dataDetail[item.field]"
+      />
+    </up-cell-group>
+
+    <!-- 操作记录 -->
+    <view v-if="false" mt-32>
+      <view text-32 font-500>操作记录</view>
+      <up-cell-group :border="false">
+        <up-cell
+          v-for="(item, index) in actionRecord"
+          :key="index"
+          :title="item.operator"
+          :label="item.operationStateStr"
+          :value="item.operationTime"
+        />
+      </up-cell-group>
+    </view>
+
+    <!-- 工作记录 -->
+    <view v-if="false" mt-32>
+      <view text-32 font-500>工作记录</view>
+      <up-cell
+        title="[工作表]"
+        :label="planTypeMap[dataDetail.planType]"
+        :value="dataDetail.finishTime"
+        :is-link="true"
+        :url="`/pages/views/elevator-maintenance/work-sheet?id=${dataDetail.id}`"
+      />
+
+      <up-cell-group v-if="workRecord.length" :border="false">
+        <up-cell
+          v-for="(item, index) in workRecord"
+          :key="index"
+          :title="item.title"
+          :value="item.time"
+        >
+          <template #label>
+            <up-image
+              :show-loading="true"
+              :src="item.label"
+              width="120px"
+              height="120px"
+              @tap="previewImage(item.label)"
+            />
+          </template>
+        </up-cell>
+      </up-cell-group>
+    </view>
+    <view w-full h-136 flex-none />
+  </view>
+  <view position="fixed" w-full bottom-36 f-c-c>
+    <up-button
+      type="primary"
+      text="完成"
+      :custom-style="{
+        width: '650rpx'
+      }"
+      @click="toDone"
+    />
+  </view>
+</template>

+ 409 - 0
elevator-ui/bak/src/pages/expert/order/undone-detail_bak.vue

@@ -0,0 +1,409 @@
+<script setup>
+import {
+  postWorkDataArriveApi,
+  getWorkDataInfoApi,
+  putWorkDataApi
+} from '@/api/elevator'
+import { useUniApi } from '@/composables/use-uni-api'
+import { useUpload } from '@/composables/use-upload'
+const { navigateTo } = useUniApi()
+const { uploadFile } = useUpload()
+
+const dataDetail = ref({})
+const getWorkDataInfo = async () => {
+  const { error, data } = await getWorkDataInfoApi(options.value)
+  if (!error) {
+    dataDetail.value = data
+  }
+}
+onMounted(() => {
+  getWorkDataInfo()
+})
+
+const operationStateMap = {
+  2: '接收',
+  3: '到场',
+  4: '完成',
+  7: '评价'
+}
+const planTypeMap = {
+  1: '半月',
+  2: '季度',
+  3: '半年',
+  4: '年度'
+}
+
+const actionRecord = ref([])
+watchEffect(() => {
+  const { status, operationRecords } = dataDetail.value
+  if ([4, 7].includes(status)) {
+    cellData.value[2].value = '已完成'
+  }
+
+  if (operationRecords) {
+    actionRecord.value = operationRecords.map((item) => {
+      const { operationState } = item
+      return {
+        ...item,
+        operationStateStr: operationStateMap[operationState]
+      }
+    })
+  }
+})
+
+// 拍照 视频 扫一扫 记录 工作表
+const apps = [
+  {
+    name: '拍照',
+    type: 'camera',
+    background: '#88C3F3',
+    icon: 'camera'
+  },
+  {
+    name: '视频',
+    type: 'video',
+    background: '#668FE6',
+    icon: 'video'
+  },
+  {
+    name: '扫一扫',
+    type: 'scan',
+    background: '#DC635E',
+    icon: 'scan'
+  },
+  {
+    name: '记录',
+    type: 'record',
+    background: '#EF8833',
+    icon: 'edit'
+  },
+  {
+    name: '工作表',
+    type: 'sheet',
+    background: '#DD7A92',
+    icon: 'form'
+  }
+]
+
+const workSheetModal = ref({
+  name: '工作表',
+  show: false,
+  actions: [
+    { name: '半月维保', value: '1' },
+    { name: '季度维保', value: '2' },
+    { name: '半年维保', value: '3' },
+    { name: '年度维保', value: '4' }
+  ]
+})
+const workSheetActionSelect = (item) => {
+  console.log('workSheetActionSelect:', item)
+  const { value: type } = item
+  const { id } = options.value
+  navigateTo(`/pages/views/submit-sheet/index?id=${id}&type=${type}`)
+}
+
+const locations = ref({
+  latitude: 30.59276,
+  longitude: 114.30525
+})
+const getLocation = () => {
+  uni.getLocation({
+    type: 'gcj02',
+    success: ({ latitude, longitude }) => {
+      locations.value = {
+        latitude,
+        longitude
+      }
+    }
+  })
+}
+
+const options = ref({})
+onLoad((e) => {
+  options.value = e
+})
+onMounted(() => {
+  getLocation()
+})
+
+const handleAppItem = (item) => {
+  console.log(`handleItem: ${item.name}`)
+  const { type } = item
+  switch (type) {
+    case 'camera':
+      uni.chooseImage({
+        success: ({ tempFilePaths }) => {
+          uploadFile(tempFilePaths)
+        }
+      })
+      break
+    case 'video':
+      uni.chooseVideo({
+        success: ({ tempFilePath }) => {
+          uploadFile([tempFilePath])
+        }
+      })
+      break
+    case 'scan':
+      uni.scanCode({
+        success: (res) => {
+          console.log('scanCode success, res=', res)
+        },
+        fail: () => {
+          uni.$u.toast('无内容')
+        }
+      })
+      break
+    case 'record':
+      navigateTo({
+        url: '/pages/views/elevator-maintenance/add-record',
+        events: {
+          done: () => getWorkDataInfo()
+        }
+      })
+      break
+    case 'sheet':
+      workSheetModal.value.show = true
+      break
+    default:
+      break
+  }
+}
+
+const timesModal = ref({
+  show: false,
+  content: '1'
+})
+const openTimesDialog = () => {
+  timesModal.value.show = true
+}
+const confirmTimesDialog = async () => {
+  console.log('confirmTimesDialog', timesModal.value)
+  const { id } = options.value
+  const { content } = timesModal.value
+  const { workType } = dataDetail.value
+  await putWorkDataApi({
+    id,
+    workType,
+    maintenanceNum: content
+  })
+  uni.$u.toast('修改成功')
+  timesModal.value.show = false
+}
+
+const currStep = ref(0)
+const undoneSteps = ref([
+  { name: '接单', value: 2 },
+  { name: '到场', value: 3 },
+  { name: '待签名', value: 4 },
+  { name: '待评价', value: 7 }
+])
+watchEffect(() => {
+  const { state, maintenanceNum } = dataDetail.value
+  currStep.value = undoneSteps.value.findIndex((item) => item.value === state)
+
+  timesModal.value.content = maintenanceNum || '1'
+})
+const setStepItemStyle = (index) => {
+  return {
+    width: '80rpx',
+    height: '80rpx',
+    backgroundColor: currStep.value >= index ? '#4395D5' : '#B2DAEF'
+  }
+}
+const handleStepItem = (item, index) => {
+  // currStep.value = index
+  switch (index) {
+    case 1:
+      openArriveModal()
+      break
+    case 2:
+      openSignature()
+      break
+    case 3:
+      openEvaluate()
+      break
+  }
+}
+const arrivalModal = ref({
+  show: false
+})
+const openArriveModal = () => {
+  console.log('openArriveModal')
+  uni.showModal({
+    title: '提示',
+    content: '确认到场工作吗?',
+    success: async (res) => {
+      if (res.confirm) {
+        console.log('用户点击确定')
+        const { latitude: lat, longitude: lng } = locations.value
+        const { error } = await postWorkDataArriveApi({
+          workId: options.value.id,
+          lat,
+          lng
+        })
+        if (error) {
+          return uni.$u.toast('系统繁忙,请稍后重试')
+        }
+        uni.$u.toast('到场成功')
+        getWorkDataInfo()
+      } else if (res.cancel) {
+        console.log('用户点击取消')
+      }
+    }
+  })
+  // arrivalModal.value.show = true
+  // 调用接口
+  // 刷新记录列表
+}
+const confirmArrival = () => {
+  console.log('confirmArrival')
+  arrivalModal.value.show = false
+}
+const openSignature = () => {
+  console.log('openSignature')
+  const { id } = options.value
+  navigateTo(`/pages/views/elevator-maintenance/user-signature?id=${id}`)
+}
+const openEvaluate = () => {
+  console.log('openEvaluate')
+  const { id } = options.value
+  navigateTo(`/pages/views/elevator-maintenance/evaluation?id=${id}`)
+}
+</script>
+
+<template>
+  <view class="undone-detail" bg="#ECECEC">
+    <!-- 操作功能 -->
+    <view mt-20 bg="#FFFFFF">
+      <up-grid :border="false" col="5">
+        <up-grid-item
+          v-for="(item, index) in apps"
+          :key="index"
+          @click="handleAppItem(item)"
+        >
+          <view
+            w-90
+            h-90
+            rounded-34
+            f-c-c
+            relative
+            :style="{
+              background: item.background
+            }"
+          >
+            <up-icon
+              :name="item.icon"
+              :size="30"
+              custom-prefix="custom-icon"
+              :color="`${'#FFFFFF'}`"
+            />
+          </view>
+          <text text-28 font-400 mt-8 mb-30>{{ item.name }}</text>
+        </up-grid-item>
+      </up-grid>
+    </view>
+
+    <!-- 地图定位 -->
+    <view w-full h-300 mt-20 bg="#FFFFFF">
+      <map
+        id="map"
+        class="map"
+        style="width: 100%; height: 100%"
+        :show-location="true"
+        :latitude="locations.latitude"
+        :longitude="locations.longitude"
+      ></map>
+    </view>
+
+    <!-- 操作流程 -->
+    <view py-30 bg="#FFFFFF">
+      <up-steps :current="currStep">
+        <up-steps-item
+          v-for="(item, index) in undoneSteps"
+          :key="index"
+          :title="item.name"
+          :item-style="setStepItemStyle(index)"
+          @tap="handleStepItem(item, index)"
+        >
+          <template #icon>
+            <up-icon name="checkmark" size="18" :color="`${'#FFF'}`" />
+          </template>
+        </up-steps-item>
+      </up-steps>
+
+      <view mt-20 px-28>
+        <up-cell-group :border="false">
+          <up-cell
+            v-for="(item, index) in actionRecord"
+            :key="index"
+            :title="item.operator"
+            :label="item.operationStateStr"
+            :value="item.operationTime"
+          />
+        </up-cell-group>
+      </view>
+    </view>
+
+    <!-- 操作记录 -->
+    <view px-28 mt-20 bg="#FFFFFF">
+      <!-- <view text-32 font-500>操作记录</view> -->
+      <up-cell-group :border="false">
+        <up-cell
+          title="[维保记录]"
+          :label="`本次维保为第${dataDetail.maintenanceNum}次维保,为半月维保`"
+          :is-link="true"
+          @click="openTimesDialog"
+        />
+      </up-cell-group>
+    </view>
+  </view>
+
+  <up-modal
+    title="维保记录"
+    :show="timesModal.show"
+    close-on-click-overlay
+    :show-cancel-button="true"
+    @close="timesModal.show = false"
+    @cancel="timesModal.show = false"
+    @confirm="confirmTimesDialog"
+  >
+    <view f-s-c>
+      <view text-28>
+        本次维保为{{ planTypeMap[dataDetail.planType] }}维保,维保次数为
+      </view>
+      <up-input
+        v-model="timesModal.content"
+        type="number"
+        placeholder="请输入"
+      />
+    </view>
+  </up-modal>
+
+  <up-modal
+    title="确认到场工作吗?"
+    :show="arrivalModal.show"
+    close-on-click-overlay
+    :show-cancel-button="true"
+    @close="arrivalModal.show = false"
+    @cancel="arrivalModal.show = false"
+    @confirm="confirmArrival"
+  />
+
+  <up-action-sheet
+    :show="workSheetModal.show"
+    cancel-text="取消"
+    :actions="workSheetModal.actions"
+    @close="workSheetModal.show = false"
+    @select="workSheetActionSelect"
+  />
+</template>
+
+<style>
+.undone-detail .u-steps {
+  grid-template-columns: repeat(auto-fit, minmax(60px, 1fr)) !important;
+}
+.undone-detail .u-steps-item__line {
+  top: 40rpx !important;
+}
+</style>

+ 65 - 0
elevator-ui/bak/src/pages/expert/order/user-signature.vue

@@ -0,0 +1,65 @@
+<script setup>
+const signatureRef = ref(null)
+// const instance = getCurrentInstance()
+const clear = () => {
+  signatureRef.value.clear()
+}
+const undo = () => {
+  signatureRef.value.undo()
+}
+const save = () => {
+  const isEmpty = signatureRef.value.isEmpty()
+  if (isEmpty) {
+    uni.$u.toast('签名不能为空')
+    return
+  }
+  signatureRef.value.canvasToTempFilePath({
+    success: ({ tempFilePath }) => {
+      uni.saveImageToPhotosAlbum({
+        filePath: tempFilePath,
+        success: () => {
+          uni.$u.toast('保存成功')
+        },
+        fail: () => {
+          uni.$u.toast('保存失败')
+        }
+      })
+    }
+  })
+}
+</script>
+
+<template>
+  <view
+    z-1
+    left-0
+    top-0
+    right-0
+    bottom-0
+    m-auto
+    text-40
+    text-center
+    color-gray
+    my-12
+  >
+    请在下方签名
+  </view>
+  <view w-746 h-600 mx-auto relative border-1 border-dotted border-gray f-c-c>
+    <HandwrittenSignature
+      ref="signatureRef"
+      style="width: 746rpx; height: 596rpx"
+      disable-scroll
+      :open-smooth="true"
+      background-color="transparent"
+      uid="0"
+      pen-color="black"
+      :pen-size="10"
+      :min-line-width="2"
+    />
+  </view>
+  <view mt-20>
+    <button @click="clear">清空</button>
+    <button @click="undo">撤消</button>
+    <button @click="save">保存</button>
+  </view>
+</template>

+ 138 - 0
elevator-ui/bak/src/pages/expert/order/work-sheet.vue

@@ -0,0 +1,138 @@
+<script setup>
+import { deepClone } from 'uview-plus'
+import {
+  getWorkCarRoofInfoApi,
+  getWorkElevatorCabinInfoApi,
+  getWorkMachineRoomInfoApi,
+  getWorkPitInfoApi
+} from '@/api/elevator'
+import {
+  record0,
+  record1,
+  record2,
+  record3
+} from '@/pages/views/submit-sheet/constants'
+
+const stateMap = {
+  1: '正常',
+  2: '润滑',
+  3: '调整',
+  4: '更换',
+  5: '异常待修复',
+  6: '无此项'
+}
+
+const tabs = [
+  { name: '轿厢' },
+  { name: '轿顶' },
+  { name: '机房' },
+  { name: '底坑' }
+]
+const currTabIdx = ref(0)
+const handleTab = async ({ index }) => {
+  currTabIdx.value = index
+  let list = []
+  switch (index) {
+    case 0:
+      await getWorkElevatorCabinInfo()
+      if (Object.keys(fieldDetail.value).length) {
+        list = deepClone(record0)
+      }
+      break
+    case 1:
+      await getWorkCarRoofInfo()
+      if (Object.keys(fieldDetail.value).length) {
+        list = deepClone(record1)
+      }
+      break
+    case 2:
+      await getWorkMachineRoomInfo()
+      if (Object.keys(fieldDetail.value).length) {
+        list = deepClone(record2)
+      }
+      break
+    case 3:
+      await getWorkPitInfo()
+      if (Object.keys(fieldDetail.value).length) {
+        list = deepClone(record3)
+      }
+      break
+
+    default:
+      break
+  }
+  record.value = list.map((item) => {
+    const { field } = item
+    return {
+      ...item,
+      stateStr: stateMap[fieldDetail.value[`${field}State`]],
+      remarkStr: fieldDetail.value[`${field}Remark`]
+    }
+  })
+}
+
+const record = ref([])
+const fieldDetail = ref({})
+const getWorkElevatorCabinInfo = async () => {
+  const { data } = await getWorkElevatorCabinInfoApi(options.value)
+  fieldDetail.value = data || {}
+}
+const getWorkCarRoofInfo = async () => {
+  const { data } = await getWorkCarRoofInfoApi(options.value)
+  fieldDetail.value = data || {}
+}
+const getWorkMachineRoomInfo = async () => {
+  const { data } = await getWorkMachineRoomInfoApi(options.value)
+  fieldDetail.value = data || {}
+}
+const getWorkPitInfo = async () => {
+  const { data } = await getWorkPitInfoApi(options.value)
+  fieldDetail.value = data || {}
+}
+
+const options = ref({})
+onLoad((e) => {
+  options.value = e
+})
+onMounted(() => {
+  handleTab({ index: 0 })
+})
+</script>
+
+<template>
+  <view f-s-c flex-col h-100vh>
+    <view w-full h-90>
+      <up-tabs
+        :list="tabs"
+        :current="currTabIdx"
+        :item-style="{
+          width: '25%',
+          height: '90rpx'
+        }"
+        @change="handleTab"
+      />
+    </view>
+
+    <view w-full flex-auto overflow-hidden>
+      <scroll-view v-if="record.length" :scroll-y="true" h-full>
+        <up-cell-group :border="false">
+          <up-cell
+            v-for="(item, index) in record"
+            :key="index"
+            :title="item.title"
+          >
+            <template #label>
+              <view color-gray mt-8>{{ item.subtitle }}</view>
+              <view mt-8>{{ item.stateStr }}</view>
+              <view mt-8>备注:{{ item.remarkStr }}</view>
+            </template>
+          </up-cell>
+        </up-cell-group>
+      </scroll-view>
+
+      <view v-else f-c-c h-full>
+        <up-empty mode="data" />
+      </view>
+    </view>
+  </view>
+</template>

+ 38 - 0
elevator-ui/bak/src/pages/expert/use-help/index.vue

@@ -0,0 +1,38 @@
+<script setup>
+import { useUniApi } from '@/composables/use-uni-api'
+import config from '@/config'
+const { cdn } = config
+
+const PNG_1 = `${cdn}/elevator/2024/12/30/4d2f6fbb2ab74f619cee8925374e2389.jpg`
+const PNG_2 = `${cdn}/elevator/2024/12/30/96978c870eea4205b5ad7d364889e0f2.jpg`
+const PNG_3 = `${cdn}/elevator/2024/12/30/0a1987d290f84a57be6ce4d5439b9ef5.jpg`
+
+const { previewImage } = useUniApi()
+</script>
+
+<template>
+  <view>
+    <up-collapse accordion value="0">
+      <up-collapse-item title="1.接单" name="0">
+        <view>
+          <view>
+            进入小程序轻触【订单】->点击左上角【未接单】查看未接单的工单列表,进行接单。
+          </view>
+          <up-image :src="PNG_1" @click="previewImage(PNG_1)" />
+        </view>
+      </up-collapse-item>
+      <up-collapse-item title="2.完成工单" name="1">
+        <view>
+          进入小程序轻触【订单】->点击左上角【进行中】查看进行中的的工单列表,进行完成工单。
+          <up-image :src="PNG_2" @click="previewImage(PNG_2)" />
+        </view>
+      </up-collapse-item>
+      <up-collapse-item title="3.查看详情" name="2">
+        <view>
+          进入小程序轻触【订单】->点击左上角【已完成】查看已完成的工单列表,查看工单详情。
+          <up-image :src="PNG_3" @click="previewImage(PNG_3)" />
+        </view>
+      </up-collapse-item>
+    </up-collapse>
+  </view>
+</template>

+ 49 - 0
elevator-ui/bak/src/pages/index/index.vue

@@ -0,0 +1,49 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import { getUserInfoApi, postElevatorWechatUserBindApi } from '@/api/system'
+import { useUserStore } from '@/store/user'
+
+const { navigateTo } = useUniApi()
+const { setUserInfo } = useUserStore()
+onMounted(async () => {
+  const { error, data } = await getUserInfoApi()
+  if (error) {
+    navigateTo('/pages/views/login/index')
+  } else {
+    const { user, roles } = data
+    user && setUserInfo(user)
+    if (roles.includes('superadmin') || roles.includes('maintenance')) {
+      uni.switchTab({
+        url: '/pages/tabbar/message/index'
+      })
+    } else if (roles.includes('expert')) {
+      uni.reLaunch({
+        url: '/pages/expert/index'
+      })
+    } else {
+      uni.reLaunch({
+        url: '/pages/views/no-permission/index'
+      })
+    }
+  }
+  sessionLogin()
+})
+
+const sessionLogin = () => {
+  uni.login({
+    success: ({ code }) => {
+      postElevatorWechatUserBindApi({ code })
+    }
+  })
+}
+</script>
+
+<template>
+  <view w-full h-100vh>
+    <up-loading-page
+      :loading="true"
+      loading-mode="semicircle"
+      loading-text=""
+    />
+  </view>
+</template>

BIN
elevator-ui/bak/src/pages/redirect/.DS_Store


+ 65 - 0
elevator-ui/bak/src/pages/redirect/elevator.vue

@@ -0,0 +1,65 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import { getUserInfoApi } from '@/api/system'
+import { useUserStore } from '@/store/user'
+const { navigateTo } = useUniApi()
+
+const { setUserInfo, setUserRoles } = useUserStore()
+const checkInfo = async () => {
+  const { error, data } = await getUserInfoApi()
+  if (error) {
+    navigateTo('/pages/views/login/index')
+  } else {
+    const { user, roles } = data
+    if (user) {
+      setUserInfo(user)
+      setUserRoles(roles)
+    }
+    if (roles.includes('superadmin') || roles.includes('maintenance')) {
+      uni.switchTab({
+        url: '/pages/tabbar/message/index'
+      })
+    } else if (roles.includes('expert')) {
+      uni.reLaunch({
+        url: '/pages/tabbar/expert/index'
+      })
+    } else {
+      uni.reLaunch({
+        url: '/pages/views/no-permission/index'
+      })
+    }
+  }
+}
+
+const options = ref({})
+onLoad((e) => {
+  const { scene } = e
+  if (!scene) {
+    return checkInfo()
+  } else {
+    try {
+      options.value = e
+      const id = scene
+      if (id) {
+        uni.reLaunch({
+          url: `/pages/views/basic-info/index?id=${id}`
+        })
+      } else {
+        checkInfo()
+      }
+    } catch (error) {
+      checkInfo()
+    }
+  }
+})
+</script>
+
+<template>
+  <view w-full h-100vh>
+    <up-loading-page
+      :loading="true"
+      loading-mode="semicircle"
+      loading-text=""
+    />
+  </view>
+</template>

+ 65 - 0
elevator-ui/bak/src/pages/redirect/index.vue

@@ -0,0 +1,65 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import { getUserInfoApi } from '@/api/system'
+import { useUserStore } from '@/store/user'
+const { navigateTo } = useUniApi()
+
+const { setUserInfo, setUserRoles } = useUserStore()
+const checkInfo = async () => {
+  const { error, data } = await getUserInfoApi()
+  if (error) {
+    navigateTo('/pages/views/login/index')
+  } else {
+    const { user, roles } = data
+    if (user) {
+      setUserInfo(user)
+      setUserRoles(roles)
+    }
+    if (roles.includes('superadmin') || roles.includes('maintenance')) {
+      uni.switchTab({
+        url: '/pages/tabbar/message/index'
+      })
+    } else if (roles.includes('expert')) {
+      uni.reLaunch({
+        url: '/pages/tabbar/expert/index'
+      })
+    } else {
+      uni.reLaunch({
+        url: '/pages/views/no-permission/index'
+      })
+    }
+  }
+}
+
+const options = ref({})
+onLoad((e) => {
+  const { scene } = e
+  if (!scene) {
+    return checkInfo()
+  } else {
+    try {
+      options.value = e
+      const id = scene
+      if (id) {
+        uni.reLaunch({
+          url: `/pages/views/basic-info/index?id=${id}`
+        })
+      } else {
+        checkInfo()
+      }
+    } catch (error) {
+      checkInfo()
+    }
+  }
+})
+</script>
+
+<template>
+  <view w-full h-100vh>
+    <up-loading-page
+      :loading="true"
+      loading-mode="semicircle"
+      loading-text=""
+    />
+  </view>
+</template>

+ 138 - 0
elevator-ui/bak/src/pages/tabbar/message/index.vue

@@ -0,0 +1,138 @@
+<script setup>
+import PositionPicker from 'components/PositionPicker.vue'
+import dayjs from 'dayjs'
+import { useUserStore } from 'store/user'
+import { throttle } from 'uview-plus'
+import { getFaultMessageListApi } from '@/api/elevator'
+import { usePaging } from '@/composables/use-paging'
+const userStore = useUserStore()
+const { place } = storeToRefs(userStore)
+
+const confirmHandler = ([value]) => {
+  userStore.setPlace(value)
+
+  const { id } = uni.getStorageSync('place') || {}
+  query.value.deptId = id || ''
+  reload()
+}
+
+const scrollTop = ref(0)
+const scrollChange = ({ detail }) => {
+  throttle(() => {
+    scrollTop.value = detail.scrollTop
+  }, 250)
+}
+const handleBackTop = () => {
+  scrollTop.value = 0
+  scrollToTop()
+}
+
+const getMessageDesc = (item) => {
+  const { faultName, floor, picture } = item
+  if (faultName) {
+    return `电梯设备:${faultName},${floor}层`
+  } else if (picture) {
+    return '[图片]'
+  } else {
+    return ''
+  }
+}
+const getHappenTime = (time) => {
+  if (dayjs().diff(time, 'day') < 1) {
+    return dayjs(time).format('HH:mm')
+  } else if (dayjs().diff(time, 'year') < 1) {
+    return dayjs(time).format('MM-DD HH:mm')
+  } else {
+    return dayjs(time).format('YYYY-MM-DD HH:mm')
+  }
+}
+
+const toSystemMessage = ({ id, elevatorNum }) => {
+  uni.navigateTo({
+    url: `/pages/views/system-message/index?id=${id}&elevatorNum=${elevatorNum}`
+  })
+}
+
+const showPicker = ref(false)
+
+const query = ref({
+  miniMsgFlag: true,
+  deptId: '',
+  elevatorName: ''
+})
+const {
+  paging,
+  dataList,
+  reload,
+  queryHandler,
+  clearHandler,
+  enterHandler,
+  scrollToTop
+} = usePaging({
+  apiFn: getFaultMessageListApi,
+  query
+})
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+
+  const { id } = uni.getStorageSync('place') || {}
+  query.value.deptId = id || ''
+})
+</script>
+
+<template>
+  <view f-s-c flex-col h-100vh>
+    <view f-s-c px-20 mt-12 w-full flex-none h-100>
+      <up-input
+        v-model="query.elevatorName"
+        placeholder="请输入搜素内容"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        @clear="
+          () => {
+            query.elevatorName = ''
+            clearHandler()
+          }
+        "
+        @confirm="enterHandler"
+      />
+      <view ml-12 @tap="showPicker = true">{{ place.label }}</view>
+    </view>
+
+    <view w-full flex-auto overflow-hidden>
+      <z-paging
+        ref="paging"
+        v-model="dataList"
+        :fixed="false"
+        @query="queryHandler"
+        @scroll="scrollChange"
+      >
+        <up-cell-group>
+          <up-cell
+            v-for="(item, index) in dataList"
+            :key="index"
+            :title="item.elevatorName"
+            :value="getHappenTime(item.happenTime)"
+            :label="getMessageDesc(item)"
+            @click="toSystemMessage(item)"
+          >
+            <template #icon>
+              <view w-80 h-80 rounded-full f-c-c mr-12 bg="#4CA4E5">
+                <up-icon name="bell-fill" size="50" :color="`${'#FFFFFF'}`" />
+              </view>
+            </template>
+          </up-cell>
+        </up-cell-group>
+      </z-paging>
+    </view>
+
+    <!-- 回到顶部 -->
+    <up-back-top :scroll-top="scrollTop" :top="500" @tap="handleBackTop" />
+
+    <!-- 选择位置弹窗 -->
+    <PositionPicker v-model="showPicker" @confirm="confirmHandler" />
+  </view>
+</template>

+ 115 - 0
elevator-ui/bak/src/pages/tabbar/mine/index.vue

@@ -0,0 +1,115 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import { getElevatorListApi } from '@/api/elevator'
+import { usePaging } from '@/composables/use-paging'
+import { useUserStore } from '@/store/user'
+const { userInfo } = useUserStore()
+
+const mainFuncList = reactive([
+  {
+    name: 'plus-circle',
+    title: '添加电梯',
+    path: '/pages/views/elevator-update/index'
+  },
+  {
+    name: 'question-circle',
+    title: '使用帮助',
+    path: '/pages/views/use-help/index'
+  },
+  {
+    name: 'setting',
+    title: '设置',
+    path: '/pages/views/user-setting/index'
+  }
+])
+
+const { navigateTo, previewImage, scanCode } = useUniApi()
+const handleScan = () => {
+  scanCode()
+    .then((res) => {
+      console.log('success =>', res)
+    })
+    .catch((err) => {
+      console.log('fail =>', err)
+    })
+}
+
+// 跳转电梯信息
+const toElevatorInfo = ({ id, elevatorNum }) => {
+  navigateTo(
+    `/pages/views/elevator-info/index?id=${id}&elevatorNum=${elevatorNum}`
+  )
+}
+
+const query = ref({
+  elevatorName: ''
+})
+const { paging, dataList, queryHandler } = usePaging({
+  apiFn: getElevatorListApi,
+  query
+})
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view h-100vh flex-col bg="#F6F4F5">
+    <view bg-blue w-full min-h-400 class="hold-table">
+      <view mt-180 px-40 f-s-c>
+        <UserAvatar :size="60" @tap="previewImage(userInfo.avatar)" />
+        <view ml-40>
+          <view f-s-c text-white>
+            <view>{{ userInfo.nickName }}</view>
+            <view ml-12 text-24>{{ userInfo.phonenumber }}</view>
+          </view>
+          <view text-white mt-8>{{ userInfo.deptName }}</view>
+        </view>
+        <view f-c-c w-40 h-40 ml-auto>
+          <up-icon
+            name="scan"
+            :color="`${'#FFFFFF'}`"
+            :size="40"
+            @tap="handleScan"
+          />
+        </view>
+      </view>
+    </view>
+
+    <!-- 主要功能 -->
+    <view w-650 mx-auto mt--70 rounded-12 bg-white relative z-2 min-h-150 py-30>
+      <up-grid>
+        <up-grid-item
+          v-for="(item, index) in mainFuncList"
+          :key="index"
+          @tap="navigateTo(item.path)"
+        >
+          <up-icon :name="item.name" :size="32" :color="`${'#6C6C6C'}`" />
+          <view text="#6C6C6C" mt-8>{{ item.title }}</view>
+        </up-grid-item>
+      </up-grid>
+    </view>
+
+    <!-- 列表 -->
+    <view w-650 mx-auto mt-24 flex-auto overflow-hidden rounded-12 bg-white>
+      <z-paging
+        ref="paging"
+        v-model="dataList"
+        :fixed="false"
+        @query="queryHandler"
+      >
+        <up-cell-group>
+          <up-cell
+            v-for="(item, index) in dataList"
+            :key="index"
+            :title="item.elevatorName"
+            :label="item.elevatorAddress"
+            @tap="toElevatorInfo(item)"
+          />
+        </up-cell-group>
+      </z-paging>
+    </view>
+  </view>
+</template>

+ 61 - 0
elevator-ui/bak/src/pages/tabbar/work/components/AppGroupItem.vue

@@ -0,0 +1,61 @@
+<script setup>
+import { useUserStore } from '@/store/user'
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
+  apps: {
+    type: Array,
+    default: () => []
+  }
+})
+const { apps } = toRefs(props)
+const { roles } = storeToRefs(useUserStore())
+
+const isShowItem = (item) => {
+  const { onlyRoles } = item || {}
+  if (!onlyRoles) {
+    return true
+  } else {
+    return onlyRoles.some((role) => roles.value.includes(role))
+  }
+}
+
+const emit = defineEmits(['itemHandler'])
+const handleItem = (index) => {
+  emit('itemHandler', apps.value[index])
+}
+</script>
+
+<template>
+  <view text-40 text-bold px-40 py-20 mt-20>{{ title }}</view>
+  <up-grid :border="false" col="4" @click="handleItem">
+    <template v-for="(item, index) in apps">
+      <up-grid-item v-if="isShowItem(item)" :key="index">
+        <view
+          w-90
+          h-90
+          rounded-34
+          f-c-c
+          relative
+          :style="{
+            background: `linear-gradient(to right, ${item.bg ? item.bg : '#FBE7A3, #F7CC4F'})`
+          }"
+        >
+          <up-icon
+            :name="item.icon"
+            custom-prefix="custom-icon"
+            :size="30"
+            :color="`${'#FFFFFF'}`"
+          />
+          <view absolute right--30 top--12 w-60 h-30 f-c-c>
+            <up-badge :value="item.number" shape="circle" :max="99" />
+          </view>
+        </view>
+        <text text-28 font-400 mt-8 mb-30>{{ item.name }}</text>
+      </up-grid-item>
+    </template>
+  </up-grid>
+</template>

+ 227 - 0
elevator-ui/bak/src/pages/tabbar/work/constants.js

@@ -0,0 +1,227 @@
+// 常用应用
+// 电梯资料 救援 维保 维修 现行状态 监控 签到 超期年检 超期维保 乘客反馈 运行数据 体检报告
+export const usualList = [
+  {
+    name: '电梯资料',
+    icon: 'folder-fill',
+    path: '/pages/views/choose-elevator/index?redirect=/pages/views/elevator-data/index',
+    type: '',
+    bg: '#B4F5CC, #70D28B',
+    number: 0
+  },
+  {
+    name: '救援',
+    icon: 'dtyjjy',
+    path: '/pages/views/elevator-rescue/index',
+    type: '',
+    bg: '#F1AB8F, #EE7C52',
+    number: 0
+  },
+  {
+    name: '维保',
+    icon: 'rhy',
+    path: '/pages/views/elevator-maintenance/index',
+    type: '',
+    bg: '#FBE7A3, #F7CC4F',
+    number: 0
+  },
+  {
+    name: '维修',
+    icon: 'spanner',
+    path: '/pages/views/elevator-maintain/index',
+    type: '',
+    bg: '#C3F7E8, #7BD3B8',
+    number: 0
+  },
+  {
+    name: '现行状态',
+    icon: 'up-down',
+    path: '/pages/views/choose-elevator/index?redirect=/pages/views/running-status/index',
+    type: '',
+    bg: '#F8DB9C, #F4B94D',
+    number: 0
+  },
+  {
+    name: '监控',
+    icon: 'jiankong',
+    // path: '/pages/views/choose-elevator/index?redirect=/pages/views/surveillance-camera/index',
+    path: '/pages/views/surveillance-camera/choose',
+    type: '',
+    bg: '#D6A4F8, #BA63F4',
+    number: 0
+  },
+  {
+    name: '签到',
+    icon: 'jiankong',
+    path: '/pages/views/check-in/index',
+    type: '',
+    bg: '#B3E0FA, #60B3F1',
+    number: 0
+  },
+  {
+    name: '超期年检',
+    icon: 'weibao',
+    path: '/pages/views/exceed-limit/annual-inspection',
+    type: '',
+    bg: '#F1A88A, #EE825A',
+    number: 0
+  },
+  {
+    name: '超期维保',
+    icon: 'sjkgth',
+    path: '/pages/views/exceed-limit/elevator-maintenance',
+    type: '',
+    bg: '#D7A6F8, #BC65F6',
+    number: 0
+  },
+  {
+    name: '乘客反馈',
+    icon: 'feedback',
+    path: '/pages/views/passenger-feedback/index',
+    type: '',
+    bg: '#AAE3FC, #67C9F9',
+    number: 0
+  },
+  {
+    name: '运行数据',
+    icon: 'shuju',
+    path: '/pages/views/choose-elevator/index?redirect=/pages/views/running-chart/index',
+    type: '',
+    bg: '#A4CCFA, #569AF7',
+    number: 0
+  },
+  {
+    name: '体检报告',
+    icon: 'tjsh',
+    path: '/pages/views/choose-elevator/index?redirect=/pages/views/elevator-examine/index',
+    type: '',
+    bg: '#FAE6A1, #F7CB4D',
+    number: 0
+  },
+  {
+    name: '监控大屏',
+    icon: 'jkdp',
+    path: '/pages/views/big-screen/index',
+    type: '',
+    bg: '#AAE3FC, #67C9F9',
+    onlyRoles: ['superadmin'],
+    number: 0
+  },
+  {
+    name: '专家咨询',
+    icon: 'zxdd',
+    path: '/pages/views/choose-elevator/index?redirect=/pages/expert/apply-order/index',
+    type: '',
+    bg: '#9EDFFC, #67C9F9',
+    number: 0
+  }
+]
+
+// 任务
+// 维保记录 救援记录 维修记录
+export const taskList = [
+  {
+    name: '维保记录',
+    icon: 'rhy',
+    path: '/pages/views/elevator-maintenance/record',
+    type: '',
+    bg: '#FBE7A3, #F7CC4F',
+    number: 0
+  },
+  {
+    name: '救援记录',
+    icon: 'dtyjjy',
+    path: '/pages/views/elevator-rescue/record',
+    type: '',
+    bg: '#F1AB8F, #EE7C52',
+    number: 0
+  },
+  {
+    name: '维修记录',
+    icon: 'spanner',
+    path: '/pages/views/elevator-maintain/record',
+    type: '',
+    bg: '#C3F7E8, #7BD3B8',
+    number: 0
+  }
+]
+
+// 实时
+// 监控 现行状态
+export const realtimeList = [
+  {
+    name: '监控',
+    icon: 'jiankong',
+    // path: '/pages/views/choose-elevator/index?redirect=/pages/views/surveillance-camera/index',
+    path: '/pages/views/surveillance-camera/choose',
+    type: '',
+    bg: '#D6A4F8, #BA63F4',
+    number: 0
+  },
+  {
+    name: '现行状态',
+    icon: 'up-down',
+    path: '/pages/views/choose-elevator/index?redirect=/pages/views/running-status/index',
+    type: '',
+    bg: '#F8DB9C, #F4B94D',
+    number: 0
+  }
+]
+
+// 历史
+// 历史信息 历史工作
+export const historyList = [
+  {
+    name: '历史信息',
+    icon: 'history-work',
+    path: '/pages/views/history-info/index',
+    type: '',
+    bg: '#F1AB8F, #EE7C52',
+    number: 0
+  },
+  {
+    name: '历史工作',
+    icon: 'job',
+    path: '/pages/views/history-work/index',
+    type: '',
+    bg: '#AAE3FC, #67C9F9',
+    number: 0
+  }
+]
+
+// 其他应用
+// 维保计划 年检计划 历史信息 历史工作
+export const otherList = [
+  {
+    name: '维保计划',
+    icon: 'wbjh',
+    path: '/pages/views/elevator-maintenance/plan',
+    type: '',
+    bg: '#B4F5CC, #70D28B',
+    number: 0
+  },
+  {
+    name: '年检计划',
+    icon: 'tjsh',
+    path: '/pages/views/inspection-plan/index',
+    type: '',
+    bg: '#EAD59F, #DEC283',
+    number: 0
+  },
+  {
+    name: '历史信息',
+    icon: 'history-work',
+    path: '/pages/views/history-info/index',
+    type: '',
+    bg: '#F1AB8F, #EE7C52',
+    number: 0
+  },
+  {
+    name: '历史工作',
+    icon: 'job',
+    path: '/pages/views/history-work/index',
+    type: '',
+    bg: '#AAE3FC, #67C9F9',
+    number: 0
+  }
+]

+ 132 - 0
elevator-ui/bak/src/pages/tabbar/work/index.vue

@@ -0,0 +1,132 @@
+<script setup>
+import { useUniApi } from 'hooks/use-uni-api'
+import AppGroupItem from './components/AppGroupItem.vue'
+import {
+  usualList,
+  taskList,
+  realtimeList,
+  historyList,
+  otherList
+} from './constants'
+import { getCompleteStatisticsApi } from '@/api/elevator'
+import { useUserStore } from '@/store/user'
+const { navigateTo } = useUniApi()
+
+// 概览数据
+const workData = ref([
+  {
+    name: '未完成工作',
+    start: 0,
+    number: 0,
+    stateType: 0,
+    stateList: '1'
+  },
+  {
+    name: '已完成工作',
+    start: 0,
+    number: 0,
+    stateType: 1,
+    stateList: '4,7'
+  }
+])
+
+const usualApps = ref(usualList)
+const taskApps = ref(taskList)
+const realtimeApps = ref(realtimeList)
+const historyApps = ref(historyList)
+const otherApps = ref(otherList)
+
+// 创建对子组件的引用
+const uToastRef = ref(null)
+
+const countHandler = (index) => {
+  const { roles } = storeToRefs(useUserStore())
+  if (roles.value.includes('maintenance')) {
+    const { stateList, stateType } = workData.value[index]
+    navigateTo(
+      `/pages/views/elevator-maintenance/state-record?stateList=${stateList}&stateType=${stateType}`
+    )
+  }
+}
+const itemHandler = ({ path }) => {
+  navigateTo(path)
+}
+
+const getCompleteStatistics = async () => {
+  const { data } = await getCompleteStatisticsApi({})
+  const [item1, item2] = data
+  const start1 = item1.total - 20
+  const start2 = item2.total - 20
+  workData.value[0].start = start1 < 0 ? 0 : start1
+  workData.value[0].number = item1.total
+  workData.value[1].start = start2 < 0 ? 0 : start2
+  workData.value[1].number = item2.total
+}
+
+onMounted(() => {
+  getCompleteStatistics()
+})
+</script>
+
+<template>
+  <view>
+    <view class="top-number" mt-20 px-40>
+      <up-grid :col="2" @click="countHandler">
+        <up-grid-item v-for="(item, index) in workData" :key="index">
+          <view h-130 f-c-c flex-col>
+            <up-count-to
+              :start-val="item.start"
+              :end-val="item.number"
+              :color="`${'#FFFFFF'}`"
+            />
+            <text text-28 font-400 text-white mt-8>{{ item.name }}</text>
+          </view>
+        </up-grid-item>
+      </up-grid>
+    </view>
+
+    <!-- 常用应用 -->
+    <AppGroupItem
+      title="常用应用"
+      :apps="usualApps"
+      @itemHandler="itemHandler"
+    />
+
+    <!-- 任务 -->
+    <AppGroupItem title="任务" :apps="taskApps" @itemHandler="itemHandler" />
+
+    <!-- 实时 -->
+    <AppGroupItem
+      title="实时"
+      :apps="realtimeApps"
+      @itemHandler="itemHandler"
+    />
+
+    <!-- 历史 -->
+    <AppGroupItem title="历史" :apps="historyApps" @itemHandler="itemHandler" />
+
+    <!-- 其他 -->
+    <AppGroupItem title="其他" :apps="otherApps" @itemHandler="itemHandler" />
+
+    <up-toast ref="uToastRef" />
+  </view>
+</template>
+
+<style lang="scss">
+.top-number {
+  .u-grid {
+    border-radius: 20rpx;
+    background-color: #1296db;
+    padding: 20rpx 0;
+  }
+  .u-grid-item {
+    border-right: 1px solid #dbfbdb;
+    &:last-child {
+      border-right: none;
+    }
+  }
+  .u-grid-item--hover-class {
+    opacity: 1 !important;
+  }
+}
+</style>

BIN
elevator-ui/bak/src/pages/views/.DS_Store


+ 20 - 0
elevator-ui/bak/src/pages/views/about-app/index.vue

@@ -0,0 +1,20 @@
+<script setup>
+import { useCdn } from '@/composables/use-cdn'
+const { cdnUrl } = useCdn('/elevator/2024/12/30')
+
+const miniProgram = ref(null)
+const accountInfo = uni.getAccountInfoSync()
+miniProgram.value = accountInfo.miniProgram
+</script>
+
+<template>
+  <view w-full h-100vh f-s-c flex-col justify-between pt-50 pb-60>
+    <up-image
+      :src="`${cdnUrl}/b7e2194e233e472fb25dc5bd3bc700de.png`"
+      width="250rpx"
+      height="250rpx"
+    />
+
+    <view>当前版本:{{ miniProgram?.version || '1.0.0' }}</view>
+  </view>
+</template>

+ 30 - 0
elevator-ui/bak/src/pages/views/basic-info/components/CardCellData.vue

@@ -0,0 +1,30 @@
+<script setup>
+defineProps({
+  info: {
+    type: Object,
+    default: () => ({
+      title: '',
+      keyValue: []
+    })
+  },
+  fieldResult: {
+    type: Object,
+    default: () => ({})
+  }
+})
+</script>
+
+<template>
+  <up-card :title="info.title" :head-style="{ fontWeight: 'bold' }">
+    <template #body>
+      <up-cell-group :border="false">
+        <up-cell
+          v-for="(item, index) in info.keyValue"
+          :key="index"
+          :title="item.key"
+          :value="fieldResult[item.field] || item.value || '无'"
+        />
+      </up-cell-group>
+    </template>
+  </up-card>
+</template>

+ 99 - 0
elevator-ui/bak/src/pages/views/basic-info/feedback.vue

@@ -0,0 +1,99 @@
+<script setup>
+import { postPassengerFeedbackListApi } from '@/api/elevator'
+
+const form = ref(null)
+const model = ref({
+  content: '',
+  contacts: '',
+  contactsPhone: ''
+})
+const rules = ref({
+  content: {
+    type: 'string',
+    required: true,
+    message: '请输入反馈内容',
+    trigger: ['change']
+  }
+})
+
+const submit = () => {
+  // 如果有错误,会在catch中返回报错信息数组,校验通过则在then中返回true
+  form.value
+    .validate()
+    .then(async (res) => {
+      console.log(res)
+      const { elevatorNum } = options.value || {}
+      const { content, contacts, contactsPhone } = model.value
+      const { error } = await postPassengerFeedbackListApi({
+        elevatorNum,
+        content,
+        contacts,
+        contactsPhone
+      })
+      if (error) {
+        return uni.$u.toast('提交失败')
+      }
+      uni.$u.toast('提交成功')
+      setTimeout(() => {
+        uni.navigateBack()
+      }, 1500)
+    })
+    .catch((errors) => {
+      console.log(errors)
+      uni.$u.toast('请检查输入内容')
+    })
+}
+
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+})
+</script>
+
+<template>
+  <view min-h-100vh px-30>
+    <up-form
+      ref="form"
+      label-width="80"
+      label-position="top"
+      :model="model"
+      :rules="rules"
+    >
+      <up-form-item label="反馈内容:" prop="content" :border="`${'none'}`">
+        <up-textarea
+          v-model="model.content"
+          placeholder="请输入反馈内容"
+          maxlength="300"
+          count
+          height="150"
+        />
+      </up-form-item>
+      <!-- 联系人 和 联系方式 -->
+      <up-form-item label="联系人:" prop="contacts" :border="`${'none'}`">
+        <up-input
+          v-model="model.contacts"
+          placeholder="请输入联系人"
+          maxlength="20"
+        />
+      </up-form-item>
+      <up-form-item
+        label="联系方式:"
+        prop="contactsPhone"
+        :border="`${'none'}`"
+      >
+        <up-input
+          v-model="model.contactsPhone"
+          placeholder="请输入联系方式"
+          maxlength="20"
+        />
+      </up-form-item>
+    </up-form>
+
+    <up-button
+      type="primary"
+      text="提交"
+      custom-style="margin-top: 30px"
+      @click="submit"
+    />
+  </view>
+</template>

+ 145 - 0
elevator-ui/bak/src/pages/views/basic-info/index.vue

@@ -0,0 +1,145 @@
+<script setup>
+import dayjs from 'dayjs'
+import CardCellData from './components/CardCellData.vue'
+import { getElevatorIdInfoWxApi } from '@/api/elevator'
+import { useFetch } from '@/composables/use-fetch'
+import { useUniApi } from '@/composables/use-uni-api'
+import { useUserStore } from '@/store/user'
+import { Qs } from '@/utils/common'
+
+const { navigateTo } = useUniApi()
+
+// 电梯信息
+const baseInfo = ref({
+  title: '电梯信息',
+  // 注册代码 登记证号 内部使用编号 电梯号码 电梯名称 电梯地址 梯龄 上次维保时间 年检日期
+  keyValue: [
+    { key: '注册代码', value: '', field: 'registerCode' },
+    { key: '登记证号', value: '', field: 'registerNumber' },
+    { key: '内部使用编号', value: '', field: 'innerCode' },
+    { key: '电梯号码', value: '', field: 'elevatorNum' },
+    { key: '电梯名称', value: '', field: 'elevatorName' },
+    { key: '电梯地址', value: '', field: 'elevatorAddress' },
+    { key: '梯龄', value: '', field: 'elevatorAge' },
+    { key: '上次维保时间', value: '', field: 'lastMaintenanceTime' },
+    { key: '年检日期', value: '', field: 'annualDate' }
+  ]
+})
+
+// 物业管理信息
+const propertyInfo = ref({
+  title: '物业管理信息',
+  // 单位名称 单位地址 联系人 联系电话
+  keyValue: [
+    { key: '单位名称', value: '', field: 'name' },
+    { key: '联系人', value: '', field: 'contacts' },
+    { key: '联系电话', value: '', field: 'phone' }
+  ]
+})
+
+// 维保管理信息
+const maintenanceInfo = ref({
+  title: '维保管理信息',
+  // 单位名称 单位地址 联系人 联系电话
+  keyValue: [
+    { key: '单位名称', value: '', field: 'name' },
+    { key: '联系人', value: '', field: 'principal' },
+    { key: '联系电话', value: '', field: 'principalPhone' }
+  ]
+})
+
+const { result, fetchApi } = useFetch({
+  apiFn: getElevatorIdInfoWxApi,
+  formatResult: (r) => {
+    const date = r.installDate
+    if (date) {
+      const y = dayjs().diff(date, 'year')
+      const m = dayjs().diff(date, 'month') % 12
+      r.elevatorAge = `${y}年${m}个月`
+    }
+    return r
+  }
+})
+const options = ref({})
+onLoad(async (e) => {
+  // uni.hideHomeButton()
+  options.value = e
+  await fetchApi(e)
+  // checkUserPermission()
+})
+
+const checkUserPermission = () => {
+  if (isVisitor.value) {
+    return
+  }
+
+  uni.showHomeButton()
+}
+
+// 是否维保人员
+const isMain = computed(() => {
+  const { roles } = storeToRefs(useUserStore())
+  return roles.value && roles.value.includes('maintenance')
+})
+
+// 是否访客
+const isVisitor = computed(() => {
+  const { userInfo } = storeToRefs(useUserStore())
+  if (!userInfo.value || !Object.keys(userInfo.value).length) {
+    return true
+  }
+  return false
+})
+
+const toHome = () => {
+  uni.reLaunch({
+    url: '/pages/index/index'
+  })
+}
+const toFeedback = () => {
+  const { elevatorNum } = result.value
+  const urlParams = Qs.stringify({ ...options.value, elevatorNum })
+  navigateTo(`/pages/views/basic-info/feedback?${urlParams}`)
+}
+const toMaintenance = () => {
+  // navigateTo('/pages/views/elevator-maintenance/index')
+  uni.reLaunch({
+    url: '/pages/views/elevator-maintenance/index'
+  })
+}
+const toMaintain = () => {
+  // navigateTo('/pages/views/elevator-maintain/index')
+  uni.reLaunch({
+    url: '/pages/views/elevator-maintain/index'
+  })
+}
+</script>
+
+<template>
+  <view class="elevator-data" pb-30>
+    <CardCellData :info="baseInfo" :field-result="result" />
+    <CardCellData :info="propertyInfo" :field-result="result.tenementInfo" />
+    <CardCellData
+      :info="maintenanceInfo"
+      :field-result="result.maintenanceInfo"
+    />
+
+    <view v-if="isMain" f-c-c justify-between px-30 mt-30>
+      <up-button type="primary" text="去维保" @click="toMaintenance" />
+      <view flex-none w-30 />
+      <up-button type="warning" text="去维修" @click="toMaintain" />
+    </view>
+    <view v-else-if="isVisitor" f-c-c px-30 mt-30>
+      <up-button type="primary" text="乘客意见" @click="toFeedback" />
+    </view>
+    <view v-else f-c-c px-30 mt-30>
+      <up-button type="primary" text="返回首页" @click="toHome" />
+    </view>
+  </view>
+</template>
+
+<style>
+.elevator-data .u-card__body {
+  padding: 0 !important;
+}
+</style>

+ 67 - 0
elevator-ui/bak/src/pages/views/big-screen/index.vue

@@ -0,0 +1,67 @@
+<script setup>
+import { getElevatorLargeScreenListApi } from '@/api/elevator'
+import { usePaging } from '@/composables/use-paging'
+
+const webView = '/pages/views/web-view/index'
+const toDetail = (item) => {
+  console.log('toDetail =>', item)
+  const { name, areaCode, address } = item
+  const domain = address.replace(
+    'http://111.172.230.93:7001',
+    'https://h5.hbrja.com'
+  )
+
+  let codeList = []
+  try {
+    codeList = JSON.parse(areaCode)
+  } catch (error) {
+    console.log('获取区域码失败', error)
+  }
+  const code = codeList[codeList.length - 1]
+  const link = `${domain}?areaCode=${code}&name=${name}`
+  const url = `${webView}?url=${encodeURIComponent(link)}&hasBack=1`
+
+  uni.navigateTo({
+    url
+  })
+}
+
+const query = ref({})
+const { paging, dataList, queryHandler } = usePaging({
+  apiFn: getElevatorLargeScreenListApi,
+  query
+})
+
+const options = ref({})
+onLoad(async (e) => {
+  dataList.value = []
+  options.value = e
+})
+</script>
+
+<template>
+  <view class="passenger-feedback" w-full h-100vh flex-col f-s-c bg="#F8F8F8">
+    <view w-full flex-auto overflow-hidden mt-8 bg-white>
+      <z-paging
+        ref="paging"
+        v-model="dataList"
+        :fixed="false"
+        @query="queryHandler"
+      >
+        <up-card
+          v-for="(item, index) in dataList"
+          :key="index"
+          :title="item.name"
+          @click="toDetail(item)"
+        >
+          <template #body>
+            <view>
+              <up-cell title="名称" :value="item.address" />
+              <up-cell title="备注" :value="item.remark" />
+            </view>
+          </template>
+        </up-card>
+      </z-paging>
+    </view>
+  </view>
+</template>

+ 84 - 0
elevator-ui/bak/src/pages/views/check-in/components/CalendarTab.vue

@@ -0,0 +1,84 @@
+<script setup>
+import dayjs from 'dayjs'
+import { useFetch } from 'hooks/use-fetch'
+import { getSignInListApi } from '@/api/elevator'
+// 3 年前的年月日
+const startDate = dayjs().subtract(3, 'year').format('YYYY-MM-DD')
+// 当前的年月日(本月最后一天)
+const endDate = dayjs().endOf('month').format('YYYY-MM-DD')
+const change = (e) => {
+  console.log(e)
+  const { fulldate } = e
+  query.value.pageNum = 1
+  // query.value.fulldate = fulldate
+  query.value.startTime = `${fulldate} 00:00:00`
+  query.value.endTime = `${fulldate} 23:59:59`
+  dataList.value = []
+  getList()
+}
+
+const dataList = ref([])
+// TODO: 首次请求 50 条,前端暂不做分页
+const query = ref({
+  pageNum: 1,
+  pageSize: 50,
+  startTime: `${dayjs().format('YYYY-MM-DD')} 00:00:00`,
+  endTime: `${dayjs().format('YYYY-MM-DD')} 23:59:59`
+})
+const { result, fetchApi } = useFetch({
+  apiFn: getSignInListApi,
+  isList: true
+})
+const getList = async () => {
+  await fetchApi({
+    ...options.value,
+    ...query.value
+  })
+  dataList.value.push(...result.value.rows)
+}
+const options = ref({})
+onLoad(async (e) => {
+  dataList.value = []
+  options.value = e
+})
+onMounted(async () => {
+  console.log('onMounted', options.value)
+  await getList()
+})
+</script>
+
+<template>
+  <view>
+    <uni-calendar
+      :insert="true"
+      :lunar="true"
+      :start-date="startDate"
+      :end-date="endDate"
+      @change="change"
+    />
+
+    <view mt-30>
+      <up-list>
+        <up-list-item
+          v-for="(item, index) in dataList"
+          :key="index"
+          :anchor="index"
+        >
+          <up-cell :title="item.signTime">
+            <template #label>
+              <view mt-8>
+                <view f-s-c mt-4 text-28 text="#999999">
+                  <up-icon name="map-fill" :size="24" :color="`${'#999999'}`" />
+                  <text text-28 font-400 ml-4>{{ item.address }}</text>
+                </view>
+                <view mt-4>备注:{{ item.remark }}</view>
+              </view>
+            </template>
+          </up-cell>
+        </up-list-item>
+      </up-list>
+
+      <!-- <up-loadmore :line="true" status="nomore" /> -->
+    </view>
+  </view>
+</template>

+ 195 - 0
elevator-ui/bak/src/pages/views/check-in/components/CheckInTab.vue

@@ -0,0 +1,195 @@
+<script setup>
+import dayjs from 'dayjs'
+import { useFetch } from 'hooks/use-fetch'
+import { useUserStore } from 'store/user'
+import { getSignInListApi, postSignInApi } from '@/api/elevator'
+
+const checkInTime = ref('')
+checkInTime.value = dayjs().format('HH:mm:ss')
+let timer = null
+timer = setInterval(() => {
+  checkInTime.value = dayjs().format('HH:mm:ss')
+}, 1000)
+
+const checkInHandler = () => {
+  uni.getLocation({
+    type: 'gcj02',
+    isHighAccuracy: true,
+    success: ({ latitude, longitude }) => {
+      uni.chooseLocation({
+        latitude,
+        longitude,
+        success: ({ address }) => {
+          showModal.value = true
+          userPosition.value = {
+            ...userPosition.value,
+            address
+          }
+        },
+        fail: (err) => {
+          console.log('err:', err)
+          uni.$u.toast('获取位置信息失败')
+        }
+      })
+    }
+  })
+}
+
+const userStore = useUserStore()
+const { getUserInfo } = storeToRefs(userStore)
+const userInfo = computed(() => getUserInfo.value)
+const userPosition = ref({})
+const showModal = ref(false)
+const checkInContent = ref('')
+const confirm = async () => {
+  const { nickName } = userStore.getUserInfo
+  const { latitude, longitude, address } = userPosition.value
+  const params = {
+    userName: nickName,
+    signTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+    address,
+    lat: latitude,
+    lng: longitude,
+    remark: checkInContent.value
+  }
+  await postSignInApi(params)
+  uni.$u.toast('签到成功')
+  checkInContent.value = ''
+  dataList.value = []
+  getList()
+  showModal.value = false
+}
+const cancel = () => {
+  showModal.value = false
+}
+const close = () => {
+  showModal.value = false
+}
+
+const dataList = ref([])
+const hasMore = ref(true)
+const query = ref({
+  pageNum: 1,
+  pageSize: 10,
+  startTime: `${dayjs().format('YYYY-MM-DD')} 00:00:00`,
+  endTime: `${dayjs().format('YYYY-MM-DD')} 23:59:59`
+})
+const { result, fetchApi } = useFetch({
+  apiFn: getSignInListApi,
+  isList: true
+})
+const getList = async () => {
+  await fetchApi({
+    ...options.value,
+    ...query.value
+  })
+  dataList.value.push(...result.value.rows)
+  const { rows } = result.value
+  if (rows.length < query.value.pageSize) {
+    hasMore.value = false
+  }
+}
+const options = ref({})
+onLoad(async (e) => {
+  dataList.value = []
+  options.value = e
+  uni.getLocation({
+    type: 'wgs84',
+    isHighAccuracy: true,
+    success: (res) => {
+      userPosition.value = res
+    }
+  })
+})
+onMounted(async () => {
+  await getList()
+})
+
+onReachBottom(() => {
+  const { rows } = result.value
+  if (rows.length < query.value.pageSize) {
+    hasMore.value = false
+    return
+  } else {
+    query.value.pageNum++
+    getList()
+  }
+})
+
+onBeforeUnmount(() => {
+  clearInterval(timer)
+  timer = null
+})
+
+onUnmounted(() => {
+  clearInterval(timer)
+  timer = null
+})
+</script>
+
+<template>
+  <view>
+    <view w-full f-s-c px-28>
+      <UserAvatar />
+      <view ml-12>
+        <view text-28>{{ userInfo.nickName }}</view>
+        <view text-24 text-gray>{{ userInfo.deptName }}</view>
+      </view>
+    </view>
+
+    <!-- 签到 -->
+    <view f-c-c w-full h-500>
+      <view
+        w-250
+        h-250
+        bg="#50A9E9"
+        text-white
+        f-c-c
+        flex-col
+        rounded-full
+        shadow-cyan
+        @tap="checkInHandler"
+      >
+        <view text-36>签到</view>
+        <view text-24>{{ checkInTime }}</view>
+      </view>
+    </view>
+
+    <view mt-30>
+      <up-cell
+        v-for="(item, index) in dataList"
+        :key="index"
+        :title="item.signTime"
+      >
+        <template #label>
+          <view mt-8>
+            <view f-s-c mt-4 text-28 text="#999999">
+              <up-icon name="map-fill" :size="24" :color="`${'#999999'}`" />
+              <text text-28 font-400 ml-4>{{ item.address }}</text>
+            </view>
+            <view mt-4>备注:{{ item.remark }}</view>
+          </view>
+        </template>
+      </up-cell>
+
+      <up-loadmore
+        v-if="!hasMore && dataList.length"
+        :line="true"
+        status="nomore"
+      />
+    </view>
+  </view>
+
+  <!-- 退出提示 -->
+  <up-modal
+    title="签到"
+    :show="showModal"
+    show-cancel-button
+    close-on-click-overlay
+    @confirm="confirm"
+    @cancel="cancel"
+    @close="close"
+  >
+    <up-input v-model="checkInContent" placeholder="请输入签到内容" />
+  </up-modal>
+</template>

+ 21 - 0
elevator-ui/bak/src/pages/views/check-in/index.vue

@@ -0,0 +1,21 @@
+<script setup>
+import CalendarTab from './components/CalendarTab.vue'
+import CheckInTab from './components/CheckInTab.vue'
+
+const currTab = ref(0)
+const tabChange = (value) => {
+  currTab.value = value
+}
+</script>
+
+<template>
+  <view>
+    <CheckInTab v-if="currTab === 0" />
+    <CalendarTab v-else />
+
+    <up-tabbar :value="currTab" @change="tabChange">
+      <up-tabbar-item text="签到" icon="map-fill" />
+      <up-tabbar-item text="统计" icon="calendar-fill" />
+    </up-tabbar>
+  </view>
+</template>

+ 149 - 0
elevator-ui/bak/src/pages/views/choose-elevator/index.vue

@@ -0,0 +1,149 @@
+<script setup>
+import PositionPicker from 'components/PositionPicker.vue'
+import { useFetch } from 'hooks/use-fetch'
+import { throttle } from 'uview-plus'
+import { getElevatorListApi } from '@/api/elevator'
+import { Qs } from '@/utils/common'
+
+const upListRef = ref(null)
+const options = ref({})
+
+// 分页
+const scrolltolower = () => {
+  loadmore()
+}
+const params = ref({
+  pageNum: 1,
+  pageSize: 10,
+  elevatorName: '',
+  deptId: ''
+})
+const list = ref([])
+const { result: listData, fetchApi: fetchList } = useFetch({
+  apiFn: getElevatorListApi,
+  isList: true,
+  defaultResult: {
+    rows: [],
+    total: 0
+  }
+})
+const loadmore = async () => {
+  await fetchList(params.value)
+  list.value.push(...listData.value.rows)
+  if (listData.value.rows.length < params.value.pageSize) {
+    params.value.pageNum++
+  }
+}
+const resetParams = () => {
+  params.value = {
+    pageNum: 1,
+    pageSize: 10,
+    elevatorName: '',
+    deptId: ''
+  }
+  list.value = []
+}
+const clearHandler = () => {
+  resetParams()
+  loadmore()
+}
+const enterHandler = () => {
+  list.value = []
+  params.value.pageNum = 1
+  loadmore()
+}
+onLoad((e) => {
+  options.value = e
+  list.value = []
+  loadmore()
+})
+
+// 滚动
+const scrollTop = ref(0)
+const scrollChange = (value) => {
+  throttle(() => {
+    scrollTop.value = value
+  }, 250)
+}
+const handleBackTop = () => {
+  scrollTop.value = 0
+}
+
+const toDetail = (item) => {
+  const { redirect: url } = options.value
+  const { id, elevatorNum } = item
+  const { deptId } = params.value
+  const urlParams = Qs.stringify({
+    id,
+    deptId,
+    elevatorNum
+  })
+  const finalUrl = `${url}?${urlParams}`
+  uni.navigateTo({
+    url: finalUrl
+  })
+}
+
+const showPicker = ref(false)
+const place = ref({
+  id: '',
+  label: '请选择'
+})
+const confirmHandler = ([value]) => {
+  place.value = value
+  params.value.deptId = value.id
+  enterHandler()
+}
+</script>
+
+<template>
+  <view f-s-c flex-col h-100vh>
+    <view f-s-c px-20 mt-12 w-full flex-none h-100>
+      <up-input
+        :model-value="params.elevatorName"
+        placeholder="请输入搜素内容"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        @clear="clearHandler"
+        @confirm="enterHandler"
+        @change="(value) => (params.elevatorName = value)"
+      />
+      <view ml-12 @tap="showPicker = true">{{ place.label }}</view>
+    </view>
+
+    <view w-full flex-auto overflow-hidden>
+      <up-list
+        ref="upListRef"
+        :scroll-top="scrollTop"
+        @scroll="scrollChange"
+        @scrolltolower="scrolltolower"
+      >
+        <up-list-item
+          v-for="(item, index) in list"
+          :key="index"
+          :anchor="index"
+          @tap="toDetail(item)"
+        >
+          <up-cell :title="item.elevatorName">
+            <template #label>
+              <view mt-8>
+                <view text-28>注册代码:{{ item.registerCode }}</view>
+                <view f-s-c mt-4 text-28 text="#999999">
+                  <up-icon name="map-fill" :size="24" :color="`${'#999999'}`" />
+                  <text text-28 font-400 ml-4>{{ item.elevatorAddress }}</text>
+                </view>
+              </view>
+            </template>
+          </up-cell>
+        </up-list-item>
+      </up-list>
+    </view>
+
+    <!-- 回到顶部 -->
+    <up-back-top :scroll-top="scrollTop" :top="500" @tap="handleBackTop" />
+
+    <!-- 选择位置弹窗 -->
+    <PositionPicker v-model="showPicker" @confirm="confirmHandler" />
+  </view>
+</template>

+ 186 - 0
elevator-ui/bak/src/pages/views/choose-worker/index.vue

@@ -0,0 +1,186 @@
+<script setup>
+import PositionPicker from 'components/PositionPicker.vue'
+import { useFetch } from 'hooks/use-fetch'
+import { throttle } from 'uview-plus'
+import {
+  getWorkerInfoListApi,
+  postWorkDataReceiveApi,
+  putWorkDataApi
+} from '@/api/elevator'
+// import { Qs } from '@/utils/common'
+// import { useUniApi } from '@/composables/use-uni-api'
+
+const instance = getCurrentInstance().proxy
+const eventChannel = instance.getOpenerEventChannel()
+
+const upListRef = ref(null)
+const options = ref({})
+// const { navigateBack } = useUniApi()
+
+// 分页
+const scrolltolower = () => {
+  loadmore()
+}
+const params = ref({
+  pageNum: 1,
+  pageSize: 20,
+  employeeType: 1,
+  phoneName: '',
+  deptId: ''
+})
+const list = ref([])
+const { result: listData, fetchApi: fetchList } = useFetch({
+  apiFn: getWorkerInfoListApi,
+  isList: true,
+  defaultResult: {
+    rows: [],
+    total: 0
+  }
+})
+const loadmore = async () => {
+  await fetchList(params.value)
+  list.value.push(...listData.value.rows)
+  if (listData.value.rows.length < params.value.pageSize) {
+    params.value.pageNum++
+  }
+}
+const resetParams = () => {
+  params.value = {
+    pageNum: 1,
+    pageSize: 20,
+    phoneName: '',
+    deptId: ''
+  }
+  list.value = []
+}
+const clearHandler = () => {
+  resetParams()
+  loadmore()
+}
+const enterHandler = () => {
+  list.value = []
+  params.value.pageNum = 1
+  loadmore()
+}
+onLoad((e) => {
+  options.value = e
+  list.value = []
+  loadmore()
+})
+
+// 滚动
+const scrollTop = ref(0)
+const scrollChange = (value) => {
+  throttle(() => {
+    scrollTop.value = value
+  }, 250)
+}
+const handleBackTop = () => {
+  scrollTop.value = 0
+}
+
+const locations = ref({
+  latitude: 30.59276,
+  longitude: 114.30525
+})
+const getLocation = () => {
+  uni.getLocation({
+    type: 'gcj02',
+    success: ({ latitude, longitude }) => {
+      locations.value = {
+        latitude,
+        longitude
+      }
+    }
+  })
+}
+onMounted(() => {
+  getLocation()
+})
+const agreeHandler = async ({ id }) => {
+  const { id: workId } = options.value
+  const { latitude: lat, longitude: lng } = locations.value
+  const { error, result } = await postWorkDataReceiveApi({
+    collaborator: id,
+    workId,
+    lat,
+    lng
+  })
+  if (error) {
+    return uni.$u.toast(result.msg)
+  }
+  uni.$u.toast('接单成功')
+  updateMaintenanceNum()
+  eventChannel.emit('done', {})
+  setTimeout(() => {
+    uni.navigateBack({
+      delta: 2
+    })
+  }, 1500)
+}
+const updateMaintenanceNum = async () => {
+  const { id, workType, maintenanceNum } = options.value
+  await putWorkDataApi({
+    id,
+    workType,
+    maintenanceNum
+  })
+}
+
+const showPicker = ref(false)
+const place = ref({
+  id: '',
+  label: '请选择'
+})
+const confirmHandler = ([value]) => {
+  place.value = value
+  params.value.deptId = value.id
+  enterHandler()
+}
+</script>
+
+<template>
+  <view f-s-c flex-col h-100vh>
+    <view f-s-c px-20 mt-12 w-full flex-none h-100>
+      <up-input
+        :model-value="params.phoneName"
+        placeholder="请输入搜素内容"
+        prefix-icon="search"
+        prefix-icon-style="font-size: 22px;color: #909399"
+        clearable
+        @clear="clearHandler"
+        @confirm="enterHandler"
+        @change="(value) => (params.phoneName = value)"
+      />
+      <view ml-12 text-blue @tap="enterHandler">搜索</view>
+    </view>
+
+    <view w-full flex-auto overflow-hidden>
+      <up-list
+        ref="upListRef"
+        :scroll-top="scrollTop"
+        @scroll="scrollChange"
+        @scrolltolower="scrolltolower"
+      >
+        <up-list-item
+          v-for="(item, index) in list"
+          :key="index"
+          :anchor="index"
+          @tap="agreeHandler(item)"
+        >
+          <up-cell
+            :title="item.phoneName"
+            :label="item.relevancyUnit"
+            :value="item.phone"
+          />
+        </up-list-item>
+      </up-list>
+    </view>
+
+    <!-- 回到顶部 -->
+    <up-back-top :scroll-top="scrollTop" :top="500" @tap="handleBackTop" />
+
+    <!-- 选择位置弹窗 -->
+    <PositionPicker v-model="showPicker" @confirm="confirmHandler" />
+  </view>
+</template>

+ 30 - 0
elevator-ui/bak/src/pages/views/elevator-data/components/CardCellData.vue

@@ -0,0 +1,30 @@
+<script setup>
+defineProps({
+  info: {
+    type: Object,
+    default: () => ({
+      title: '',
+      keyValue: []
+    })
+  },
+  fieldResult: {
+    type: Object,
+    default: () => ({})
+  }
+})
+</script>
+
+<template>
+  <up-card :title="info.title" :head-style="{ fontWeight: 'bold' }">
+    <template #body>
+      <up-cell-group :border="false">
+        <up-cell
+          v-for="(item, index) in info.keyValue"
+          :key="index"
+          :title="item.key"
+          :value="fieldResult[item.field] || item.value || '无'"
+        />
+      </up-cell-group>
+    </template>
+  </up-card>
+</template>

+ 82 - 0
elevator-ui/bak/src/pages/views/elevator-data/index.vue

@@ -0,0 +1,82 @@
+<script setup>
+import dayjs from 'dayjs'
+import CardCellData from './components/CardCellData.vue'
+import { getElevatorInfoApi } from '@/api/elevator'
+import { useFetch } from '@/composables/use-fetch'
+
+// 电梯信息
+const baseInfo = ref({
+  title: '电梯信息',
+  // 注册代码 登记证号 内部使用编号 电梯号码 电梯名称 电梯地址 梯龄 上次维保时间 年检日期
+  keyValue: [
+    { key: '注册代码', value: '', field: 'registerCode' },
+    { key: '登记证号', value: '', field: 'registerNumber' },
+    { key: '内部使用编号', value: '', field: 'innerCode' },
+    { key: '电梯号码', value: '', field: 'elevatorNum' },
+    { key: '电梯名称', value: '', field: 'elevatorName' },
+    { key: '电梯地址', value: '', field: 'elevatorAddress' },
+    { key: '梯龄', value: '', field: 'elevatorAge' },
+    { key: '上次维保时间', value: '', field: 'lastMaintenanceTime' },
+    { key: '年检日期', value: '', field: 'annualDate' }
+  ]
+})
+
+// 物业管理信息
+const propertyInfo = ref({
+  title: '物业管理信息',
+  // 单位名称 单位地址 联系人 联系电话
+  keyValue: [
+    { key: '单位名称', value: '', field: 'name' },
+    { key: '单位地址', value: '', field: 'address' },
+    { key: '联系人', value: '', field: 'contacts' },
+    { key: '联系电话', value: '', field: 'phone' }
+  ]
+})
+
+// 维保管理信息
+const maintenanceInfo = ref({
+  title: '维保管理信息',
+  // 单位名称 单位地址 联系人 联系电话
+  keyValue: [
+    { key: '单位名称', value: '', field: 'name' },
+    { key: '单位地址', value: '', field: 'address' },
+    { key: '联系人', value: '', field: 'principal' },
+    { key: '联系电话', value: '', field: 'principalPhone' }
+  ]
+})
+
+const { result, fetchApi } = useFetch({
+  apiFn: getElevatorInfoApi,
+  formatResult: (r) => {
+    const date = r.installDate
+    if (date) {
+      const y = dayjs().diff(date, 'year')
+      const m = dayjs().diff(date, 'month') % 12
+      r.elevatorAge = `${y}年${m}个月`
+    }
+    return r
+  }
+})
+const options = ref({})
+onLoad(async (e) => {
+  options.value = e
+  await fetchApi(e)
+})
+</script>
+
+<template>
+  <view class="elevator-data" pb-30>
+    <CardCellData :info="baseInfo" :field-result="result" />
+    <CardCellData :info="propertyInfo" :field-result="result.tenementInfo" />
+    <CardCellData
+      :info="maintenanceInfo"
+      :field-result="result.maintenanceInfo"
+    />
+  </view>
+</template>
+
+<style>
+.elevator-data .u-card__body {
+  padding: 0 !important;
+}
+</style>

+ 0 - 0
elevator-ui/bak/src/pages/views/elevator-examine/detail.vue


Деякі файли не було показано, через те що забагато файлів було змінено