瀏覽代碼

整体代码首次提交

fanghuisheng 1 月之前
父節點
當前提交
7682a6e837
共有 89 個文件被更改,包括 10684 次插入0 次删除
  1. 18 0
      AndroidManifest.xml
  2. 二進制
      favicon.ico
  3. 29 0
      index.html
  4. 9 0
      jsconfig.json
  5. 二進制
      nativeplugins/device_plugin/android/device_plugin-release.aar
  6. 39 0
      nativeplugins/device_plugin/package.json
  7. 二進制
      nativeplugins/opencv_plugin/android/opencv_plugin-release.aar
  8. 19 0
      nativeplugins/opencv_plugin/package.json
  9. 80 0
      package.json
  10. 84 0
      src/App.vue
  11. 71 0
      src/api/business/door.js
  12. 17 0
      src/api/business/face.js
  13. 289 0
      src/api/business/meeting.js
  14. 64 0
      src/api/index/index.js
  15. 54 0
      src/api/login.js
  16. 52 0
      src/api/system/dict.js
  17. 21 0
      src/api/system/setting.js
  18. 117 0
      src/api/system/user.js
  19. 106 0
      src/components/calendar/calendar.vue
  20. 175 0
      src/components/helang-compress/helang-compress.vue
  21. 123 0
      src/components/mpvue-echarts/src/echarts.vue
  22. 73 0
      src/components/mpvue-echarts/src/wx-canvas.js
  23. 546 0
      src/components/oa-calendar/calendar.js
  24. 12 0
      src/components/oa-calendar/i18n/en.json
  25. 8 0
      src/components/oa-calendar/i18n/index.js
  26. 12 0
      src/components/oa-calendar/i18n/zh-Hans.json
  27. 12 0
      src/components/oa-calendar/i18n/zh-Hant.json
  28. 188 0
      src/components/oa-calendar/uni-calendar-item.vue
  29. 607 0
      src/components/oa-calendar/uni-calendar.vue
  30. 350 0
      src/components/oa-calendar/util.js
  31. 65 0
      src/components/oa-dropdown/index.vue
  32. 149 0
      src/components/oa-movable/index.vue
  33. 210 0
      src/components/oa-passBody/index.vue
  34. 二進制
      src/components/oa-passBody/qingchu.png
  35. 264 0
      src/components/oa-scroll/index.vue
  36. 95 0
      src/components/oa-steps/index.vue
  37. 56 0
      src/components/oa-tabbar/index.vue
  38. 115 0
      src/components/oa-timeLine-item/index.vue
  39. 62 0
      src/components/oa-timeLine/index.vue
  40. 90 0
      src/components/oa-touch/index.vue
  41. 119 0
      src/components/oa-transForm/index.vue
  42. 70 0
      src/components/oa-ttsAudio/index.vue
  43. 91 0
      src/components/oa-ttsAudio/ttsAudio.js
  44. 303 0
      src/components/oa-upgrade/index.vue
  45. 205 0
      src/components/oa-upload/index.vue
  46. 111 0
      src/components/oa-weather/index.vue
  47. 83 0
      src/components/searchSelect/searchSelect.vue
  48. 236 0
      src/components/yealuo-select/yealuo-select.vue
  49. 115 0
      src/components/zzlb-mutiselect/zzlb-mutiselect.vue
  50. 53 0
      src/config.js
  51. 63 0
      src/main.js
  52. 254 0
      src/manifest.json
  53. 64 0
      src/pages.json
  54. 252 0
      src/pages/door/index.vue
  55. 175 0
      src/pages/door/setting.vue
  56. 188 0
      src/pages/door/setting/index.vue
  57. 122 0
      src/pages/door/setting/serve/index.vue
  58. 426 0
      src/pages/face/index.vue
  59. 62 0
      src/permission.js
  60. 60 0
      src/plugins/auth.plugins.js
  61. 315 0
      src/plugins/common.plugins.js
  62. 277 0
      src/plugins/constData.plugins.js
  63. 71 0
      src/plugins/index.js
  64. 72 0
      src/plugins/jsencrypt.js
  65. 128 0
      src/plugins/keyListen.plugins.js
  66. 94 0
      src/plugins/modal.plugins.js
  67. 298 0
      src/plugins/nfc.plugins.js
  68. 238 0
      src/plugins/permission.plugins.js
  69. 98 0
      src/plugins/setting.plugins.js
  70. 32 0
      src/plugins/tab.plugins.js
  71. 217 0
      src/plugins/time.plugins.js
  72. 8 0
      src/store/getters.js
  73. 8 0
      src/store/index.js
  74. 129 0
      src/store/modules/common.js
  75. 424 0
      src/store/modules/control.js
  76. 11 0
      src/store/modules/index.js
  77. 230 0
      src/store/modules/setting.js
  78. 21 0
      src/store/modules/system.js
  79. 67 0
      src/uni.scss
  80. 13 0
      src/utils/auth.js
  81. 17 0
      src/utils/dict.js
  82. 48 0
      src/utils/hideHead.js
  83. 38 0
      src/utils/jsencrypt.js
  84. 57 0
      src/utils/jssdk.js
  85. 132 0
      src/utils/permission.js
  86. 152 0
      src/utils/request.js
  87. 23 0
      src/utils/storage.js
  88. 91 0
      src/utils/upgrade.js
  89. 42 0
      vite.config.js

+ 18 - 0
AndroidManifest.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" standalone="no" ?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="33" android:compileSdkVersionCodename="13" package="android.dcloud.uskyMobile" platformBuildVersionCode="33" platformBuildVersionName="13">
+	<application android:allowBackup="false" android:allowClearUserData="true" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:debuggable="false" android:extractNativeLibs="true" android:hardwareAccelerated="true" android:icon="@drawable/icon" android:label="@string/app_name" android:largeHeap="true" android:name="io.dcloud.application.DCloudApplication" android:supportsRtl="true" android:usesCleartextTraffic="true">
+        <activity android:configChanges="fontScale|keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize" android:exported="true" android:hardwareAccelerated="true" android:label="@string/app_name" android:name="io.dcloud.PandoraEntry" android:screenOrientation="user" android:theme="@style/DCloudTranslucentTheme" android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+				<action android:name="android.intent.action.MAIN"/>
+				<category android:name="android.intent.category.LAUNCHER"/>
+				<category android:name="android.intent.category.HOME"/>
+				<category android:name="android.intent.category.DEFAULT"/>
+			</intent-filter>
+			<intent-filter>
+				<action android:name="android.intent.action.VIEW"/>
+				<category android:name="android.intent.category.DEFAULT"/>
+				<category android:name="android.intent.category.BROWSABLE"/>
+			</intent-filter>
+		</activity>	
+    </application>
+</manifest>

二進制
favicon.ico


+ 29 - 0
index.html

@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+	<meta charset="UTF-8" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge">
+	<meta name="viewport" content="width=device-width,initial-scale=1.0">
+	<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico">
+	<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>

+ 9 - 0
jsconfig.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "types": [
+      "@dcloudio/types",
+      "miniprogram-api-typings",
+      "mini-types"
+    ]
+  }
+}

二進制
nativeplugins/device_plugin/android/device_plugin-release.aar


+ 39 - 0
nativeplugins/device_plugin/package.json

@@ -0,0 +1,39 @@
+{
+    "name": "device_plugin",
+    "id": "device_plugin",
+    "version": "1.0.0",
+    "description": "集成插件",
+    "_dp_type": "nativeplugin",
+    "_dp_nativeplugin": {
+        "android": {
+            "plugins": [
+                {
+                    "type": "module",
+                    "name": "yxDevice",
+                    "class": "com.example.yxDevice.test"
+                },
+                {
+                    "type": "module",
+                    "name": "my",
+                    "class": "com.example.my.MyUniModule"
+                },
+                {
+                    "type": "module",
+                    "name": "temp",
+                    "class": "com.example.temp.test"
+                },
+                {
+                    "type": "module",
+                    "name": "opencv",
+                    "class": "com.example.opencv.test"
+                },
+                {
+                    "type": "module",
+                    "name": "serialPort",
+                    "class": "com.example.serialPort.UniSerialPort"
+                }
+            ],
+            "integrateType": "aar"
+        }
+    }
+}

二進制
nativeplugins/opencv_plugin/android/opencv_plugin-release.aar


+ 19 - 0
nativeplugins/opencv_plugin/package.json

@@ -0,0 +1,19 @@
+{
+    "name": "opencv_plugin",
+    "id": "opencv_plugin",
+    "version": "1.0.0",
+    "description": "opencv插件",
+    "_dp_type": "nativeplugin",
+    "_dp_nativeplugin": {
+        "android": {
+            "plugins": [
+                {
+                    "type": "module",
+                    "name": "opencv_plugin",
+                    "class": "org.opencv.face.FaceDetectionHelper"
+                }
+            ],
+            "integrateType": "aar"
+        }
+    }
+}

+ 80 - 0
package.json

@@ -0,0 +1,80 @@
+{
+  "name": "uni-preset-vue",
+  "version": "0.0.0",
+  "scripts": {
+    "dev:app": "uni -p app",
+    "dev:custom": "uni -p",
+    "dev:h5": "uni",
+    "dev:h5:prod": "uni --mode production",
+    "dev:h5:ssr": "uni --ssr",
+    "dev:mp-alipay": "uni -p mp-alipay",
+    "dev:mp-baidu": "uni -p mp-baidu",
+    "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: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:custom": "uni build -p",
+    "build:h5:prod": "rimraf dist && uni build --mode production",
+    "build:h5:dev": "rimraf dist && uni build --mode development",
+    "build:h5:ssr": "uni build --ssr",
+    "build:mp-alipay": "uni build -p mp-alipay",
+    "build:mp-baidu": "uni build -p mp-baidu",
+    "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: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",
+    "clear": "rimraf node_modules && npm install --force",
+    "clean": "npm cache clean --force",
+    "update:setting": "node unpackage/config/setting.js"
+  },
+  "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
+    "@dcloudio/uni-app": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-app-harmony": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-app-plus": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-components": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-h5": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-alipay": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-baidu": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-jd": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-lark": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-qq": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-toutiao": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-weixin": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-mp-xhs": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4020320240703001",
+    "echarts": "^5.3.3",
+    "jsencrypt": "^3.3.2",
+    "pinia": "2.0.14",
+    "pinia-plugin-persistedstate": "^3.1.0",
+    "vue": "^3.4.21",
+    "vue-echarts": "^6.2.3",
+    "vue-i18n": "^9.1.9",
+    "vue-json-excel": "^0.3.0",
+    "vuex": "^4.0.2",
+    "weixin-js-sdk": "^1.6.0"
+  },
+  "devDependencies": {
+    "@dcloudio/types": "^3.4.8",
+    "@dcloudio/uni-automator": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-cli-shared": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/uni-stacktracey": "3.0.0-alpha-4020320240703001",
+    "@dcloudio/vite-plugin-uni": "3.0.0-alpha-4020320240703001",
+    "@vitejs/plugin-vue": "^2.3.3",
+    "@vue/runtime-core": "^3.4.21",
+    "cross-env": "^7.0.3",
+    "dayjs": "^1.11.7",
+    "sass": "1.57.1",
+    "vite": "5.2.8"
+  }
+}

+ 84 - 0
src/App.vue

@@ -0,0 +1,84 @@
+<script setup>
+/*----------------------------------依赖引入-----------------------------------*/
+import { onLoad, onShow, onHide, onLaunch, onReady, onBackPress } from "@dcloudio/uni-app";
+import { defineComponent, getCurrentInstance, inject, nextTick, onMounted, watchEffect, ref } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+/*----------------------------------公共方法引入-----------------------------------*/
+import config from "./config";
+import { storage } from "@/utils/storage";
+/*----------------------------------公共变量-----------------------------------*/
+const { proxy } = getCurrentInstance();
+/*----------------------------------变量声明-----------------------------------*/
+function initApp() {
+  proxy.$settingStore.initThemeColor(storage.get("themeColor")); //初始化默认主题
+  proxy.$settingStore.systemHeightTop(); //初始化获取安全区高度
+  //#ifdef APP-PLUS
+  stteingInit();
+  //#endif
+}
+
+//设置初始化
+function stteingInit() {
+  plus.screen.unlockOrientation(); //解除屏幕方向的锁定,但是不一定是竖屏;
+  // 智能会议
+  if (config.appInfo.appid === "__UNI__F3963F8") {
+    plus.screen.lockOrientation("landscape-primary"); //设置屏幕方向(1.竖屏正方向:portrait-primary 2.竖屏反方向:portrait-secondary 3.横屏正方向:landscape-primary 4.横屏反方向:landscape-secondary 5.自然方向:default)
+  }
+  // 智能门禁
+  else if (config.appInfo.appid === "__UNI__8D6E9FD") {
+    plus.screen.lockOrientation("portrait-primary"); //设置屏幕方向(1.竖屏正方向:portrait-primary 2.竖屏反方向:portrait-secondary 3.横屏正方向:landscape-primary 4.横屏反方向:landscape-secondary 5.自然方向:default)
+    plus.navigator.hideSystemNavigation(); //隐藏安卓底部虚拟导航键
+  }
+
+  proxy.$settingStore.pushListener(); //开启消息推送监听
+}
+
+watchEffect(() => {
+  //#ifdef APP-PLUS || MP-WEIXIN
+  if (uni.getStorageSync("serveUrl")) {
+    config.baseUrl = "http://" + uni.getStorageSync("serveUrl") + "/prod-api";
+  }
+  //#endif
+});
+
+onLaunch(() => {
+  console.log("App Launch");
+  initApp();
+});
+
+onReady(() => {});
+
+onShow(() => {
+  console.log("App Show");
+});
+
+onHide(() => {
+  console.log("App Hide");
+});
+</script>
+
+<style lang="scss">
+@import "@/static/scss/index.scss";
+</style>
+
+<style lang="scss">
+@import "@/uni_modules/uview-plus/index.scss";
+
+uni-page-body,
+uni-page-refresh {
+  height: 100%;
+}
+
+//默认返回按钮样式
+:deep(.uni-page-head-btn) {
+  background-color: rgba(0, 0, 0, 0) !important;
+}
+
+//默认头部导航title样式
+:deep(.uni-page-head__title) {
+  font-size: $uni-font-size-lg;
+  font-weight: bold;
+}
+</style>

+ 71 - 0
src/api/business/door.js

@@ -0,0 +1,71 @@
+import { request } from "@/utils/request";
+
+/**
+ * 门禁接口集合
+ * @method Select 列表查询
+ * @method Update 列表修改
+ * @method Insert 列表新增
+ * @method Delete 列表删除
+ * @method RecordInsert 记录列表新增
+ * @method RecordSelect 记录列表查询
+ * @method control 开门
+ */
+export function doorApi() {
+    return {
+        Select: (data) => {
+            return request({
+                url: `/service-eg/egDevice/page`,
+                method: 'POST',
+                data,
+            });
+        },
+        Update: (data) => {
+            return request({
+                url: `/service-eg/egDevice`,
+                method: 'PUT',
+                data,
+            });
+        },
+        Insert: (data) => {
+            return request({
+                url: `/service-eg/egDevice`,
+                method: 'POST',
+                data,
+            });
+        },
+        Delete: (data) => {
+            return request({
+                url: `/service-eg/egDevice/` + data,
+                method: 'DELETE',
+            });
+        },
+        RecordInsert: (data) => {
+            return request({
+                url: `/service-eg/egRecord`,
+                method: 'POST',
+                data,
+            });
+        },
+        MyPage: (data) => {
+            return request({
+                url: `/service-eg/egDevice/wePage`,
+                method: 'POST',
+                data,
+            });
+        },
+        RecordSelect: (data) => {
+            return request({
+                url: `/service-eg/egRecord/page`,
+                method: 'POST',
+                data,
+            });
+        },
+        control: (data) => {
+            return request({
+                url: `/service-eg/egDevice/control`,
+                method: 'GET',
+                data,
+            });
+        },
+    };
+}

+ 17 - 0
src/api/business/face.js

@@ -0,0 +1,17 @@
+import { request } from "@/utils/request";
+
+/**
+ * 人脸校验接口集合
+ * @method faceVef 人脸验证
+ */
+export function faceApi() {
+    return {
+        faceVef: (data) => {
+            return request({
+                url: '/service-meeting/meetingFace/vef',
+                method: 'POST',
+                data,
+            });
+        },
+    };
+}

+ 289 - 0
src/api/business/meeting.js

@@ -0,0 +1,289 @@
+import { request } from "@/utils/request";
+
+// 首页统计
+export function FirstPageStatistic(data) {
+    return request({
+        url: '/service-meeting/meetingRoom/FirstPageStatistic',
+        method: 'GET',
+        params: data
+    })
+}
+
+// 会议室列表
+export function dmMeetingRoomList(data) {
+    return request({
+        url: '/service-meeting/meetingRoom/dmMeetingRoomList',
+        method: 'POST',
+        data: data
+    })
+}
+
+// 会议室下拉列表
+export function MeetingRoomList(data) {
+    return request({
+        url: '/service-meeting/meetingRoom/MeetingRoomList',
+        method: 'GET',
+        params: data
+    })
+}
+
+
+
+/**
+ * 楼层管理api接口集合
+ * @method Select 列表
+ * @method Insert 新增
+ * @method Update 修改
+ * @method Delete 删除
+ */
+export function floorApi() {
+    return {
+        Select: (data) => {
+            return request({
+                url: '/service-meeting/meetingFloor/meetingFloorList',
+                method: 'POST',
+                data,
+            });
+        },
+        Insert: (data) => {
+            return request({
+                url: '/service-meeting/meetingFloor',
+                method: 'POST',
+                data
+            });
+        },
+        Update: (data) => {
+            return request({
+                url: '/service-meeting/meetingFloor',
+                method: 'PUT',
+                data
+            });
+        },
+        Delete: (data) => {
+            return request({
+                url: '/service-meeting/meetingFloor?floorId=' + data,
+                method: 'DELETE',
+            });
+        },
+    };
+}
+
+/**
+ * 设备管理api接口集合
+ * @method Select 列表
+ * @method Insert 新增
+ * @method Update 修改
+ * @method Delete 删除
+ */
+export function deviceApi() {
+    return {
+        Select: (data) => {
+            return request({
+                url: '/service-meeting/meetingDevice/meetingDeviceList',
+                method: 'POST',
+                data,
+            });
+        },
+        Insert: (data) => {
+            return request({
+                url: '/service-meeting/meetingDevice',
+                method: 'POST',
+                data
+            });
+        },
+        Update: (data) => {
+            return request({
+                url: '/service-meeting/meetingDevice',
+                method: 'PUT',
+                data
+            });
+        },
+        Delete: (data) => {
+            return request({
+                url: '/service-meeting/meetingDevice?deviceId=' + data,
+                method: 'DELETE',
+            });
+        },
+    };
+}
+
+
+/**
+ * 会议室管理api接口集合
+ * @method Select 列表
+ * @method Insert 新增
+ * @method Update 修改
+ * @method Delete 删除
+ */
+export function roomApi() {
+    return {
+        Select: (data) => {
+            return request({
+                url: '/service-meeting/meetingRoom/dmMeetingRoomList',
+                method: 'POST',
+                data,
+            });
+        },
+        Insert: (data) => {
+            return request({
+                url: '/service-meeting/meetingRoom',
+                method: 'POST',
+                data
+            });
+        },
+        Update: (data) => {
+            return request({
+                url: '/service-meeting/meetingRoom',
+                method: 'PUT',
+                data
+            });
+        },
+        Delete: (data) => {
+            return request({
+                url: '/service-meeting/meetingRoom?roomId=' + data,
+                method: 'DELETE',
+            });
+        },
+    };
+}
+
+//会议室详情
+export function meetingRoomDetails(data) {
+    return request({
+        url: '/service-meeting/meetingRoom/meetingRoomDetails',
+        method: 'GET',
+        params: data
+    })
+}
+
+//会议室预约情况列表
+export function MeetingRoomReservationList(data) {
+    return request({
+        url: '/service-meeting/meetingRoom/getMeetingRoomReservationList',
+        method: 'POST',
+        data: data
+    })
+}
+
+//会议室预约-新增会议
+export function meetingInfoAdd(data) {
+    return request({
+        url: '/service-meeting/meetingInfo/add',
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 会议室记录api接口集合
+ * @method Select 列表
+ * @method Cancel 取消
+ */
+export function recordApi() {
+    return {
+        Select: (data) => {
+            return request({
+                url: '/service-meeting/meetingInfo/meetingInfoList',
+                method: 'POST',
+                data,
+            });
+        },
+        Cancel: (data) => {
+            return request({
+                url: '/service-meeting/meetingInfo/cancel?meetingId=' + data,
+                method: 'POST',
+            });
+        },
+    };
+}
+
+/**
+ * 我的会议api接口集合
+ * @method Select 列表
+ * @method SignList 人员签到-列表
+ * @method InsertFile 会议文件-添加
+ * @method SelectFile 会议文件-列表
+ */
+export function myApi() {
+    return {
+        Select: (data) => {
+            return request({
+                url: '/service-meeting/meetingInfo/myMeetingList',
+                method: 'POST',
+                data,
+            });
+        },
+        SignList: (data) => {
+            return request({
+                url: '/service-meeting/meetingInfo/meetingSignList',
+                method: 'GET',
+                params: data
+            });
+        },
+        InsertFile: (data) => {
+            return request({
+                url: '/service-meeting/meetingFile/insertMeetingFile',
+                method: 'POST',
+                data,
+            });
+        },
+        SelectFile: (data) => {
+            return request({
+                url: '/service-meeting/meetingFile/meetingFileList',
+                method: 'POST',
+                data,
+            });
+        },
+    };
+}
+
+//门禁开门
+export function control(data) {
+    return request({
+        url: `/service-iot/deviceHttp/control`,
+        method: 'GET',
+        params: data
+    })
+}
+
+
+/**
+ * 会议室管理接口集合
+ * @method GetMeetingRoomList 会议室下拉列表
+ * @method GetMeetingRoomReservationList 会议预约详情列表
+ * @method Attendee 会议人员权限审核
+ */
+export function meetingApi() {
+    return {
+        GetMeetingRoomList(data) {
+            return request({
+                url: '/service-meeting/meetingRoom/MeetingRoomList',
+                method: 'GET',
+                params: data
+            })
+        },
+        GetMeetingRoomReservationList(data) {
+            return request({
+                url: '/service-meeting/meetingRoom/getMeetingRoomReservationList',
+                method: 'POST',
+                data: data
+            })
+        },
+        Attendee(data) {
+            return request({
+                url: '/service-meeting/meetingRoom/attendee',
+                method: 'GET',
+                params: data
+            })
+        }
+    }
+}
+
+//人员签到-人员签退
+export function signOnOut(data) {
+    return request({
+        url: '/service-meeting/meetingInfo/signOnOut',
+        method: 'POST',
+        data
+    })
+}

+ 64 - 0
src/api/index/index.js

@@ -0,0 +1,64 @@
+import { request } from "@/utils/request";
+
+// 动态菜单请求
+export function getAppRouters(param) {
+  return request({
+    url: "/system/sysMobileMenu/getAppRouters",
+    method: "GET",
+    data: param,
+  });
+}
+
+// 动态菜单请求
+export function getRouters(param) {
+  return request({
+    url: "/system/sysMobileMenu/getRouters",
+    method: "GET",
+    data: param,
+  });
+}
+
+
+// 扫码请求
+export function scan_push(param) {
+  return request({
+    url: "/ScanPush/scan_push",
+    method: "GET",
+    data: param,
+  });
+}
+
+//echarts图表请求
+export function getHomePageData(param) {
+  return request({
+    url: "/Index/getHomePageData",
+    method: "GET",
+    data: param,
+  });
+}
+
+// 未处理告警请求
+export function getFunctionalModuleStatistics(param) {
+  return request({
+    url: "/Index/getFunctionalModuleStatistics",
+    method: "GET",
+    data: param,
+  });
+}
+
+//扫码登录PC端
+export function qrCodeSend(param) {
+  return request({
+    url: "/system/qrCodeSend",
+    method: "GET",
+    data: param,
+  });
+}
+
+//移动端banner图查询
+export function getMobileBanner(param) {
+  return request({
+    url: "/system/sysMobileBanner/getMobileBanner?tenantId=" + param.tenantId,
+    method: "GET",
+  });
+}

+ 54 - 0
src/api/login.js

@@ -0,0 +1,54 @@
+import { request } from "@/utils/request";
+
+// 登录方法
+export function login(data) {
+  return request({
+    url: "/system/appLogin",
+    headers: {
+      isToken: false,
+    },
+    method: "POST",
+    data: data,
+  });
+}
+
+// 获取用户详细信息
+export function getInfo() {
+  return request({
+    url: "/system/getInfo",
+    method: "get",
+  });
+}
+
+// 退出方法
+export function logout(data) {
+  return request({
+    url: "/system/logout",
+    method: "delete",
+    params: data
+  });
+}
+
+// 获取验证码
+export function getCodeImg(data) {
+  return request({
+    url: "/system/SendSms/noteSending",
+    headers: {
+      isToken: false,
+    },
+    method: "GET",
+    data: data,
+  });
+}
+
+/** 获取登录页数据 */
+export function getMobileTenantConfig(data) {
+  return request({
+    url: "/system/sysMobileTenantConfig/getAppTenantConfig",
+    headers: {
+      isToken: false,
+    },
+    method: "GET",
+    data: data,
+  });
+}

+ 52 - 0
src/api/system/dict.js

@@ -0,0 +1,52 @@
+import { request } from "@/utils/request";
+
+// 查询字典数据列表
+export function listData(query) {
+  return request({
+    url: '/system/dict/data/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询字典数据详细
+export function getData(dictCode) {
+  return request({
+    url: '/system/dict/data/' + dictCode,
+    method: 'get'
+  })
+}
+
+// 根据字典类型查询字典数据信息
+export function getDicts(dictType) {
+  return request({
+    url: '/system/dict/data/type/' + dictType,
+    method: 'get'
+  })
+}
+
+// 新增字典数据
+export function addData(data) {
+  return request({
+    url: '/system/dict/data',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改字典数据
+export function updateData(data) {
+  return request({
+    url: '/system/dict/data',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除字典数据
+export function delData(dictCode) {
+  return request({
+    url: '/system/dict/data/' + dictCode,
+    method: 'delete'
+  })
+}

+ 21 - 0
src/api/system/setting.js

@@ -0,0 +1,21 @@
+import { request, uploads } from '@/utils/request';
+
+// 检测App是否有更新
+export function checkUpdates(data) {
+    return request({
+        baseUrl: "https://www.pgyer.com",
+        url: '/apiv2/app/check',
+        method: 'POST',
+        params: data
+    })
+}
+
+// 安装 App
+export function install(data) {
+    return request({
+        baseUrl: "https://www.pgyer.com",
+        url: '/apiv2/app/install',
+        method: 'GET',
+        params: data
+    })
+}

+ 117 - 0
src/api/system/user.js

@@ -0,0 +1,117 @@
+import { request, uploads } from '@/utils/request';
+
+// 用户密码重置
+export function updateUserPwd(oldPassword, newPassword) {
+  const data = {
+    oldPassword,
+    newPassword
+  }
+  return request({
+    url: '/system/user/profile/updatePwd',
+    method: 'put',
+    params: data
+  })
+}
+
+// 查询用户个人信息
+export function getUserProfile() {
+  return request({
+    url: '/system/user/profile',
+    method: 'get'
+  })
+}
+
+// 修改用户个人信息
+export function updateUserProfile(data) {
+  return request({
+    url: '/system/user/profile',
+    method: 'put',
+    data: data
+  })
+}
+
+// 用户头像上传
+export function uploadAvatar(data) {
+  return uploads({
+    url: "/service-file/upload",
+    name: data.name,
+    filePath: data.filePath,
+  });
+}
+
+// 注册用户个人信息
+export function appAdd(data) {
+  return request({
+    url: '/system/user/appAdd',
+    method: 'POST',
+    data: data
+  })
+}
+
+// 注销用户个人信息
+export function appDel(data) {
+  return request({
+    url: `/system/user/${data.id}`,
+    method: 'DELETE',
+  })
+}
+
+// 树结构查询部门--用户列表
+export function deptUserTreeSelect(query) {
+  return request({
+    url: '/system/dept/deptUserTreeSelect',
+    method: 'GET',
+    params: query
+  })
+}
+
+// 用户列表
+export function UserList(query) {
+  return request({
+    url: '/system/user/list',
+    method: 'GET',
+    params: query
+  })
+}
+
+// 用户列表(无数据权限)
+export function dUserList(query) {
+  return request({
+    url: '/system/user/dUserList',
+    method: 'GET',
+    params: query
+  })
+}
+
+// 微信用户验证
+export function getPageAuthorization(data) {
+  return request({
+    url: '/service-iot/weChat/getPageAuthorization',
+    method: 'GET',
+    data: data,
+  })
+}
+
+// 查询部门列表(权限隔离)
+export function listDept(query) {
+  return request({
+      url: '/system/dept/list',
+      method: 'get',
+      params: query
+  })
+}
+// 查询部门列表(无权限隔离)
+export function listDeptNoAuth(query) {
+  return request({
+      url: '/system/dept/deptList',
+      method: 'get',
+      params: query
+  })
+}
+// 查询用户所属企业
+export function getTenantByUser(params) {
+  return request({
+      url: '/system/sysUserTenant/getTenantByUser/' + params,
+      method: 'get',
+  })
+}

+ 106 - 0
src/components/calendar/calendar.vue

@@ -0,0 +1,106 @@
+<template>
+  <view class="leave_cont">
+    <view class="ul">
+      <view class="li">
+        <view class="flex1">
+          <picker mode="date" :value="start_date" :start="start_date" :end="other" @change="bindDateChange">
+            <view class="date">{{ start_date }}</view>
+          </picker>
+        </view>
+      </view>
+      ~
+      <view class="li">
+        <view class="flex1">
+          <picker mode="date" :value="start_date" :start="start_date" @change="bindDateChange2">
+            <view class="date">{{ end_date }}</view>
+          </picker>
+        </view>
+      </view>
+      <image class="canlendar-icon" src="@/static/images/calendar.png"></image>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { onLoad, onShow, onHide, onLaunch } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, toRefs } from "vue";
+
+const currentDate = getDate({
+  format: true,
+});
+
+const data = reactive({
+  start_date: "请选择开始日期",
+  end_date: "请选择结束日期",
+  other: "请输入",
+});
+
+const { start_date, end_date, other } = toRefs(data);
+
+// 选择日期
+function bindDateChange(e) {
+  start_date.value = e.target.value;
+}
+function bindDateChange2(e) {
+  end_date.value = e.target.value;
+  other.value = end_date.value;
+}
+// 获取当前时间
+function getDate(type) {
+  const date = new Date();
+  let year = date.getFullYear();
+  let month = date.getMonth() + 1;
+  let day = date.getDate();
+
+  if (type === "start") {
+    year = year - 60;
+  } else if (type === "end") {
+    year = year + 2;
+  }
+  month = month > 9 ? month : "0" + month;
+  day = day > 9 ? day : "0" + day;
+  return `${year}-${month}-${day}`;
+}
+
+</script>
+
+<style>
+.leave_cont .ul {
+  border: 1px solid red;
+  margin: 24rpx 24rpx;
+  border: 1px solid #e8f1ff;
+  border-radius: 36rpx;
+  line-height: 70rpx;
+  position: relative;
+}
+
+.leave_cont .ul .li {
+  display: inline-block;
+  text-align: center;
+  width: 35%;
+}
+
+.leave_cont .ul .li text {
+  padding: 40rpx 0;
+  font-size: 30rpx;
+  color: #666666;
+  text-align: center;
+}
+
+.leave_cont .ul .li .flex1 {
+  flex: 1;
+  color: #999999;
+  font-size: 32rpx;
+}
+
+.date {
+  height: 42rpx;
+}
+.canlendar-icon {
+  width: 36rpx;
+  height: 36rpx;
+  position: absolute;
+  right: 30rpx;
+  top: 16rpx;
+}
+</style>

+ 175 - 0
src/components/helang-compress/helang-compress.vue

@@ -0,0 +1,175 @@
+<template>
+	<view class="compress">
+		<canvas :style="{ width: canvasSize.width,height: canvasSize.height}" canvas-id="myCanvas"></canvas>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pic:'',
+				canvasSize: {
+					width: 0,
+					height: 0
+				}
+			}
+		},
+		methods: {
+			// 压缩
+			compress(params) {
+				return new Promise(async (resolve, reject) => {
+					// 等待图片信息
+					let info = await this.getImageInfo(params.src).then(info=>info).catch(err=>err);
+					
+					if(!info){
+						reject('获取图片信息异常');
+						return;
+					}
+					
+					// 设置最大 & 最小 尺寸
+					const maxSize = params.maxSize || 1080;
+					const minSize = params.minSize || 640;
+					
+					// 当前图片尺寸
+					let {width,height} = info;
+					
+					// 非 H5 平台进行最小尺寸校验
+					// #ifndef H5
+					if(width <= minSize && height <= minSize){
+						resolve(params.src);
+						return;
+					}
+					// #endif
+					
+					// 最大尺寸计算
+					if (width > maxSize || height > maxSize) {
+						if (width > height) {
+							height = Math.floor(height / (width / maxSize));
+							width = maxSize;
+						} else {
+							width = Math.floor(width / (height / maxSize));
+							height = maxSize;
+						}
+					}
+
+					// 设置画布尺寸
+					this.$set(this,"canvasSize",{
+						width: `${width}rpx`,
+						height: `${height}rpx`
+					});
+					
+					// Vue.nextTick 回调在 App 有异常,则使用 setTimeout 等待DOM更新
+					setTimeout(() => {
+						const ctx = uni.createCanvasContext('myCanvas', this);
+						ctx.clearRect(0,0,width, height)
+						ctx.drawImage(info.path, 0, 0, uni.upx2px(width), uni.upx2px(height));
+						ctx.draw(false, () => {
+							uni.canvasToTempFilePath({
+								x: 0,
+								y: 0,
+								width: uni.upx2px(width),
+								height: uni.upx2px(height),
+								destWidth: width,
+								destHeight: height,
+								canvasId: 'myCanvas',
+								fileType: params.fileType || 'png',
+								quality: params.quality || 0.9,
+								success: (res) => {
+									// 在H5平台下,tempFilePath 为 base64
+									resolve(res.tempFilePath);
+								},
+								fail:(err)=>{
+									reject(null);
+								}
+							},this);
+						});
+					}, 300);
+				});
+			},
+			// 获取图片信息
+			getImageInfo(src){
+				return new Promise((resolve, reject)=>{
+					uni.getImageInfo({
+						src,
+						success: (info)=> {
+							resolve(info);
+						},
+						fail: () => {
+							reject(null);
+						}
+					});
+				});
+			},
+			// 批量压缩
+			batchCompress(params){
+				// index:进度,done:成功,fail:失败
+				let [index,done,fail] = [0,0,0];
+				// 压缩完成的路径集合
+				let paths = [];
+				// 批量压缩方法
+				let batch = ()=>{
+					return new Promise((resolve, reject)=>{
+						// 开始
+						let start = async ()=>{
+							params.progress && params.progress({
+								done,
+								fail,
+								count:params.batchSrc.length
+							});
+							// 等待图片压缩方法返回
+							let path = await next();
+							if(path){
+								done++;
+								paths.push(path);
+							}else{
+								fail++;
+							}
+							
+							index++;
+							// 压缩完成
+							if(index >= params.batchSrc.length){
+								resolve(true);
+							}else{
+								start();
+							}
+						}
+						start();
+					});
+				}
+				// 依次调用压缩方法
+				let next = ()=>{
+					return this.compress({
+						src:params.batchSrc[index],
+						maxSize:params.maxSize,
+						fileType:params.fileType,
+						quality:params.quality,
+						minSize:params.minSize
+					})
+				}
+				
+				// 全部压缩完成后调用
+				return new Promise(async (resolve, reject)=>{
+					// 批量压缩方法回调
+					let res = await batch();
+					if(res){
+						resolve(paths);
+					}else{
+						reject(null);
+					}
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.compress{
+		position: fixed;
+		width: 12px;
+		height: 12px;
+		overflow: hidden;
+		top: -99999px;
+		left: 0;
+	}
+</style>

+ 123 - 0
src/components/mpvue-echarts/src/echarts.vue

@@ -0,0 +1,123 @@
+<template>
+	<canvas v-if="canvasId" class="ec-canvas" :id="canvasId" :canvasId="canvasId" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @error="error"></canvas>
+</template>
+
+<script>
+import WxCanvas from './wx-canvas';
+
+export default {
+	props: {
+		canvasId: {
+			type: String,
+			default: 'ec-canvas'
+		},
+		lazyLoad: {
+			type: Boolean,
+			default: false
+		},
+		disableTouch: {
+			type: Boolean,
+			default: false
+		},
+		throttleTouch: {
+			type: Boolean,
+			default: false
+		}
+	},
+	// #ifdef H5
+	mounted() {
+		if (!this.lazyLoad) this.init();
+	},
+	// #endif
+	// #ifndef H5
+	onReady() {
+		if (!this.lazyLoad) this.init();
+	},
+	// #endif
+	methods: {
+		setChart(chart){
+			this.chart = chart
+		},
+		init() {
+			const { canvasId } = this;
+			this.ctx = wx.createCanvasContext(canvasId, this);
+
+			this.canvas = new WxCanvas(this.ctx, canvasId);
+
+			const query = wx.createSelectorQuery().in(this);
+			query
+				.select(`#${canvasId}`)
+				.boundingClientRect(res => {
+					if (!res) {
+						setTimeout(() => this.init(), 50);
+						return;
+					}
+					this.$emit('onInit', {
+						width: res.width,
+						height: res.height
+					});
+				})
+				.exec();
+		},
+		canvasToTempFilePath(opt) {
+			const { canvasId } = this;
+			this.ctx.draw(true, () => {
+				wx.canvasToTempFilePath({
+					canvasId,
+					...opt
+				});
+			});
+		},
+		touchStart(e) {
+			const { disableTouch, chart } = this;
+			if (disableTouch || !chart || !e.mp.touches.length) return;
+			const touch = e.mp.touches[0];
+			chart._zr.handler.dispatch('mousedown', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+			chart._zr.handler.dispatch('mousemove', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+		},
+		touchMove(e) {
+			const { disableTouch, throttleTouch, chart, lastMoveTime } = this;
+			if (disableTouch || !chart || !e.mp.touches.length) return;
+
+			if (throttleTouch) {
+				const currMoveTime = Date.now();
+				if (currMoveTime - lastMoveTime < 240) return;
+				this.lastMoveTime = currMoveTime;
+			}
+
+			const touch = e.mp.touches[0];
+			chart._zr.handler.dispatch('mousemove', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+		},
+		touchEnd(e) {
+			const { disableTouch, chart } = this;
+			if (disableTouch || !chart) return;
+			const touch = e.mp.changedTouches ? e.mp.changedTouches[0] : {};
+			chart._zr.handler.dispatch('mouseup', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+			chart._zr.handler.dispatch('click', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+		}
+	}
+};
+</script>
+
+<style scoped>
+.ec-canvas {
+	width: 100%;
+	height: 100%;
+	flex: 1;
+}
+</style>

+ 73 - 0
src/components/mpvue-echarts/src/wx-canvas.js

@@ -0,0 +1,73 @@
+export default class WxCanvas {
+  constructor(ctx, canvasId) {
+    this.ctx = ctx;
+    this.canvasId = canvasId;
+    this.chart = null;
+
+    WxCanvas.initStyle(ctx);
+    this.initEvent();
+  }
+
+  getContext(contextType) {
+    return contextType === '2d' ? this.ctx : null;
+  }
+
+  setChart(chart) {
+    this.chart = chart;
+  }
+
+  attachEvent() {
+    // noop
+  }
+
+  detachEvent() {
+    // noop
+  }
+
+  static initStyle(ctx) {
+    const styles = ['fillStyle', 'strokeStyle', 'globalAlpha',
+      'textAlign', 'textBaseAlign', 'shadow', 'lineWidth',
+      'lineCap', 'lineJoin', 'lineDash', 'miterLimit', 'fontSize'];
+
+    styles.forEach((style) => {
+      Object.defineProperty(ctx, style, {
+        set: (value) => {
+          if ((style !== 'fillStyle' && style !== 'strokeStyle')
+            || (value !== 'none' && value !== null)
+          ) {
+            ctx[`set${style.charAt(0).toUpperCase()}${style.slice(1)}`](value);
+          }
+        },
+      });
+    });
+
+    ctx.createRadialGradient = () => ctx.createCircularGradient(arguments);
+  }
+
+  initEvent() {
+    this.event = {};
+    const eventNames = [{
+      wxName: 'touchStart',
+      ecName: 'mousedown',
+    }, {
+      wxName: 'touchMove',
+      ecName: 'mousemove',
+    }, {
+      wxName: 'touchEnd',
+      ecName: 'mouseup',
+    }, {
+      wxName: 'touchEnd',
+      ecName: 'click',
+    }];
+
+    eventNames.forEach((name) => {
+      this.event[name.wxName] = (e) => {
+        const touch = e.mp.touches[0];
+        this.chart._zr.handler.dispatch(name.ecName, {
+          zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
+          zrY: name.wxName === 'tap' ? touch.clientY : touch.y,
+        });
+      };
+    });
+  }
+}

+ 546 - 0
src/components/oa-calendar/calendar.js

@@ -0,0 +1,546 @@
+/**
+* @1900-2100区间内的公历、农历互转
+* @charset UTF-8
+* @github  https://github.com/jjonline/calendar.js
+* @Author  Jea杨(JJonline@JJonline.Cn)
+* @Time    2014-7-21
+* @Time    2016-8-13 Fixed 2033hex、Attribution Annals
+* @Time    2016-9-25 Fixed lunar LeapMonth Param Bug
+* @Time    2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
+* @Version 1.0.3
+* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
+* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
+*/
+/* eslint-disable */
+var calendar = {
+
+  /**
+      * 农历1900-2100的润大小信息表
+      * @Array Of Property
+      * @return Hex
+      */
+  lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
+    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
+    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
+    0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
+    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
+    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
+    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
+    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
+    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
+    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
+    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
+    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
+    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
+    0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
+    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
+    /** Add By JJonline@JJonline.Cn**/
+    0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
+    0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
+    0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
+    0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
+    0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
+    0x0d520], // 2100
+
+  /**
+      * 公历每个月份的天数普通表
+      * @Array Of Property
+      * @return Number
+      */
+  solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+
+  /**
+      * 天干地支之天干速查表
+      * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
+      * @return Cn string
+      */
+  Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
+
+  /**
+      * 天干地支之地支速查表
+      * @Array Of Property
+      * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
+      * @return Cn string
+      */
+  Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
+
+  /**
+      * 天干地支之地支速查表<=>生肖
+      * @Array Of Property
+      * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
+      * @return Cn string
+      */
+  Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
+
+  /**
+      * 24节气速查表
+      * @Array Of Property
+      * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
+      * @return Cn string
+      */
+  solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
+
+  /**
+      * 1900-2100各年的24节气日期速查表
+      * @Array Of Property
+      * @return 0x string For splice
+      */
+  sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
+    '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
+    'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
+    '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
+    '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
+    '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+    '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
+    '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+    '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
+    '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+    '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
+    '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
+    '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
+    '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
+    '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
+    '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
+    '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
+    '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
+    '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
+    '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
+
+  /**
+      * 数字转中文速查表
+      * @Array Of Property
+      * @trans ['日','一','二','三','四','五','六','七','八','九','十']
+      * @return Cn string
+      */
+  nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
+
+  /**
+      * 日期转农历称呼速查表
+      * @Array Of Property
+      * @trans ['初','十','廿','卅']
+      * @return Cn string
+      */
+  nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
+
+  /**
+      * 月份转农历称呼速查表
+      * @Array Of Property
+      * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
+      * @return Cn string
+      */
+  nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
+
+  /**
+      * 返回农历y年一整年的总天数
+      * @param lunar Year
+      * @return Number
+      * @eg:var count = calendar.lYearDays(1987) ;//count=387
+      */
+  lYearDays: function (y) {
+    var i; var sum = 348
+    for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
+    return (sum + this.leapDays(y))
+  },
+
+  /**
+      * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
+      * @param lunar Year
+      * @return Number (0-12)
+      * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
+      */
+  leapMonth: function (y) { // 闰字编码 \u95f0
+    return (this.lunarInfo[y - 1900] & 0xf)
+  },
+
+  /**
+      * 返回农历y年闰月的天数 若该年没有闰月则返回0
+      * @param lunar Year
+      * @return Number (0、29、30)
+      * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
+      */
+  leapDays: function (y) {
+    if (this.leapMonth(y)) {
+      return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
+    }
+    return (0)
+  },
+
+  /**
+      * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
+      * @param lunar Year
+      * @return Number (-1、29、30)
+      * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
+      */
+  monthDays: function (y, m) {
+    if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
+    return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
+  },
+
+  /**
+      * 返回公历(!)y年m月的天数
+      * @param solar Year
+      * @return Number (-1、28、29、30、31)
+      * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
+      */
+  solarDays: function (y, m) {
+    if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+    var ms = m - 1
+    if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
+      return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
+    } else {
+      return (this.solarMonth[ms])
+    }
+  },
+
+  /**
+     * 农历年份转换为干支纪年
+     * @param  lYear 农历年的年份数
+     * @return Cn string
+     */
+  toGanZhiYear: function (lYear) {
+    var ganKey = (lYear - 3) % 10
+    var zhiKey = (lYear - 3) % 12
+    if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
+    if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
+    return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
+  },
+
+  /**
+     * 公历月、日判断所属星座
+     * @param  cMonth [description]
+     * @param  cDay [description]
+     * @return Cn string
+     */
+  toAstro: function (cMonth, cDay) {
+    var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
+    var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
+    return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
+  },
+
+  /**
+      * 传入offset偏移量返回干支
+      * @param offset 相对甲子的偏移量
+      * @return Cn string
+      */
+  toGanZhi: function (offset) {
+    return this.Gan[offset % 10] + this.Zhi[offset % 12]
+  },
+
+  /**
+      * 传入公历(!)y年获得该年第n个节气的公历日期
+      * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
+      * @return day Number
+      * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
+      */
+  getTerm: function (y, n) {
+    if (y < 1900 || y > 2100) { return -1 }
+    if (n < 1 || n > 24) { return -1 }
+    var _table = this.sTermInfo[y - 1900]
+    var _info = [
+      parseInt('0x' + _table.substr(0, 5)).toString(),
+      parseInt('0x' + _table.substr(5, 5)).toString(),
+      parseInt('0x' + _table.substr(10, 5)).toString(),
+      parseInt('0x' + _table.substr(15, 5)).toString(),
+      parseInt('0x' + _table.substr(20, 5)).toString(),
+      parseInt('0x' + _table.substr(25, 5)).toString()
+    ]
+    var _calday = [
+      _info[0].substr(0, 1),
+      _info[0].substr(1, 2),
+      _info[0].substr(3, 1),
+      _info[0].substr(4, 2),
+
+      _info[1].substr(0, 1),
+      _info[1].substr(1, 2),
+      _info[1].substr(3, 1),
+      _info[1].substr(4, 2),
+
+      _info[2].substr(0, 1),
+      _info[2].substr(1, 2),
+      _info[2].substr(3, 1),
+      _info[2].substr(4, 2),
+
+      _info[3].substr(0, 1),
+      _info[3].substr(1, 2),
+      _info[3].substr(3, 1),
+      _info[3].substr(4, 2),
+
+      _info[4].substr(0, 1),
+      _info[4].substr(1, 2),
+      _info[4].substr(3, 1),
+      _info[4].substr(4, 2),
+
+      _info[5].substr(0, 1),
+      _info[5].substr(1, 2),
+      _info[5].substr(3, 1),
+      _info[5].substr(4, 2)
+    ]
+    return parseInt(_calday[n - 1])
+  },
+
+  /**
+      * 传入农历数字月份返回汉语通俗表示法
+      * @param lunar month
+      * @return Cn string
+      * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
+      */
+  toChinaMonth: function (m) { // 月 => \u6708
+    if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+    var s = this.nStr3[m - 1]
+    s += '\u6708'// 加上月字
+    return s
+  },
+
+  /**
+      * 传入农历日期数字返回汉字表示法
+      * @param lunar day
+      * @return Cn string
+      * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
+      */
+  toChinaDay: function (d) { // 日 => \u65e5
+    var s
+    switch (d) {
+      case 10:
+        s = '\u521d\u5341'; break
+      case 20:
+        s = '\u4e8c\u5341'; break
+        break
+      case 30:
+        s = '\u4e09\u5341'; break
+        break
+      default :
+        s = this.nStr2[Math.floor(d / 10)]
+        s += this.nStr1[d % 10]
+    }
+    return (s)
+  },
+
+  /**
+      * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
+      * @param y year
+      * @return Cn string
+      * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
+      */
+  getAnimal: function (y) {
+    return this.Animals[(y - 4) % 12]
+  },
+
+  /**
+      * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
+      * @param y  solar year
+      * @param m  solar month
+      * @param d  solar day
+      * @return JSON object
+      * @eg:console.log(calendar.solar2lunar(1987,11,01));
+      */
+  solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
+    // 年份限定、上限
+    if (y < 1900 || y > 2100) {
+      return -1// undefined转换为数字变为NaN
+    }
+    // 公历传参最下限
+    if (y == 1900 && m == 1 && d < 31) {
+      return -1
+    }
+    // 未传参  获得当天
+    if (!y) {
+      var objDate = new Date()
+    } else {
+      var objDate = new Date(y, parseInt(m) - 1, d)
+    }
+    var i; var leap = 0; var temp = 0
+    // 修正ymd参数
+    var y = objDate.getFullYear()
+    var m = objDate.getMonth() + 1
+    var d = objDate.getDate()
+    var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
+    for (i = 1900; i < 2101 && offset > 0; i++) {
+      temp = this.lYearDays(i)
+      offset -= temp
+    }
+    if (offset < 0) {
+      offset += temp; i--
+    }
+
+    // 是否今天
+    var isTodayObj = new Date()
+    var isToday = false
+    if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
+      isToday = true
+    }
+    // 星期几
+    var nWeek = objDate.getDay()
+    var cWeek = this.nStr1[nWeek]
+    // 数字表示周几顺应天朝周一开始的惯例
+    if (nWeek == 0) {
+      nWeek = 7
+    }
+    // 农历年
+    var year = i
+    var leap = this.leapMonth(i) // 闰哪个月
+    var isLeap = false
+
+    // 效验闰月
+    for (i = 1; i < 13 && offset > 0; i++) {
+      // 闰月
+      if (leap > 0 && i == (leap + 1) && isLeap == false) {
+        --i
+        isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
+      } else {
+        temp = this.monthDays(year, i)// 计算农历普通月天数
+      }
+      // 解除闰月
+      if (isLeap == true && i == (leap + 1)) { isLeap = false }
+      offset -= temp
+    }
+    // 闰月导致数组下标重叠取反
+    if (offset == 0 && leap > 0 && i == leap + 1) {
+      if (isLeap) {
+        isLeap = false
+      } else {
+        isLeap = true; --i
+      }
+    }
+    if (offset < 0) {
+      offset += temp; --i
+    }
+    // 农历月
+    var month = i
+    // 农历日
+    var day = offset + 1
+    // 天干地支处理
+    var sm = m - 1
+    var gzY = this.toGanZhiYear(year)
+
+    // 当月的两个节气
+    // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
+    var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
+    var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
+
+    // 依据12节气修正干支月
+    var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
+    if (d >= firstNode) {
+      gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
+    }
+
+    // 传入的日期的节气与否
+    var isTerm = false
+    var Term = null
+    if (firstNode == d) {
+      isTerm = true
+      Term = this.solarTerm[m * 2 - 2]
+    }
+    if (secondNode == d) {
+      isTerm = true
+      Term = this.solarTerm[m * 2 - 1]
+    }
+    // 日柱 当月一日与 1900/1/1 相差天数
+    var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
+    var gzD = this.toGanZhi(dayCyclical + d - 1)
+    // 该日期所属的星座
+    var astro = this.toAstro(m, d)
+
+    return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
+  },
+
+  /**
+      * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
+      * @param y  lunar year
+      * @param m  lunar month
+      * @param d  lunar day
+      * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
+      * @return JSON object
+      * @eg:console.log(calendar.lunar2solar(1987,9,10));
+      */
+  lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
+    var isLeapMonth = !!isLeapMonth
+    var leapOffset = 0
+    var leapMonth = this.leapMonth(y)
+    var leapDay = this.leapDays(y)
+    if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
+    if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
+    var day = this.monthDays(y, m)
+    var _day = day
+    // bugFix 2016-9-25
+    // if month is leap, _day use leapDays method
+    if (isLeapMonth) {
+      _day = this.leapDays(y, m)
+    }
+    if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
+
+    // 计算农历的时间差
+    var offset = 0
+    for (var i = 1900; i < y; i++) {
+      offset += this.lYearDays(i)
+    }
+    var leap = 0; var isAdd = false
+    for (var i = 1; i < m; i++) {
+      leap = this.leapMonth(y)
+      if (!isAdd) { // 处理闰月
+        if (leap <= i && leap > 0) {
+          offset += this.leapDays(y); isAdd = true
+        }
+      }
+      offset += this.monthDays(y, i)
+    }
+    // 转换闰月农历 需补充该年闰月的前一个月的时差
+    if (isLeapMonth) { offset += day }
+    // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
+    var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
+    var calObj = new Date((offset + d - 31) * 86400000 + stmap)
+    var cY = calObj.getUTCFullYear()
+    var cM = calObj.getUTCMonth() + 1
+    var cD = calObj.getUTCDate()
+
+    return this.solar2lunar(cY, cM, cD)
+  }
+}
+
+export default calendar

+ 12 - 0
src/components/oa-calendar/i18n/en.json

@@ -0,0 +1,12 @@
+{
+	"uni-calender.ok": "ok",
+	"uni-calender.cancel": "cancel",
+	"uni-calender.today": "today",
+	"uni-calender.MON": "MON",
+	"uni-calender.TUE": "TUE",
+	"uni-calender.WED": "WED",
+	"uni-calender.THU": "THU",
+	"uni-calender.FRI": "FRI",
+	"uni-calender.SAT": "SAT",
+	"uni-calender.SUN": "SUN"
+}

+ 8 - 0
src/components/oa-calendar/i18n/index.js

@@ -0,0 +1,8 @@
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+export default {
+	en,
+	'zh-Hans': zhHans,
+	'zh-Hant': zhHant
+}

+ 12 - 0
src/components/oa-calendar/i18n/zh-Hans.json

@@ -0,0 +1,12 @@
+{
+	"uni-calender.ok": "确定",
+	"uni-calender.cancel": "取消",
+	"uni-calender.today": "今日",
+	"uni-calender.SUN": "日",
+	"uni-calender.MON": "一",
+	"uni-calender.TUE": "二",
+	"uni-calender.WED": "三",
+	"uni-calender.THU": "四",
+	"uni-calender.FRI": "五",
+	"uni-calender.SAT": "六"
+}

+ 12 - 0
src/components/oa-calendar/i18n/zh-Hant.json

@@ -0,0 +1,12 @@
+{
+	"uni-calender.ok": "確定",
+	"uni-calender.cancel": "取消",
+	"uni-calender.today": "今日",
+	"uni-calender.SUN": "日",
+	"uni-calender.MON": "一",
+	"uni-calender.TUE": "二",
+	"uni-calender.WED": "三",
+	"uni-calender.THU": "四",
+	"uni-calender.FRI": "五",
+	"uni-calender.SAT": "六"
+}

+ 188 - 0
src/components/oa-calendar/uni-calendar-item.vue

@@ -0,0 +1,188 @@
+<template>
+	<view class="uni-calendar-item__weeks-box" :class="{
+		'uni-calendar-item--disable':weeks.disable,
+		'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+		'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
+		'uni-calendar-item--before-checked':weeks.beforeMultiple,
+		'uni-calendar-item--multiple': weeks.multiple,
+		'uni-calendar-item--after-checked':weeks.afterMultiple,
+		}"
+	 @click="choiceDate(weeks)">
+		<view class="uni-calendar-item__weeks-box-item">
+			<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
+			<text class="uni-calendar-item__weeks-box-text" :class="{
+				'uni-calendar-item--isDay-text': weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.date}}</text>
+			<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				}">{{todayText}}</text>
+			<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
+			<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--extra':weeks.extraInfo.info,
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.extraInfo.info}}</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+	initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from './i18n/index.js'
+	const {	t	} = initVueI18n(messages)
+	export default {
+		emits:['change'],
+		props: {
+			weeks: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			calendar: {
+				type: Object,
+				default: () => {
+					return {}
+				}
+			},
+			selected: {
+				type: Array,
+				default: () => {
+					return []
+				}
+			},
+			lunar: {
+				type: Boolean,
+				default: false
+			}
+		},
+		computed: {
+			todayText() {
+				return t("uni-calender.today")
+			},
+		},
+		methods: {
+			choiceDate(weeks) {
+				this.$emit('change', weeks)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	$uni-font-size-base:14px;
+	$uni-text-color:#333;
+	$uni-font-size-sm:12px;
+	$uni-color-error: #e43d33;
+	$uni-opacity-disabled: 0.3;
+	$uni-text-color-disable:#c0c0c0;
+	$uni-primary: #2979ff !default;
+	.uni-calendar-item__weeks-box {
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.uni-calendar-item__weeks-box-text {
+		font-size: $uni-font-size-base;
+		color: $uni-text-color;
+	}
+
+	.uni-calendar-item__weeks-lunar-text {
+		font-size: $uni-font-size-sm;
+		color: $uni-text-color;
+	}
+
+	.uni-calendar-item__weeks-box-item {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		width: 100rpx;
+		height: 100rpx;
+	}
+
+	.uni-calendar-item__weeks-box-circle {
+		position: absolute;
+		top: 5px;
+		right: 5px;
+		width: 8px;
+		height: 8px;
+		border-radius: 8px;
+		background-color: $uni-color-error;
+
+	}
+
+	.uni-calendar-item--disable {
+		background-color: rgba(249, 249, 249, $uni-opacity-disabled);
+		color: $uni-text-color-disable;
+	}
+
+	.uni-calendar-item--isDay-text {
+		color: $uni-primary;
+	}
+
+	.uni-calendar-item--isDay {
+		background-color: $uni-primary;
+		opacity: 0.8;
+		color: #fff;
+	}
+
+	.uni-calendar-item--extra {
+		color: $uni-color-error;
+		opacity: 0.8;
+	}
+
+	.uni-calendar-item--checked {
+		background-color: $uni-primary;
+		color: #fff;
+		opacity: 0.8;
+	}
+
+	.uni-calendar-item--multiple {
+		background-color: $uni-primary;
+		color: #fff;
+		opacity: 0.8;
+	}
+	.uni-calendar-item--before-checked {
+		background-color: #ff5a5f;
+		color: #fff;
+	}
+	.uni-calendar-item--after-checked {
+		background-color: #ff5a5f;
+		color: #fff;
+	}
+</style>

+ 607 - 0
src/components/oa-calendar/uni-calendar.vue

@@ -0,0 +1,607 @@
+<template>
+  <view class="uni-calendar">
+    <view v-if="!insert && show" class="uni-calendar__mask" :class="{ 'uni-calendar--mask-show': aniMaskShow }" @click="clean"></view>
+    <view v-if="insert || show" class="uni-calendar__content" :class="{ 'uni-calendar--fixed': !insert, 'uni-calendar--ani-show': aniMaskShow }" @touchstart="touchStart" @touchend="touchEnd">
+      <view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
+        <view class="uni-calendar__header-btn-box" @click="close">
+          <text class="uni-calendar__header-text uni-calendar--fixed-width">{{ cancelText }}</text>
+        </view>
+        <view class="uni-calendar__header-btn-box" @click="confirm">
+          <text class="uni-calendar__header-text uni-calendar--fixed-width">{{ okText }}</text>
+        </view>
+      </view>
+      <view class="uni-calendar__header">
+        <view class="uni-calendar__header-btn-box" v-if="false" @click.stop="pre">
+          <view class="uni-calendar__header-btn uni-calendar--left"></view>
+        </view>
+
+        <picker mode="date" :value="date" fields="month" @change="bindDateChange">
+          <text class="uni-calendar__header-text">{{ (nowDate.year || "") + " 年 " + (nowDate.month || "") + " 月" }}</text>
+        </picker>
+
+        <view style="margin: auto">
+          <text class="uni-calendar__backtoday" v-if="false" @click="backtoday">{{ todayText }}</text>
+        </view>
+
+        <view class="uni-calendar__header-right">
+          <slot name="headerRight"> </slot>
+        </view>
+
+        <view class="uni-calendar__header-btn-box" v-if="false" @click.stop="next">
+          <view class="uni-calendar__header-btn uni-calendar--right"></view>
+        </view>
+      </view>
+      <view class="uni-calendar__box">
+        <view v-if="showMonth" class="uni-calendar__box-bg">
+          <text class="uni-calendar__box-bg-text">{{ nowDate.month }}</text>
+        </view>
+        <view class="uni-calendar__weeks">
+          <view class="uni-calendar__weeks-day">
+            <text class="uni-calendar__weeks-day-text">{{ SUNText }}</text>
+          </view>
+          <view class="uni-calendar__weeks-day">
+            <text class="uni-calendar__weeks-day-text">{{ monText }}</text>
+          </view>
+          <view class="uni-calendar__weeks-day">
+            <text class="uni-calendar__weeks-day-text">{{ TUEText }}</text>
+          </view>
+          <view class="uni-calendar__weeks-day">
+            <text class="uni-calendar__weeks-day-text">{{ WEDText }}</text>
+          </view>
+          <view class="uni-calendar__weeks-day">
+            <text class="uni-calendar__weeks-day-text">{{ THUText }}</text>
+          </view>
+          <view class="uni-calendar__weeks-day">
+            <text class="uni-calendar__weeks-day-text">{{ FRIText }}</text>
+          </view>
+          <view class="uni-calendar__weeks-day">
+            <text class="uni-calendar__weeks-day-text">{{ SATText }}</text>
+          </view>
+        </view>
+        <view class="uni-calendar__weeks" v-for="(item, weekIndex) in weeks" :key="weekIndex">
+          <view class="uni-calendar__weeks-item" v-for="(weeks, weeksIndex) in item" :key="weeksIndex">
+            <calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import Calendar from "./util.js";
+import calendarItem from "./uni-calendar-item.vue";
+import { initVueI18n } from "@dcloudio/uni-i18n";
+import messages from "./i18n/index.js";
+const { t } = initVueI18n(messages);
+/**
+ * Calendar 日历
+ * @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=56
+ * @property {String} date 自定义当前时间,默认为今天
+ * @property {Boolean} lunar 显示农历
+ * @property {String} startDate 日期选择范围-开始日期
+ * @property {String} endDate 日期选择范围-结束日期
+ * @property {Boolean} range 范围选择
+ * @property {Boolean} insert = [true|false] 插入模式,默认为false
+ * @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
+ * @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
+ * @property {Boolean} showMonth 是否选择月份为背景
+ * @event {Function} change 日期改变,`insert :ture` 时生效
+ * @event {Function} confirm 确认选择`insert :false` 时生效
+ * @event {Function} monthSwitch 切换月份时触发
+ * @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
+ */
+
+let touchStartX = 0; // 触屏起始点x
+let touchStartY = 0; // 触屏起始点y
+export default {
+  components: {
+    calendarItem,
+  },
+  emits: ["close", "confirm", "change", "monthSwitch"],
+  props: {
+    date: {
+      type: String,
+      default: "",
+    },
+    selected: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+    lunar: {
+      type: Boolean,
+      default: false,
+    },
+    startDate: {
+      type: String,
+      default: "",
+    },
+    endDate: {
+      type: String,
+      default: "",
+    },
+    range: {
+      type: Boolean,
+      default: false,
+    },
+    insert: {
+      type: Boolean,
+      default: true,
+    },
+    showMonth: {
+      type: Boolean,
+      default: true,
+    },
+    clearDate: {
+      type: Boolean,
+      default: true,
+    },
+  },
+  data() {
+    return {
+      show: false,
+      weeks: [],
+      calendar: {},
+      nowDate: "",
+      aniMaskShow: false,
+    };
+  },
+  computed: {
+    /**
+     * for i18n
+     */
+
+    okText() {
+      return t("uni-calender.ok");
+    },
+    cancelText() {
+      return t("uni-calender.cancel");
+    },
+    todayText() {
+      return t("uni-calender.today");
+    },
+    monText() {
+      return t("uni-calender.MON");
+    },
+    TUEText() {
+      return t("uni-calender.TUE");
+    },
+    WEDText() {
+      return t("uni-calender.WED");
+    },
+    THUText() {
+      return t("uni-calender.THU");
+    },
+    FRIText() {
+      return t("uni-calender.FRI");
+    },
+    SATText() {
+      return t("uni-calender.SAT");
+    },
+    SUNText() {
+      return t("uni-calender.SUN");
+    },
+  },
+  watch: {
+    date(newVal) {
+      // this.cale.setDate(newVal)
+      this.init(newVal);
+    },
+    startDate(val) {
+      this.cale.resetSatrtDate(val);
+      this.cale.setDate(this.nowDate.fullDate);
+      this.weeks = this.cale.weeks;
+    },
+    endDate(val) {
+      this.cale.resetEndDate(val);
+      this.cale.setDate(this.nowDate.fullDate);
+      this.weeks = this.cale.weeks;
+    },
+    selected(newVal) {
+      this.cale.setSelectInfo(this.nowDate.fullDate, newVal);
+      this.weeks = this.cale.weeks;
+    },
+  },
+  created() {
+    // 获取日历方法实例
+    this.cale = new Calendar({
+      // date: new Date(),
+      selected: this.selected,
+      startDate: this.startDate,
+      endDate: this.endDate,
+      range: this.range,
+    });
+    // 选中某一天
+    // this.cale.setDate(this.date)
+    this.init(this.date);
+    // this.setDay
+  },
+  methods: {
+    // 取消穿透
+    clean() {},
+    bindDateChange(e) {
+      const value = e.detail.value + "-1";
+      console.log(this.cale.getDate(value));
+      this.setDate(value);
+    },
+    /**
+     * 初始化日期显示
+     * @param {Object} date
+     */
+    init(date) {
+      this.cale.setDate(date);
+      this.weeks = this.cale.weeks;
+      this.nowDate = this.calendar = this.cale.getInfo(date);
+    },
+    /**
+     * 打开日历弹窗
+     */
+    open() {
+      // 弹窗模式并且清理数据
+      if (this.clearDate && !this.insert) {
+        this.cale.cleanMultipleStatus();
+        // this.cale.setDate(this.date)
+        this.init(this.date);
+      }
+      this.show = true;
+      this.$nextTick(() => {
+        setTimeout(() => {
+          this.aniMaskShow = true;
+        }, 50);
+      });
+    },
+    /**
+     * 关闭日历弹窗
+     */
+    close() {
+      this.aniMaskShow = false;
+      this.$nextTick(() => {
+        setTimeout(() => {
+          this.show = false;
+          this.$emit("close");
+        }, 300);
+      });
+    },
+    /**
+     * 确认按钮
+     */
+    confirm() {
+      this.setEmit("confirm");
+      this.close();
+    },
+    /**
+     * 变化触发
+     */
+    change() {
+      if (!this.insert) return;
+      this.setEmit("change");
+    },
+    /**
+     * 选择月份触发
+     */
+    monthSwitch() {
+      let { year, month } = this.nowDate;
+      this.$emit("monthSwitch", {
+        year,
+        month: Number(month),
+      });
+    },
+    /**
+     * 派发事件
+     * @param {Object} name
+     */
+    setEmit(name) {
+      let { year, month, date, fullDate, lunar, extraInfo } = this.calendar;
+      this.$emit(name, {
+        range: this.cale.multipleStatus,
+        year,
+        month,
+        date,
+        fulldate: fullDate,
+        lunar,
+        extraInfo: extraInfo || {},
+      });
+    },
+    /**
+     * 选择天触发
+     * @param {Object} weeks
+     */
+    choiceDate(weeks) {
+      if (weeks.disable) return;
+      this.calendar = weeks;
+      // 设置多选
+      this.cale.setMultiple(this.calendar.fullDate);
+      this.weeks = this.cale.weeks;
+      this.change();
+    },
+    /**
+     * 回到今天
+     */
+    backtoday() {
+      console.log(this.cale.getDate(new Date()).fullDate);
+      let date = this.cale.getDate(new Date()).fullDate;
+      // this.cale.setDate(date)
+      this.init(date);
+      this.change();
+    },
+    /**
+     * 上个月
+     */
+    pre() {
+      const preDate = this.cale.getDate(this.nowDate.fullDate, -1, "month").fullDate;
+      this.setDate(preDate);
+      this.monthSwitch();
+    },
+    /**
+     * 下个月
+     */
+    next() {
+      const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, "month").fullDate;
+      this.setDate(nextDate);
+      this.monthSwitch();
+    },
+    /**
+     * 设置日期
+     * @param {Object} date
+     */
+    setDate(date) {
+      this.cale.setDate(date);
+      this.weeks = this.cale.weeks;
+      this.nowDate = this.cale.getInfo(date);
+    },
+    /**
+     * @触摸开始
+     **/
+    touchStart(e) {
+      console.log("触摸开始");
+      touchStartX = e.touches[0].clientX;
+      touchStartY = e.touches[0].clientY;
+    },
+    /**
+     * @触摸结束
+     **/
+    touchEnd(e) {
+      console.log("触摸结束");
+      let deltaX = e.changedTouches[0].clientX - touchStartX;
+      let deltaY = e.changedTouches[0].clientY - touchStartY;
+      if (Math.abs(deltaX) > 50 && Math.abs(deltaX) > Math.abs(deltaY)) {
+        if (deltaX >= 0) {
+          console.log("左滑");
+          this.pre();
+          // calendar.value.selectDate("prev-month");
+        } else {
+          console.log("右滑");
+          this.next();
+          // calendar.value.selectDate("next-month");
+        }
+      } else if (Math.abs(deltaY) > 50 && Math.abs(deltaX) < Math.abs(deltaY)) {
+        if (deltaY < 0) {
+          console.log("上滑");
+        } else {
+          console.log("下滑");
+        }
+      } else {
+        console.log("可能是误触!");
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+$uni-bg-color-mask: rgba(
+  $color: #000000,
+  $alpha: 0.4,
+);
+$uni-border-color: #ededed;
+$uni-text-color: #333;
+$uni-bg-color-hover: #f1f1f1;
+$uni-font-size-sm: 12px;
+$uni-font-size-base: 14px;
+$uni-text-color-placeholder: #808080;
+$uni-color-subtitle: #555555;
+$uni-text-color-grey: #999;
+.uni-calendar {
+  /* #ifndef APP-NVUE */
+  display: flex;
+  /* #endif */
+  flex-direction: column;
+}
+
+.uni-calendar__mask {
+  position: fixed;
+  bottom: 0;
+  top: 0;
+  left: 0;
+  right: 0;
+  background-color: $uni-bg-color-mask;
+  transition-property: opacity;
+  transition-duration: 0.3s;
+  opacity: 0;
+  /* #ifndef APP-NVUE */
+  z-index: 99;
+  /* #endif */
+}
+
+.uni-calendar--mask-show {
+  opacity: 1;
+}
+
+.uni-calendar--fixed {
+  position: fixed;
+  /* #ifdef APP-NVUE */
+  bottom: 0;
+  /* #endif */
+  left: 0;
+  right: 0;
+  transition-property: transform;
+  transition-duration: 0.3s;
+  transform: translateY(460px);
+  /* #ifndef APP-NVUE */
+  bottom: calc(var(--window-bottom));
+  z-index: 99;
+  /* #endif */
+}
+
+.uni-calendar--ani-show {
+  transform: translateY(0);
+}
+
+.uni-calendar__content {
+  background-color: #fff;
+}
+
+.uni-calendar__header {
+  position: relative;
+  /* #ifndef APP-NVUE */
+  display: flex;
+  /* #endif */
+  flex-direction: row;
+  // justify-content: center;
+  align-items: center;
+  // height: 50px;
+  line-height: 40px;
+  padding: 0 15px;
+  border-bottom-color: $uni-border-color;
+  border-bottom-style: solid;
+  border-bottom-width: 1px;
+}
+
+.uni-calendar--fixed-top {
+  /* #ifndef APP-NVUE */
+  display: flex;
+  /* #endif */
+  flex-direction: row;
+  justify-content: space-between;
+  border-top-color: $uni-border-color;
+  border-top-style: solid;
+  border-top-width: 1px;
+}
+
+.uni-calendar--fixed-width {
+  width: 50px;
+}
+
+.uni-calendar__backtoday {
+  padding: 0 10px;
+  height: 25px;
+  line-height: 25px;
+  font-size: $uni-font-size-sm;
+  border-radius: 25px;
+  color: $uni-text-color;
+  background-color: $uni-bg-color-hover;
+}
+
+.uni-calendar__header-text {
+  text-align: center;
+  width: 100px;
+  font-size: $uni-font-size-base;
+  color: $uni-text-color;
+}
+
+:deep() {
+  .uni-calendar__header-right {
+    display: flex;
+    .item {
+      font-size: $uni-font-size-base;
+      + .item {
+        margin-left: 10px;
+      }
+      .item_title {
+        margin-right: 5px;
+      }
+    }
+  }
+}
+
+.uni-calendar__header-btn-box {
+  /* #ifndef APP-NVUE */
+  display: flex;
+  /* #endif */
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  width: 20px;
+  height: 10px;
+}
+
+.uni-calendar__header-btn {
+  width: 10px;
+  height: 10px;
+  border-left-color: $uni-text-color-placeholder;
+  border-left-style: solid;
+  border-left-width: 1.5px;
+  border-top-color: $uni-color-subtitle;
+  border-top-style: solid;
+  border-top-width: 1.5px;
+}
+
+.uni-calendar--left {
+  transform: rotate(-45deg);
+}
+
+.uni-calendar--right {
+  transform: rotate(135deg);
+}
+
+.uni-calendar__weeks {
+  position: relative;
+  /* #ifndef APP-NVUE */
+  display: flex;
+  /* #endif */
+  flex-direction: row;
+}
+
+.uni-calendar__weeks-item {
+  flex: 1;
+}
+
+.uni-calendar__weeks-day {
+  flex: 1;
+  /* #ifndef APP-NVUE */
+  display: flex;
+  /* #endif */
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  height: 45px;
+  border-bottom-color: #f5f5f5;
+  border-bottom-style: solid;
+  border-bottom-width: 1px;
+}
+
+.uni-calendar__weeks-day-text {
+  font-size: $uni-font-size-base;
+}
+
+.uni-calendar__box {
+  position: relative;
+}
+
+.uni-calendar__box-bg {
+  /* #ifndef APP-NVUE */
+  display: flex;
+  /* #endif */
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+
+.uni-calendar__box-bg-text {
+  font-size: 200px;
+  font-weight: bold;
+  color: $uni-text-color-grey;
+  opacity: 0.1;
+  text-align: center;
+  /* #ifndef APP-NVUE */
+  line-height: 1;
+  /* #endif */
+}
+</style>

+ 350 - 0
src/components/oa-calendar/util.js

@@ -0,0 +1,350 @@
+import CALENDAR from './calendar.js'
+
+class Calendar {
+	constructor({
+		date,
+		selected,
+		startDate,
+		endDate,
+		range
+	} = {}) {
+		// 当前日期
+		this.date = this.getDate(new Date()) // 当前初入日期
+		// 打点信息
+		this.selected = selected || [];
+		// 范围开始
+		this.startDate = startDate
+		// 范围结束
+		this.endDate = endDate
+		this.range = range
+		// 多选状态
+		this.cleanMultipleStatus()
+		// 每周日期
+		this.weeks = {}
+		// this._getWeek(this.date.fullDate)
+	}
+	/**
+	 * 设置日期
+	 * @param {Object} date
+	 */
+	setDate(date) {
+		this.selectDate = this.getDate(date)
+		this._getWeek(this.selectDate.fullDate)
+	}
+
+	/**
+	 * 清理多选状态
+	 */
+	cleanMultipleStatus() {
+		this.multipleStatus = {
+			before: '',
+			after: '',
+			data: []
+		}
+	}
+
+	/**
+	 * 重置开始日期
+	 */
+	resetSatrtDate(startDate) {
+		// 范围开始
+		this.startDate = startDate
+
+	}
+
+	/**
+	 * 重置结束日期
+	 */
+	resetEndDate(endDate) {
+		// 范围结束
+		this.endDate = endDate
+	}
+
+	/**
+	 * 获取任意时间
+	 */
+	getDate(date, AddDayCount = 0, str = 'day') {
+		if (!date) {
+			date = new Date()
+		}
+		if (typeof date !== 'object') {
+			date = date.replace(/-/g, '/')
+		}
+		const dd = new Date(date)
+		switch (str) {
+			case 'day':
+				dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
+				break
+			case 'month':
+				if (dd.getDate() === 31) {
+					dd.setDate(dd.getDate() + AddDayCount)
+				} else {
+					dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
+				}
+				break
+			case 'year':
+				dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
+				break
+		}
+		const y = dd.getFullYear()
+		const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
+		const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
+		return {
+			fullDate: y + '-' + m + '-' + d,
+			year: y,
+			month: m,
+			date: d,
+			day: dd.getDay()
+		}
+	}
+
+
+	/**
+	 * 获取上月剩余天数
+	 */
+	_getLastMonthDays(firstDay, full) {
+		let dateArr = []
+		for (let i = firstDay; i > 0; i--) {
+			const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
+			dateArr.push({
+				date: beforeDate,
+				month: full.month - 1,
+				lunar: this.getlunar(full.year, full.month - 1, beforeDate),
+				disable: true
+			})
+		}
+		return dateArr
+	}
+	/**
+	 * 获取本月天数
+	 */
+	_currentMonthDys(dateData, full) {
+		let dateArr = []
+		let fullDate = this.date.fullDate
+		for (let i = 1; i <= dateData; i++) {
+			let nowDate = full.year + '-' + (full.month < 10 ?
+				full.month : full.month) + '-' + (i < 10 ?
+				'0' + i : i)
+			// 是否今天
+			let isDay = fullDate === nowDate
+			// 获取打点信息
+			let info = this.selected && this.selected.find((item) => {
+				if (this.dateEqual(nowDate, item.date)) {
+					return item
+				}
+			})
+
+			// 日期禁用
+			let disableBefore = true
+			let disableAfter = true
+			if (this.startDate) {
+				// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
+				// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
+				disableBefore = this.dateCompare(this.startDate, nowDate)
+			}
+
+			if (this.endDate) {
+				// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
+				// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
+				disableAfter = this.dateCompare(nowDate, this.endDate)
+			}
+			let multiples = this.multipleStatus.data
+			let checked = false
+			let multiplesStatus = -1
+			if (this.range) {
+				if (multiples) {
+					multiplesStatus = multiples.findIndex((item) => {
+						return this.dateEqual(item, nowDate)
+					})
+				}
+				if (multiplesStatus !== -1) {
+					checked = true
+				}
+			}
+			let data = {
+				fullDate: nowDate,
+				year: full.year,
+				date: i,
+				multiple: this.range ? checked : false,
+				beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
+				afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
+				month: full.month,
+				lunar: this.getlunar(full.year, full.month, i),
+				disable: !(disableBefore && disableAfter),
+				isDay
+			}
+			if (info) {
+				data.extraInfo = info
+			}
+
+			dateArr.push(data)
+		}
+		return dateArr
+	}
+	/**
+	 * 获取下月天数
+	 */
+	_getNextMonthDays(surplus, full) {
+		let dateArr = []
+		for (let i = 1; i < surplus + 1; i++) {
+			dateArr.push({
+				date: i,
+				month: Number(full.month) + 1,
+				lunar: this.getlunar(full.year, Number(full.month) + 1, i),
+				disable: true
+			})
+		}
+		return dateArr
+	}
+
+	/**
+	 * 获取当前日期详情
+	 * @param {Object} date
+	 */
+	getInfo(date) {
+		if (!date) {
+			date = new Date()
+		}
+		const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
+		return dateInfo
+	}
+
+	/**
+	 * 比较时间大小
+	 */
+	dateCompare(startDate, endDate) {
+		// 计算截止时间
+		startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
+		// 计算详细项的截止时间
+		endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
+		if (startDate <= endDate) {
+			return true
+		} else {
+			return false
+		}
+	}
+
+	/**
+	 * 比较时间是否相等
+	 */
+	dateEqual(before, after) {
+		// 计算截止时间
+		before = new Date(before.replace('-', '/').replace('-', '/'))
+		// 计算详细项的截止时间
+		after = new Date(after.replace('-', '/').replace('-', '/'))
+		if (before.getTime() - after.getTime() === 0) {
+			return true
+		} else {
+			return false
+		}
+	}
+
+
+	/**
+	 * 获取日期范围内所有日期
+	 * @param {Object} begin
+	 * @param {Object} end
+	 */
+	geDateAll(begin, end) {
+		var arr = []
+		var ab = begin.split('-')
+		var ae = end.split('-')
+		var db = new Date()
+		db.setFullYear(ab[0], ab[1] - 1, ab[2])
+		var de = new Date()
+		de.setFullYear(ae[0], ae[1] - 1, ae[2])
+		var unixDb = db.getTime() - 24 * 60 * 60 * 1000
+		var unixDe = de.getTime() - 24 * 60 * 60 * 1000
+		for (var k = unixDb; k <= unixDe;) {
+			k = k + 24 * 60 * 60 * 1000
+			arr.push(this.getDate(new Date(parseInt(k))).fullDate)
+		}
+		return arr
+	}
+	/**
+	 * 计算阴历日期显示
+	 */
+	getlunar(year, month, date) {
+		return CALENDAR.solar2lunar(year, month, date)
+	}
+	/**
+	 * 设置打点
+	 */
+	setSelectInfo(data, value) {
+		this.selected = value
+		this._getWeek(data)
+	}
+
+	/**
+	 *  获取多选状态
+	 */
+	setMultiple(fullDate) {
+		let {
+			before,
+			after
+		} = this.multipleStatus
+
+		if (!this.range) return
+		if (before && after) {
+			this.multipleStatus.before = ''
+			this.multipleStatus.after = ''
+			this.multipleStatus.data = []
+		} else {
+			if (!before) {
+				this.multipleStatus.before = fullDate
+			} else {
+				this.multipleStatus.after = fullDate
+				if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
+					this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
+				} else {
+					this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
+				}
+			}
+		}
+		this._getWeek(fullDate)
+	}
+
+	/**
+	 * 获取每周数据
+	 * @param {Object} dateData
+	 */
+	_getWeek(dateData) {
+		const {
+			year,
+			month
+		} = this.getDate(dateData)
+		let firstDay = new Date(year, month - 1, 1).getDay()
+		let currentDay = new Date(year, month, 0).getDate()
+		let dates = {
+			lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
+			currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
+			nextMonthDays: [], // 下个月开始几天
+			weeks: []
+		}
+		let canlender = []
+		const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
+		dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
+		canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
+		let weeks = {}
+		// 拼接数组  上个月开始几天 + 本月天数+ 下个月开始几天
+		for (let i = 0; i < canlender.length; i++) {
+			if (i % 7 === 0) {
+				weeks[parseInt(i / 7)] = new Array(7)
+			}
+			weeks[parseInt(i / 7)][i % 7] = canlender[i]
+		}
+		this.canlender = canlender
+		this.weeks = weeks
+	}
+
+	//静态方法
+	// static init(date) {
+	// 	if (!this.instance) {
+	// 		this.instance = new Calendar(date);
+	// 	}
+	// 	return this.instance;
+	// }
+}
+
+
+export default Calendar

+ 65 - 0
src/components/oa-dropdown/index.vue

@@ -0,0 +1,65 @@
+<template>
+  <view>
+    <view class="oa-dropdown" :class="dropdownShow ? 'show' : 'none'">
+      <view class="content">
+        <slot name="content"></slot>
+      </view>
+    </view>
+    <view v-if="closeOnClickOverlay" class="oa-dropdown" :class="dropdownShow ? 'mask' : 'none'" @click="close()"> </view>
+  </view>
+</template>
+<script setup>
+import { onReady, onLoad, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance, watchEffect, toRefs } from "vue";
+
+const emit = defineEmits(["close"]);
+const props = defineProps({
+  //显示隐藏
+  dropdownShow: {
+    type: Boolean,
+    default: false,
+  },
+  //是否开启遮罩层关闭
+  closeOnClickOverlay: {
+    type: Boolean,
+    default: true,
+  },
+});
+
+const { dropdownShow, closeOnClickOverlay } = toRefs(props);
+
+function close() {
+  emit("close", false);
+}
+</script>
+<style lang="scss" scoped>
+.oa-dropdown {
+  position: absolute;
+  width: 100%;
+  background-color: #fff;
+  max-height: 0;
+  overflow: hidden;
+  z-index: 90;
+  transition: max-height 0.4s ease-in-out;
+  box-shadow: 0px 1px 2px 0px rgba(141, 141, 141, 0.4);
+
+  &.show {
+    max-height: 100%;
+  }
+
+  &.mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: transparent;
+    height: 100%;
+    z-index: 50;
+  }
+
+  .content {
+    padding: 10px;
+  }
+}
+</style>

+ 149 - 0
src/components/oa-movable/index.vue

@@ -0,0 +1,149 @@
+<template>
+  <movable-area class="fixed-box">
+    <movable-view class="fixed-button" direction="all" :inertia="true" y="100px">
+      <view class="menuBox">
+        <view class="mainMenu iconfont oaIcon-jiahao menu-item-icon" :style="{ background: themesColor }" @click="declick"></view>
+        <view class="posi" :animation="animationData">
+          <slot name="content"> </slot>
+        </view>
+      </view>
+    </movable-view>
+  </movable-area>
+</template>
+<script setup>
+import { onReady, onLoad, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance, watchEffect, toRefs, toRef, watch } from "vue";
+
+const emit = defineEmits(["load", "refresh"]);
+const props = defineProps({
+  //主题颜色
+  themesColor: {
+    type: String,
+    default: "#3f99ff",
+  },
+});
+
+const defaultArray = reactive({
+  off: true,
+  animation: null,
+  animationData: {},
+});
+
+const { themesColor } = toRefs(props);
+const { off, animation, animationData } = toRefs(defaultArray);
+
+// 悬浮按钮
+function declick() {
+  if (off.value) {
+    //使用动画
+    rotateAndScale();
+  } else {
+    norotateAndScale();
+  }
+  off.value = !off.value;
+}
+
+//定义动画内容
+function rotateAndScale() {
+  animation.value.rotate(0).translateY(-1).step();
+  //导出动画数据传递给data层
+  animationData.value = animation.value.export();
+}
+
+//当!off的时候动画回到原始位置
+function norotateAndScale() {
+  animation.value.rotate(0).translateY(150).step();
+  animationData.value = animation.value.export();
+}
+
+onLoad((option) => {
+  animation.value = uni.createAnimation();
+});
+
+onShow(() => {
+  var animation1 = uni.createAnimation({
+    duration: 500,
+    trmingFunction: "ease",
+  });
+  animation.value = animation1;
+});
+</script>
+
+<style lang="scss" scoped>
+.fixed-box {
+  pointer-events: none;
+  width: 100vw;
+  height: 100vh;
+  position: fixed;
+  left: 0;
+  bottom: 0;
+  z-index: 100000;
+}
+
+.fixed-button {
+  right: 0;
+  top: 60vh;
+  left: auto;
+  width: 50px;
+  height: 150px;
+  display: flex;
+  padding: 5px 5px;
+  justify-content: center;
+  align-items: center;
+  pointer-events: auto;
+}
+
+.menuBox {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  z-index: 1;
+  overflow: hidden;
+  border-radius: 25px;
+}
+
+.mainMenu {
+  width: 50px;
+  height: 50px;
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 100%;
+}
+
+:deep(.iconfont) {
+  color: #fff;
+  font-size: 28px;
+}
+
+.posi {
+  width: 50px;
+  position: relative;
+  z-index: -1;
+  transform: rotate(0deg) translateY(150px);
+  background-color: #fff;
+  border-radius: 50rpx;
+  padding-bottom: 50px;
+
+  :deep(.iconfont) {
+    width: 50px;
+    height: 50px;
+    font-size: 18px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: #333333 !important;
+  }
+}
+
+/* 适配iphonex 有底部横条的 */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+  .fixed-box {
+    bottom: constant(safe-area-inset-bottom);
+    bottom: env(safe-area-inset-bottom);
+  }
+}
+</style>

+ 210 - 0
src/components/oa-passBody/index.vue

@@ -0,0 +1,210 @@
+<template>
+  <view class="visit-box" :style="{ backgroundImage: 'url(' + bg + ')' }" v-if="show">
+    <!-- <view class="forget-pass" @tap="forgetpass">忘记密码</view> -->
+    <view class="visit-icon"><image :src="icon"></image></view>
+    <view class="visit-text">请输入密码</view>
+    <view class="visit-list">
+      <view class="visit-list-box" v-for="(item, index) in passselect" :key="index" :style="{ background: index < selectIndex ? '#FFFFFF' : 'transparent' }"> </view>
+    </view>
+    <view class="visit-Nine-palaces">
+      <view
+        class="visit-Nine-palaces-list"
+        v-for="(item, index) in Keylist"
+        :key="index"
+        :style="{ borderColor: styleClass(item.key), background: keytype(item.key, index) }"
+        @click="selectkey(item, index)"
+      >
+        <text v-if="item.key !== 'backspace'">{{ item.key }}</text>
+        <view class="qingchu-box" v-else><image src="./qingchu.png"></image></view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: "visit-box",
+  props: {
+    bg: {
+      type: String,
+      default: "",
+    },
+    icon: {
+      type: String,
+      default: "",
+    },
+    show: {
+      type: Boolean,
+      default: false,
+    },
+    selectColor: {
+      type: String,
+      default: "",
+    },
+  },
+  data() {
+    return {
+      passselect: new Array(6),
+      Keylist: [{ key: 1 }, { key: 2 }, { key: 3 }, { key: 4 }, { key: 5 }, { key: 6 }, { key: 7 }, { key: 8 }, { key: 9 }, { key: "#" }, { key: 0 }, { key: "backspace" }],
+      key_index: null,
+      select: [], //选择的时候,
+      selectIndex: null,
+      KeylistIndex: null,
+    };
+  },
+  methods: {
+    styleClass(type) {
+      // return typeof type !== "number" ? "transparent" : "#ffffff";
+      return "#ffffff";
+    },
+    keytype(type, index) {
+      if (this.key_index === index && typeof type === "number") {
+        type = this.selectColor;
+      } else {
+        type = "transparent";
+      }
+      return type;
+    },
+    //选择事件
+    selectkey(even, index) {
+      this.key_index = index;
+
+      if (even.key !== "backspace" && this.selectIndex < 6) {
+        this.select.push(even.key);
+        this.selectIndex++;
+        this.$forceUpdate();
+      } else if (even.key === "backspace") {
+        //点击清除的时候
+        if (this.selectIndex > 0) {
+          this.selectIndex--;
+        }
+        this.select.splice(this.selectIndex, 1);
+        this.$forceUpdate();
+      } else if (this.selectIndex === 6) {
+      }
+    },
+    forgetpass() {
+      this.$emit("forgetpass");
+    },
+  },
+  watch: {
+    select: {
+      handler(newName, oldName) {
+        if (newName.length === 6) {
+          this.$emit("select", newName.join(""));
+        }
+      },
+
+      immediate: true,
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.qingchu-box {
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  padding-top: 0.325rem;
+  & > image {
+    width: 50%;
+    height: 50%;
+  }
+}
+.visit-box {
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+  position: relative;
+  background-position: center center;
+  background-size: cover;
+  background: rgba(0, 0, 0, 0.3);
+  .forget-pass {
+    width: 144rpx;
+    height: 56rpx;
+    background: rgba(0, 0, 0, 0.3);
+    border-radius: 28rpx 0rpx 0rpx 28rpx;
+    font-size: 26rpx;
+    font-family: PingFang SC;
+    font-weight: 400;
+    line-height: 56rpx;
+    color: #ffffff;
+    opacity: 1;
+    text-align: center;
+    position: absolute;
+    top: 19rpx;
+    right: 0rpx;
+  }
+  .visit-icon {
+    width: 68rpx;
+    height: 68rpx;
+    opacity: 1;
+    margin: 0 auto;
+    margin-top: 180rpx;
+    & > image {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .visit-text {
+    width: 100%;
+    height: 42rpx;
+    font-size: 30rpx;
+    font-family: PingFang SC;
+    font-weight: bold;
+    line-height: 41rpx;
+    color: #ffffff;
+    opacity: 1;
+    text-align: center;
+    margin-top: 15rpx;
+  }
+  .visit-list {
+    width: 45vw;
+    height: 28rpx;
+    display: flex;
+    flex-wrap: wrap;
+    margin: 0 auto;
+    margin-top: 35rpx;
+    margin-bottom: 50rpx;
+    justify-content: center;
+    .visit-list-box {
+      width: 28rpx;
+      height: 28rpx;
+      border: 2px solid #ffffff;
+      border-radius: 50%;
+      margin-right: 20rpx;
+
+      &:nth-last-child(1) {
+        margin-right: 0rpx;
+      }
+    }
+  }
+  .visit-Nine-palaces {
+    width: 70vw;
+    height: 579rpx;
+    margin: 0 auto;
+    // border: 1rpx solid;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    .visit-Nine-palaces-list {
+      width: 128rpx;
+      height: 128rpx;
+      border: 4rpx solid #ffffff;
+      border-radius: 50%;
+      opacity: 1;
+      text-align: center;
+      font-size: 50rpx;
+      font-family: Roboto;
+      font-weight: bold;
+      line-height: 120rpx;
+      color: #ffffff;
+      margin-right: 50rpx;
+      &:nth-child(3n) {
+        margin-right: 0rpx;
+      }
+    }
+  }
+}
+</style>

二進制
src/components/oa-passBody/qingchu.png


+ 264 - 0
src/components/oa-scroll/index.vue

@@ -0,0 +1,264 @@
+<template>
+  <scroll-view
+    :class="customClass"
+    :style="{
+      //#ifdef APP-PLUS || MP-WEIXIN
+      height: `calc(100vh - (${!isSticky ? '0px' : '44px'}))`,
+      //#endif
+      //#ifdef H5
+      height: `calc(100vh - (${!isSticky ? '44px' : '88px'}))`,
+      //#endif
+      ...customStyle,
+    }"
+    :scroll-y="true"
+    scroll-with-animation
+    :refresher-threshold="refresherThreshold"
+    :refresher-default-style="refresherDefaultStyle"
+    :refresherEnabled="refresherEnabled"
+    :refresher-triggered="triggered"
+    :refresher-background="refresherBackground"
+    :scroll-top="scrollTop"
+    :scroll-into-view="scrollIntoView"
+    @refresherrefresh="onRefresh"
+    @scrolltolower="scrolltolower"
+  >
+    <slot name="topLoading" v-if="refresherDefaultStyle === 'none'">
+      <view
+        class="topBox"
+        :style="{
+          marginTop: '-' + refresherThreshold + 'px',
+          height: refresherThreshold + 'px',
+        }"
+      >
+        <view class="loader">
+          <view v-for="(v, i) in 10" :key="v" :style="{ transform: 'rotate(' + i * 36 + 'deg)', animationDelay: v == 10 ? 1 + 's' : '0.' + v + 's' }"> </view>
+        </view>
+        <view class="title">
+          {{ topTis }}
+        </view>
+      </view>
+    </slot>
+    <slot name="default"> </slot>
+    <slot name="bottomLoading">
+      <div class="bottoBox">
+        <span
+          v-show="total != 0 && refresherLoad && refresherLoadTitle"
+          :style="{
+            marginTop: '20px',
+            marginBottom: '20px',
+          }"
+        >
+          {{ pageSize >= total ? "没有更多啦~" : isScrolltolower }}
+        </span>
+        <span
+          v-show="total == 0 && refresherEnabled && refresherEnabledTitle && !triggered"
+          :style="{
+            marginTop: '20px',
+            marginBottom: '20px',
+          }"
+        >
+          <image style="width: 160px; height: 160px" src="@/static/images/data.png"></image>
+          <view>暂无数据</view>
+        </span>
+      </div>
+    </slot>
+  </scroll-view>
+</template>
+<script setup>
+import { onReady, onLoad, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance, watchEffect, toRefs, toRef, watch, computed } from "vue";
+
+const emit = defineEmits(["load", "refresh"]);
+const props = defineProps({
+  //当前页数量
+  pageSize: {
+    type: Number,
+    default: 30,
+  },
+  //数据总数
+  total: {
+    type: Number,
+    default: 0,
+  },
+  //设置滚动条位置
+  scrollTop: {
+    type: String,
+    default: "",
+  },
+  //设置外部class
+  customClass: {
+    type: String,
+    default: "",
+  },
+  //设置外部style
+  customStyle: {
+    type: [String, Object],
+    default: {},
+  },
+  //是否开启吸顶高度自适应
+  isSticky: {
+    type: Boolean,
+    default: false,
+  },
+  //是否开启上拉加载
+  refresherLoad: {
+    type: Boolean,
+    default: false,
+  },
+  //是否显示上拉加载文字
+  refresherLoadTitle: {
+    type: Boolean,
+    default: true,
+  },
+  //是否开启下拉刷新
+  refresherEnabled: {
+    type: Boolean,
+    default: false,
+  },
+  //是否显示下拉刷新文字
+  refresherEnabledTitle: {
+    type: Boolean,
+    default: true,
+  },
+  //距离顶部下拉刷新距离
+  refresherThreshold: {
+    type: Number,
+    default: 44,
+  },
+  //是否使用默认下拉刷新样式(支持设置 black、white、none/none 表示不使用默认样式)
+  refresherDefaultStyle: {
+    type: String,
+    default: "black",
+  },
+  //设置自定义下拉刷新区域背景颜色
+  refresherBackground: {
+    type: String,
+    default: "#fff",
+  },
+  //是否滚动到指定id的位置
+  scrollIntoView: {
+    type: String,
+    default: "",
+  },
+});
+
+const {
+  pageSize,
+  total,
+  scrollTop,
+  customClass,
+  customStyle,
+  refresherLoad,
+  refresherLoadTitle,
+  refresherEnabled,
+  refresherEnabledTitle,
+  refresherThreshold,
+  refresherDefaultStyle,
+  refresherBackground,
+} = toRefs(props);
+
+const state = reactive({
+  StatusBarHeight: computed(() => {
+    let systemInfo = uni.getSystemInfoSync();
+    return systemInfo.statusBarHeight + "px";
+  }),
+  tabBarHeight: computed(() => {
+    let systemInfo = uni.getSystemInfoSync();
+    return systemInfo.screenHeight - systemInfo.safeArea.bottom + "px";
+  }),
+  triggered: false,
+  topTis: "松手刷新",
+  isScrolltolower: "上拉加载更多",
+});
+
+const { StatusBarHeight, tabBarHeight, triggered, topTis, isScrolltolower } = toRefs(state);
+
+/**
+ * @scrollView上拉刷新
+ */
+function onRefresh() {
+  isScrolltolower.value = "上拉加载更多";
+  topTis.value = "努力加载中";
+  //做一个判断,判断triggered 是否为true
+  if (!triggered.value) {
+    triggered.value = true;
+    setTimeout((e) => {
+      triggered.value = false;
+      topTis.value = "松手刷新";
+      emit("refresh");
+    }, 1000);
+  }
+}
+
+/**
+ * @scrollView触底事件
+ */
+function scrolltolower(e) {
+  console.log("触底事件", e);
+  if (!refresherLoad.value || pageSize.value >= total.value) {
+    return;
+  } else {
+    isScrolltolower.value = "正在加载中~";
+    setTimeout(() => {
+      emit("load");
+      isScrolltolower.value = "上拉加载更多";
+    }, 1000);
+  }
+}
+
+onLoad((option) => {});
+</script>
+
+<style scoped>
+.topBox {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: gray;
+}
+
+.topBox .loader {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  position: relative;
+  margin-top: -25rpx;
+}
+
+.topBox .loader view {
+  width: 2px;
+  height: 6px;
+  background-color: gray;
+  transform-origin: 50% 150%;
+  position: absolute;
+  animation: color-change 1s infinite;
+}
+
+.topBox .title {
+  position: relative;
+  margin-left: 35rpx;
+  color: #c0c4cc;
+  font-size: 14px;
+}
+
+@keyframes color-change {
+  from {
+    background-color: gray;
+  }
+
+  to {
+    background-color: white;
+  }
+}
+
+.bottoBox {
+  display: flex;
+  align-items: center;
+  text-align: center;
+  justify-content: center;
+  color: #c0c4cc;
+  font-size: 14px;
+}
+</style>

+ 95 - 0
src/components/oa-steps/index.vue

@@ -0,0 +1,95 @@
+<!--
+ * @Description: 
+ * @Author: vivi
+ * @Date: 2023-05-06 14:58:54
+ * @LastEditTime: 2023-05-19 11:52:27
+-->
+<template>
+  <view class="step-status">
+    <view class="step-status-item" v-for="(li, index) in list" :key="index">
+      <view
+        class="step-status-item-lable"
+        :style="{
+          color: step >= index + 1 ? activeColor : '',
+        }"
+        @click="handleItem(li)"
+      >
+        {{ li }}
+      </view>
+
+      <uni-icons
+        type="forward"
+        class="step-status-item-icons"
+        size="15"
+        :style="{
+          color: step > index + 1 ? activeColor : '',
+          left: `calc((100% / ${list.length}) * ${index + 1})`,
+        }"
+        v-if="index != list.length - 1"
+      ></uni-icons>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: "Step-status",
+  props: {
+    list: {
+      type: Object,
+      default: [],
+    },
+    step: {
+      type: Number,
+      default: 1,
+    },
+    activeColor: {
+      type: String,
+      default: "#409EFF",
+    },
+  },
+  data() {
+    // 这里存放数据
+    return {};
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created() {},
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted() {},
+  methods: {
+    handleItem(event) {
+      this.$emit("stepsClick", event);
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.step-status {
+  position: relative;
+  width: 100%;
+  height: 40px;
+  opacity: 1;
+  background: #ffffff;
+  border-radius: 10px;
+  box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.06);
+
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+  padding: 0 10upx;
+
+  &-item {
+    display: flex;
+    // width: 100%;
+
+    &-lable {
+    }
+
+    &-icons {
+      position: absolute;
+      top: 13px;
+    }
+  }
+}
+</style>

+ 56 - 0
src/components/oa-tabbar/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <u-tabbar :value="tabbarValue" @change="tabbarChange" :fixed="true" :placeholder="true" :safeAreaInsetBottom="true" :border="true" :activeColor="proxy.$settingStore.themeColor.color">
+    <u-tabbar-item :text="tab.text" v-for="(tab, index) in tabbarList" :key="index" :dot="tab.dot">
+      <template #active-icon>
+        <!-- <image style="width: 24px; height: 24px" :src="proxy.$settingStore.themeColor.tabList[index]"></image> -->
+        <text class="iconfont font20" :class="tab.iconClass" :style="{ color: proxy.$settingStore.themeColor.color }"></text>
+      </template>
+      <template #inactive-icon>
+        <!-- <image style="width: 24px; height: 24px" :src="tab.iconPath"></image> -->
+        <text class="iconfont font20" :class="tab.iconClass" style="color: #7d7e80"></text>
+      </template>
+    </u-tabbar-item>
+  </u-tabbar>
+</template>
+<script setup>
+/*----------------------------------依赖引入-----------------------------------*/
+import { onReady, onLoad, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance, watchEffect, toRefs } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+/*----------------------------------公共方法引入-----------------------------------*/
+/*----------------------------------公共变量-----------------------------------*/
+const { proxy } = getCurrentInstance();
+/*----------------------------------变量声明-----------------------------------*/
+const props = defineProps({
+  //数据集
+  tabbarList: {
+    type: Object,
+    default: [],
+  },
+  //选中值
+  tabbarValue: {
+    type: Number,
+    default: 0,
+  },
+  //是否使用tabBar跳转页面
+  isSwitchTab: {
+    type: Boolean,
+    default: true,
+  },
+});
+
+const { tabbarValue, tabbarList, isSwitchTab } = toRefs(props);
+
+function tabbarChange(e) {
+  if (isSwitchTab.value) {
+    console.log(tabbarList.value[e].pagePath)
+    proxy.$tab.switchTab(tabbarList.value[e].pagePath);
+  } else {
+    proxy.$tab.redirectTo(tabbarList.value[e].pagePath);
+  }
+}
+
+onLoad((option) => {});
+</script>

+ 115 - 0
src/components/oa-timeLine-item/index.vue

@@ -0,0 +1,115 @@
+<template>
+  <view class="oa-timeLine-item">
+    <view class="oa-timeLine-item-header">
+      <view class="oa-timeLine-item-header-icon">
+        <u-icon name="info-circle-fill" :color="iconColor" size="18"></u-icon>
+      </view>
+      <view class="oa-timeLine-item-header-cont">
+        <view class="title">
+          {{ titleValue }}
+        </view>
+        <!-- <view style="margin: auto"> </view> -->
+        <view class="time">
+          {{ timeValue }}
+        </view>
+      </view>
+    </view>
+
+    <view class="oa-timeLine-item-content">
+      <view class="oa-timeLine-item-content-icon">
+        <view class="icon"></view>
+      </view>
+      <view class="oa-timeLine-item-content-cont">
+        <slot class="content"></slot>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { toRefs } from "vue";
+
+const props = defineProps({
+  titleValue: {
+    type: String,
+    default: "",
+  },
+  timeValue: {
+    type: String,
+    default: "",
+  },
+  iconColor: {
+    type: String,
+    default: "#149eff",
+  },
+});
+
+const { titleValue, timeValue, iconColor } = toRefs(props);
+</script>
+
+<style lang="scss" scoped>
+.oa-timeLine-item {
+  .oa-timeLine-item-header {
+    display: flex;
+    height: 25px;
+    line-height: 25px;
+
+    .oa-timeLine-item-header-icon {
+      margin: auto 0;
+    }
+
+    .oa-timeLine-item-header-cont {
+      display: flex;
+      width: calc(100% - 18px - 15px);
+      margin-left: 15px;
+
+      .title {
+        font-size: 15px;
+        color: #000000;
+        overflow: hidden; //超出的文本隐藏
+        text-overflow: ellipsis; //溢出用省略号显示
+        white-space: nowrap; // 默认不换行;
+      }
+
+      .time {
+        font-size: 14px;
+        color: #b5b5b5;
+        margin-left: auto;
+      }
+    }
+  }
+
+  .oa-timeLine-item-content {
+    display: flex;
+
+    .oa-timeLine-item-content-icon {
+      width: 18px;
+      display: flex;
+
+      .icon {
+        width: 2px;
+        background-color: #e4e7ed;
+        margin: -5px auto;
+      }
+    }
+
+    .oa-timeLine-item-content-cont {
+      width: 100%;
+      margin: 15px 0px 15px 15px;
+      padding: 15px;
+      background-color: #fff;
+      border-radius: 10px;
+    }
+  }
+}
+
+// .oa-timeLine-item:last-child {
+//   .oa-timeLine-item-content {
+//     .oa-timeLine-item-content-icon {
+//       .icon {
+//         margin-bottom: 15px;
+//       }
+//     }
+//   }
+// }
+</style>

+ 62 - 0
src/components/oa-timeLine/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <view class="oa-timeLine">
+    <view class="oa-timeLine-btn" :style="{ color: fontColor }">
+      <span @click="activitiesSortClick()">排序</span>
+    </view>
+    <slot></slot>
+  </view>
+</template>
+
+<script setup>
+import { ref, toRefs } from "vue";
+
+const emit = defineEmits(["activitiesSortChange"]);
+
+const props = defineProps({
+  sort: {
+    type: String,
+    default: "DESC",
+  },
+  fontColor: {
+    type: String,
+    default: "#149eff",
+  },
+});
+
+const { sort, fontColor } = toRefs(props);
+
+const _sort = ref(sort.value);
+
+function activitiesSortClick() {
+  if (_sort.value == "ASC") {
+    _sort.value = "DESC";
+  } else {
+    _sort.value = "ASC";
+  }
+
+  emit("activitiesSortChange", _sort.value);
+}
+</script>
+
+<style lang="scss" scoped>
+.oa-timeLine {
+  padding: 0px 15px;
+  padding-bottom: 15px;
+
+  .u-steps-item__wrapper {
+    background-color: #f1f1f1;
+  }
+
+  .oa-timeLine-btn {
+    margin: 0px -3.5px;
+    font-size: 13px;
+  }
+}
+
+.oa-timeLine-data {
+  padding: 0px 15px;
+  text-align: center;
+  color: #bdbdbd;
+  font-size: 14px;
+}
+</style>

+ 90 - 0
src/components/oa-touch/index.vue

@@ -0,0 +1,90 @@
+<template>
+  <view @touchstart="fingerstart" @touchend="fingerend">
+    <slot name="content"> </slot>
+  </view>
+</template>
+
+<script setup>
+import { onReady, onLoad, onShow, onNavigationBarButtonTap } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, watchEffect, getCurrentInstance, toRefs } from "vue";
+
+const emit = defineEmits(["touchChange"]);
+const props = defineProps({});
+const {} = toRefs(props);
+
+const startData = ref({
+  clientX: "",
+  clientY: "",
+});
+const updDistance = ref(100);
+const lrDistance = ref(50);
+const topMed = ref("");
+const bottomMed = ref("");
+const leftMed = ref("");
+const rightMed = ref("");
+
+/**
+ * @当按下去的时候
+ */
+function fingerstart(e) {
+  // 记录 距离可视区域左上角 左边距 和 上边距
+  startData.value.clientX = e.changedTouches[0].clientX;
+  startData.value.clientY = e.changedTouches[0].clientY;
+}
+
+/**
+ * @当抬起来的时候
+ */
+function fingerend(e) {
+  // 当前位置 减去 按下位置 计算 距离
+  const subX = e.changedTouches[0].clientX - startData.value.clientX;
+  const subY = e.changedTouches[0].clientY - startData.value.clientY;
+  if (subY > updDistance.value || subY < -updDistance.value) {
+    if (subY > updDistance.value) {
+      bottomscroll(subY);
+    } else if (subY < -updDistance.value) {
+      topscroll(subY);
+    }
+  } else {
+    if (subX > lrDistance.value) {
+      rightscroll(subX);
+    } else if (subX < -lrDistance.value) {
+      leftscroll(subX);
+    } else {
+      console.log("无效操作");
+    }
+  }
+}
+/**
+ * @上滑触发
+ */
+function topscroll(dista) {
+  topMed.value ? (topMed.value = dista) : (topMed.value = null);
+  // console.log("触发了上滑方法!");
+  emit("change", "上滑");
+}
+/**
+ * @下滑触发
+ */
+function bottomscroll(dista) {
+  bottomMed.value ? (bottomMed.value = dista) : (bottomMed.value = null);
+  // console.log("触发了下滑方法!");
+  emit("change", "下滑");
+}
+/**
+ * @右滑触发
+ */
+function rightscroll(dista) {
+  rightMed.value ? (rightMed.value = dista) : (rightMed.value = null);
+  // console.log("触发了右滑方法!");
+  emit("change", "右滑");
+}
+/**
+ * @左滑触发
+ */
+function leftscroll(dista) {
+  leftMed.value ? (leftMed.value = dista) : (leftMed.value = null);
+  // console.log("触发了左滑方法!");
+  emit("change", "左滑");
+}
+</script>

+ 119 - 0
src/components/oa-transForm/index.vue

@@ -0,0 +1,119 @@
+<template>
+  <view
+    class="content-section"
+    :style="[
+      {
+        transform: coverTransform,
+        transition: coverTransition,
+      },
+    ]"
+    @touchstart="coverTouchstart"
+    @touchmove="coverTouchmove"
+    @touchend="coverTouchend"
+  >
+    <image class="mine-image" src="@/static/images/mine/arc.png"></image>
+    <slot name="content"></slot>
+  </view>
+</template>
+<script setup>
+/*----------------------------------依赖引入-----------------------------------*/
+import { onReady, onLoad, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance, watchEffect, toRefs } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+/*----------------------------------公共方法引入-----------------------------------*/
+/*----------------------------------公共变量-----------------------------------*/
+const { proxy } = getCurrentInstance();
+/*----------------------------------变量声明-----------------------------------*/
+const state = reactive({
+  coverTransform: "translateY(0px)",
+  coverTransition: "0s",
+  moving: false,
+});
+
+const { coverTransform, coverTransition, moving } = toRefs(state);
+
+let startY = 0,
+  moveY = 0,
+  pageAtTop = true;
+
+/**
+ * @触摸开始
+ */
+function coverTouchstart(e) {
+  if (pageAtTop === false) {
+    return;
+  }
+  coverTransition.value = "transform .1s linear";
+  startY = e.touches[0].clientY;
+}
+
+/**
+ * @触摸移动
+ */
+function coverTouchmove(e) {
+  moveY = e.touches[0].clientY;
+  let moveDistance = moveY - startY;
+  if (moveDistance < 0) {
+    moving.value = false;
+    return;
+  }
+  moving.value = true;
+  if (moveDistance >= 80 && moveDistance < 100) {
+    moveDistance = 80;
+  }
+  if (moveDistance > 0 && moveDistance <= 80) {
+    coverTransform.value = `translateY(${moveDistance}px)`;
+  }
+}
+
+/**
+ * @触摸结束
+ */
+function coverTouchend() {
+  if (moving.value === false) {
+    return;
+  }
+  moving.value = false;
+  coverTransition.value = "transform 0.3s cubic-bezier(.21,1.93,.53,.64)";
+  coverTransform.value = "translateY(0px)";
+}
+
+onLoad((option) => {});
+</script>
+<style lang="scss" scoped>
+.content-section {
+  position: relative;
+  margin-top: -85px;
+  padding-bottom: 50.67px;
+  background-color: #f5f6f7;
+
+  .mine-image {
+    position: absolute;
+    left: 0;
+    top: -16px;
+    width: 100%;
+    height: 18px;
+  }
+
+  .mine-actions {
+    margin: 0.625rem 0.625rem;
+    padding: 20px 0px;
+    border-radius: 8px;
+    background-color: white;
+
+    .action-item {
+      .icon {
+        font-size: 28px;
+      }
+
+      .text {
+        display: block;
+        font-size: 13px;
+        margin: 8px 0px;
+      }
+    }
+  }
+}
+</style>

+ 70 - 0
src/components/oa-ttsAudio/index.vue

@@ -0,0 +1,70 @@
+<template></template>
+<script setup>
+import { onReady, onLoad, onUnload, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
+import { defineComponent, getCurrentInstance, inject, nextTick, onMounted, watchEffect, ref } from "vue";
+
+const emit = defineEmits(["onEnded", "onError"]);
+const props = defineProps({
+  //播放原地址
+  audioUrl: {
+    type: String,
+    defualt: "",
+  },
+  //是否开启
+  audioBool: {
+    type: Boolean,
+    defualt: "",
+  },
+});
+
+const isPlay = ref(false);
+const innerAudio = ref(null);
+
+function handleTTS() {
+  if (!audioBool) {
+    return;
+  }
+
+  if (innerAudio.value) {
+    try {
+      innerAudio.value.pause();
+      innerAudio.value.destroy();
+      innerAudio.value = null;
+    } catch (e) {
+      //TODO handle the exception
+    }
+  }
+
+  innerAudio.value = uni.createInnerAudioContext();
+  innerAudio.value.autoplay = true;
+  innerAudio.value.volume = 1;
+  innerAudio.value.src = props.audioUrl;
+  innerAudio.value.onPlay(() => {
+    console.log("开始播放");
+  });
+  innerAudio.value.onEnded((res) => {
+    console.log("音频播放结束");
+    isPlay.value = false;
+  });
+  innerAudio.value.onError((res) => {
+    console.log("音频播放出错" + res);
+    console.log(res.errCode);
+  });
+
+  innerAudio.value.play();
+}
+
+watchEffect(() => {
+  if (props.audioUrl) {
+    handleTTS();
+  }
+});
+
+onUnload(() => {
+  if (innerAudio.value) {
+    innerAudio.value.stop();
+    innerAudio.value.destroy();
+  }
+});
+</script>
+<style lang="scss" scoped></style>

+ 91 - 0
src/components/oa-ttsAudio/ttsAudio.js

@@ -0,0 +1,91 @@
+// 这个东西我都没执行yarn add crypto竟然能用,可能另一个项目安装了全局共享了,如报错找不到,执行一下yarn add crypto
+import crypto from 'crypto'
+
+export class AccessToken {
+    static encodeText(text) {
+        let encodedText = encodeURIComponent(text);
+        return encodedText.replace('+', '%20').replace('*', '%2A').replace('~', '%7E');
+    }
+
+    static encodeDict(dict) {
+        let keys = Object.keys(dict).sort();
+        return keys.map(key => `${this.encodeText(key)}=${this.encodeText(dict[key])}`).join('&');
+    }
+
+    static async createToken(accessKeyId, accessKeySecret) {
+        const parameters = {
+            AccessKeyId: accessKeyId,
+            Action: 'CreateToken',
+            Format: 'JSON',
+            RegionId: 'cn-shanghai',
+            SignatureMethod: 'HMAC-SHA1',
+            SignatureNonce: uuidv4(),
+            SignatureVersion: '1.0',
+            Timestamp: new Date().toISOString(),
+            Version: '2019-02-28'
+        };
+
+        const queryString = this.encodeDict(parameters);
+        console.log('Normalized request string:', queryString);
+
+        const stringToSign = `GET&${this.encodeText('/')}&${this.encodeText(queryString)}`;
+        console.log('String to sign:', stringToSign);
+
+        const hmac = crypto.createHmac('sha1', `${accessKeySecret}&`);
+        hmac.update(stringToSign);
+        const signature = hmac.digest('base64');
+        console.log('Signature:', signature);
+
+        const encodedSignature = this.encodeText(signature);
+        console.log('URL-encoded signature:', encodedSignature);
+
+        const fullUrl = `https://nls-meta.cn-shanghai.aliyuncs.com/?Signature=${encodedSignature}&${queryString}`;
+        console.log('URL:', fullUrl);
+
+        let resData =  await new Promise((resolve, reject) => {
+            uni.request({
+                url: fullUrl,
+                method: 'GET',
+                success: res => {
+                    const data = res.data
+                    resolve({
+                        token: data.Token.Id,
+                        expireTime: data.Token.ExpireTime
+                    })
+                },
+                fail: error => {
+                    console.log(error)
+                    reject(error)
+                }
+            })
+        })
+         console.log('res',resData)
+         if(resData){
+            return resData
+         }
+        // Using fetch for HTTP request
+        // const response = await fetch(fullUrl);
+        // if (response.ok) {
+        //     const jsonResponse = await response.json();
+        //     if (jsonResponse.Token) {
+        //         return {
+        //             token: jsonResponse.Token.Id,
+        //             expireTime: jsonResponse.Token.ExpireTime
+        //         };
+        //     }
+        // }
+        // console.error(await response.text());
+        return {
+            token: null,
+            expireTime: null
+        };
+    }
+}
+
+// Sample UUIDv4 function, or you could use a library like `uuid`
+function uuidv4() {
+    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+        return v.toString(16);
+    });
+}

+ 303 - 0
src/components/oa-upgrade/index.vue

@@ -0,0 +1,303 @@
+<template>
+  <view class="upgrade-popup" v-if="isModalShow">
+    <image class="header-bg" src="@/static/images/common/oa-upgrade.png" mode="widthFix"></image>
+    <view
+      class="iconfont oaIcon-upgrade header-bg"
+      style="font-size: 110px; margin-top: -110px"
+      :style="{
+        color: themesColor,
+        fill: themesColor,
+      }"
+    ></view>
+
+    <view class="main">
+      <view
+        class="version"
+        :style="{
+          color: themesColor,
+        }"
+      >
+        发现新版本v{{ versionName }}
+      </view>
+      <view class="content">
+        <text class="desc">{{ versionDesc }}</text>
+      </view>
+      <!--下载状态-进度条显示 -->
+      <view class="footer" v-if="isStartDownload">
+        <view class="progress-view" :class="{ active: !hasProgress }" @click="handleInstallApp">
+          <!-- 进度条 -->
+          <view v-if="hasProgress" style="height: 100%">
+            <view class="txt">{{ percentText }}</view>
+            <view class="progress" :style="setProStyle"></view>
+          </view>
+          <view v-else>
+            <view class="btn upgrade force">{{ isDownloadFinish ? "立即安装" : "下载中..." }}</view>
+          </view>
+        </view>
+      </view>
+      <!-- 强制更新 -->
+      <view class="footer" v-else-if="isForceUpdate">
+        <view class="btn upgrade force" @click="handleUpgrade">立即更新</view>
+      </view>
+      <!-- 可选择更新 -->
+      <view class="footer" v-else>
+        <view class="btn close" @click="handleCancel">以后再说</view>
+        <view
+          class="btn upgrade"
+          :style="{
+            backgroundColor: themesColor,
+          }"
+          @click="handleUpgrade"
+        >
+          立即更新
+        </view>
+      </view>
+    </view>
+  </view>
+  <view class="upgrade-show" v-if="isModalShow" @click="handleCancel"></view>
+</template>
+<script setup>
+import { onReady, onLoad, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance, watchEffect, toRefs, computed } from "vue";
+
+import { downloadApp, installApp } from "@/utils/upgrade.js";
+
+const { proxy } = getCurrentInstance();
+const props = defineProps({
+  //主题颜色
+  themesColor: {
+    type: String,
+    default: "#3f99ff",
+  },
+});
+const emits = defineEmits([]);
+const { themesColor } = toRefs(props);
+
+const checkInfo = reactive({
+  isModalShow: false, //是否打开弹窗
+  isForceUpdate: false, //是否强制更新
+  versionName: undefined, //版本名称
+  versionDesc: undefined, //更新说明
+  downloadUrl: undefined, //APP下载链接
+  isDownloadFinish: false, //是否下载完成
+  hasProgress: false, //是否能显示进度条
+  currentPercent: 0, //当前下载百分比
+  isStartDownload: false, //是否开始下载
+  newFileName: "", //下载后app本地路径名称
+});
+
+const { isModalShow, isForceUpdate, versionName, versionDesc, downloadUrl, isDownloadFinish, hasProgress, currentPercent, isStartDownload, newFileName } = toRefs(checkInfo);
+
+//设置进度条样式,实时更新进度位置
+const setProStyle = computed(() => {
+  return {
+    width: (510 * currentPercent.value) / 100 + "rpx", //510:按钮进度条宽度
+  };
+});
+
+//百分比文字
+const percentText = computed(() => {
+  let percent = currentPercent.value;
+  if (typeof percent !== "number" || isNaN(percent)) return "下载中...";
+  if (percent < 100) return `下载中${percent}%`;
+  return "立即安装";
+});
+
+//更新
+function handleUpgrade() {
+  if (downloadUrl.value) {
+    isStartDownload.value = true;
+    //开始下载App
+    downloadApp(downloadUrl.value, (current) => {
+      //下载进度监听
+      hasProgress.value = true;
+      currentPercent.value = current;
+    })
+      .then((fileName) => {
+        //下载完成
+        isDownloadFinish.value = true;
+        newFileName.value = fileName;
+        if (fileName) {
+          //自动安装App
+          handleInstallApp();
+        }
+      })
+      .catch((e) => {
+        console.log(e, "e");
+      });
+  } else {
+    uni.showToast({
+      title: "下载链接不存在",
+      icon: "none",
+    });
+  }
+}
+
+//安装app
+function handleInstallApp() {
+  //下载完成才能安装,防止下载过程中点击
+  if (isDownloadFinish.value && newFileName.value) {
+    installApp(newFileName.value, () => {
+      //安装成功,关闭升级弹窗
+      isModalShow.value = false;
+    });
+  }
+}
+
+/** 打开弹窗 */
+function openUpgrade(e) {
+  isModalShow.value = true;
+
+  versionName.value = e.modalArray.buildVersion;
+  versionDesc.value = e.modalArray.buildUpdateDescription.replace(/\\n/, "\n");
+  downloadUrl.value = e.modalArray.downloadURL;
+}
+
+/** 取消按钮 */
+function handleCancel() {
+  isModalShow.value = false;
+}
+
+onLoad((option) => {});
+
+defineExpose({
+  openUpgrade,
+});
+</script>
+
+<style lang="scss" scoped>
+.upgrade-popup {
+  width: 580rpx;
+  height: auto;
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  background: #fff;
+  border-radius: 20rpx;
+  box-sizing: border-box;
+  border: 1px solid #eee;
+  z-index: 10090;
+}
+
+.header-bg {
+  display: flex;
+  width: 230rpx;
+  margin: -112rpx auto 0 auto;
+}
+
+.main {
+  padding: 10rpx 30rpx 30rpx;
+  box-sizing: border-box;
+  .version {
+    font-size: 36rpx;
+    font-weight: 700;
+    width: 100%;
+    text-align: center;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    letter-spacing: 1px;
+  }
+
+  .content {
+    margin-top: 60rpx;
+    .desc {
+      display: block;
+      box-sizing: border-box;
+      margin-top: 20rpx;
+      font-size: 28rpx;
+      color: #6a6a6a;
+      max-height: 80vh;
+      overflow-y: auto;
+    }
+  }
+
+  .footer {
+    width: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    flex-shrink: 0;
+    margin-top: 100rpx;
+
+    .btn {
+      width: 246rpx;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      position: relative;
+      z-index: 999;
+      height: 90rpx;
+      box-sizing: border-box;
+      font-size: 30rpx;
+      border-radius: 10rpx;
+      letter-spacing: 2rpx;
+
+      &.force {
+        width: 500rpx;
+      }
+
+      &.close {
+        border: 1px solid #e0e0e0;
+        margin-right: 25rpx;
+        color: #000;
+      }
+
+      &.upgrade {
+        color: white;
+      }
+    }
+
+    .progress-view {
+      width: 510rpx;
+      height: 90rpx;
+      display: flex;
+      position: relative;
+      align-items: center;
+      border-radius: 6rpx;
+      background-color: #dcdcdc;
+      display: flex;
+      justify-content: flex-start;
+      padding: 0px;
+      box-sizing: border-box;
+      border: none;
+      overflow: hidden;
+
+      &.active {
+        background-color: #026df7;
+      }
+
+      .progress {
+        height: 100%;
+        background-color: #026df7;
+        padding: 0px;
+        box-sizing: border-box;
+        border: none;
+        border-top-left-radius: 10rpx;
+        border-bottom-left-radius: 10rpx;
+      }
+
+      .txt {
+        font-size: 28rpx;
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        color: #fff;
+      }
+    }
+  }
+}
+
+.upgrade-show {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 10085;
+  background: rgba(0, 0, 0, 0.6);
+}
+</style>

+ 205 - 0
src/components/oa-upload/index.vue

@@ -0,0 +1,205 @@
+<template>
+  <!-- 图片上传数量等于1显示 -->
+  <view class="oa-upload" v-if="uploadCount == 1">
+    <view v-if="uploadImage" class="uploadView" :style="uploadStyle">
+      <view class="uploadUimage">
+        <u-image width="100%" height="100%" :src="uploadImage" @click="handlePreviewImage(uploadImage)"></u-image>
+      </view>
+
+      <view v-if="!uploadCloseStatus" class="uploadViewClose" @click="uploadViewClose()">
+        <u-icon name="close" color="#ffffff" size="12"></u-icon>
+      </view>
+    </view>
+    <view v-if="!uploadImage" :class="uploadCloseStatus ? 'uploadView upload-buttom uploadDisabled' : 'uploadView upload-buttom'" :style="uploadStyle" @click="uploadClick()">
+      <u-icon style="margin: auto" name="plus" color="#909399" :size="uploadIconSize"></u-icon>
+    </view>
+  </view>
+
+  <!-- 图片上传数量大于1显示 -->
+  <view class="oa-upload" v-if="uploadCount > 1">
+    <view class="uploadView" :style="uploadStyle" v-for="(up, index) in uploadList" :key="index">
+      <view class="uploadUimage">
+        <u-image width="100%" height="100%" :src="up[uploadListSrc]" @click="handlePreviewImage(up[uploadListSrc])"></u-image>
+      </view>
+
+      <view v-if="!uploadCloseStatus" class="uploadViewClose" @click="uploadViewClose(index)">
+        <u-icon name="close" color="#ffffff" size="12"></u-icon>
+      </view>
+    </view>
+    <view v-if="uploadCount > uploadList.length" :class="uploadCloseStatus ? 'uploadView upload-buttom uploadDisabled' : 'uploadView upload-buttom'" :style="uploadStyle" @click="uploadClick()">
+      <u-icon style="margin: auto" name="plus" color="#909399" :size="uploadIconSize"></u-icon>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { uploadAvatar } from "@/api/system/user.js";
+import { ref, toRefs } from "vue";
+
+const emit = defineEmits(["uploadSuccessChange", "uploadDeleteChange"]);
+
+const props = defineProps({
+  //图片路径(uploadCount为1时使用)
+  uploadImage: {
+    type: String,
+    default: "",
+  },
+  //图片集合(uploadCount大于1时使用)
+  uploadList: {
+    type: Object,
+    default: [],
+  },
+  //图片集合src(uploadCount大于1时使用)
+  uploadListSrc: {
+    type: String,
+    default: "url",
+  },
+  //样式
+  uploadStyle: {
+    type: Object,
+    default: {
+      width: "80px",
+      height: "80px",
+    },
+  },
+  //icon大小
+  uploadIconSize: {
+    type: String,
+    default: "20px",
+  },
+  //是否禁用
+  uploadCloseStatus: {
+    type: Boolean,
+    default: false,
+  },
+  //上传图片数量
+  uploadCount: {
+    type: Number,
+    default: 1,
+  },
+});
+
+const { uploadImage, uploadList, uploadListSrc, uploadStyle, uploadIconSize, uploadCloseStatus, uploadCount } = toRefs(props);
+
+/**
+ * @查看图片
+ * @点击事件
+ */
+function handlePreviewImage(url) {
+  uni.previewImage({
+    urls: [url],
+    current: url,
+  });
+}
+
+/**
+ * @upload图片上传
+ * @点击事件
+ */
+function uploadClick() {
+  uni.chooseImage({
+    count: uploadCount.value, //默认9
+    sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
+    sourceType: ["album", "camera"], //从相册选择、摄像头
+    success: function (res) {
+      res.tempFilePaths.forEach((url) => {
+        if (uploadCount.value >= uploadList.value.length) {
+          uploadApi(url);
+        }
+      });
+    },
+  });
+}
+
+/**
+ * @upload图片上传
+ * @api接口请求
+ */
+function uploadApi(url) {
+  let data = { name: "file", filePath: url };
+  uploadAvatar(data).then((response) => {
+    emit("uploadSuccessChange", response.data);
+  });
+}
+
+/**
+ * @upload图片上传
+ * @点击事件
+ * @删除事件
+ */
+function uploadViewClose(index) {
+  if (uploadCount.value > 1) {
+    uploadList.value.splice(index, 1);
+    emit("uploadDeleteChange", uploadList.value);
+  } else {
+    emit("uploadDeleteChange", "");
+  }
+}
+</script>
+
+<style scoped>
+.oa-upload {
+  width: 100%;
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.uploadView {
+  position: relative;
+  overflow: hidden;
+  width: calc(33% - 10px);
+  height: 110px;
+  margin: 0 10px 10px 0;
+  border: 1px solid #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+}
+
+.uploadView .uploadUimage {
+  height: 100%;
+}
+
+.uploadView .uploadUimage > uni-view {
+  height: 100%;
+}
+
+.uploadView .uploadViewClose {
+  position: absolute;
+  background-color: #409eff;
+  transform: rotate(45deg);
+  width: 40px;
+  height: 24px;
+  text-align: center;
+  right: -15px;
+  top: -6px;
+  cursor: pointer;
+}
+
+.uploadView .uploadViewClose .u-icon {
+  font-size: 12px;
+  margin-top: 4px;
+  margin-left: 10px;
+  transform: rotate(-45deg);
+  color: #ffffff;
+}
+
+.uploadView img {
+  width: 100%;
+}
+
+.upload-buttom {
+  display: flex;
+  font-size: 28px;
+  color: #909399;
+  border: 1px dashed #d9d9d9;
+  background-color: #fafafa;
+}
+
+.upload-buttom:hover {
+  border: 1px dashed #409eff;
+}
+
+:deep(.uploadDisabled) {
+  display: none !important;
+}
+</style>

+ 111 - 0
src/components/oa-weather/index.vue

@@ -0,0 +1,111 @@
+<template>
+  <!-- 天气 -->
+  <view class="app-common-window bg-white radius shadow-default" v-if="state.weatherData.length != 0">
+    <view class="weather radius">
+      <view class="weather-header">
+        <view class="iconfont oaIcon-address"></view>
+        <view class="weather-header-location">{{ state.weatherData[0].location || "上海" }}</view>
+        <view class="weather-header-day">今天</view>
+        <view class="weather-header-text">{{ state.weatherData[0].text || "未知" }}</view>
+      </view>
+      <view class="weather-center">
+        <view class="weather-center-temperature">{{ state.weatherData[0].today.low + "/" + state.weatherData[0].today.high || "0℃" }}</view>
+        <image class="weather-center-image" :src="`${'/static/icons/white/' + state.weatherData[0].code.now + '.png'}`" mode="heightFix"></image>
+      </view>
+    </view>
+  </view>
+</template>
+<script setup>
+import { onReady, onLoad, onShow, onNavigationBarButtonTap, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
+import { ref, onMounted, inject, shallowRef, reactive, getCurrentInstance, watchEffect, toRefs, toRef, watch } from "vue";
+
+const emit = defineEmits(["load", "refresh"]);
+const props = defineProps({});
+
+const state = reactive({
+  weatherData: [],
+});
+
+/**
+ * @获取天气信息
+ */
+function getWeather(string) {
+  uni.request({
+    url: `https://widget-v3.seniverse.com/api/weather/7b8a7d89-f01d-4b14-bdec-5ae0b82c857f?unit=c&language=zh-Hans&location=${string}`,
+    success: (res) => {
+      if (res.statusCode == 200) {
+        state.weatherData = res.data.results[0].data;
+      } else {
+        console.log("获取天气信息失败");
+      }
+    },
+    fail: (err) => {
+      console.log("获取天气信息失败", err);
+    },
+  });
+}
+
+onLoad((option) => {});
+
+onShow(() => {});
+
+// 暴露变量
+defineExpose({
+  getWeather,
+});
+</script>
+
+<style lang="scss" scoped>
+.app-common-window {
+  position: relative;
+  margin-top: -20px;
+  margin: -20px 20upx 20upx 20upx;
+  z-index: 50;
+
+  .weather {
+    padding: 10px;
+    background-image: url("@/static/images/index/weather.png");
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: no-repeat;
+
+    .weather-header {
+      display: flex;
+      margin-bottom: 10px;
+      font-size: 14px;
+
+      &-location {
+        margin-right: 15px;
+      }
+
+      &-day {
+        margin: 0 auto 0 0;
+      }
+
+      &-text {
+        margin: 0;
+      }
+
+      .iconfont {
+        margin: auto 5px auto 0;
+        color: #0c83fa;
+      }
+    }
+
+    .weather-center {
+      display: flex;
+
+      &-temperature {
+        margin: auto auto auto 0;
+        font-size: 18px;
+        font-weight: 600;
+      }
+
+      &-image {
+        margin: 0 0 0 0;
+        height: 30px !important;
+      }
+    }
+  }
+}
+</style>

+ 83 - 0
src/components/searchSelect/searchSelect.vue

@@ -0,0 +1,83 @@
+<template>
+  <view>
+    <view class="searchSelect shadow" v-if="flag">
+      <view class="cu-bar search bg-white">
+        <view class="search-form round" style="margin-top: 0">
+          <uni-easyinput type="text" v-model="searchInput" placeholder="" prefixIcon="search" @focus="InputFocus" @blur="InputBlur" @input="handleInput()" style="border: none" />
+          <text class="cuIcon-search"></text>
+        </view>
+      </view>
+      <view class="select-items">
+        <view class="select-item" v-for="(item, index) in searchList" @click="clickSelectItem(item, index)" :key="index">{{ item }}</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: "searchSelect",
+  props: {
+    flag: {
+      type: Boolean,
+      default: false,
+    },
+    searchList: {
+      type: Array,
+      // default: []
+    },
+    searchList2: {
+      type: Array,
+      // default: []
+    },
+  },
+  data() {
+    return {
+      searchInput: "",
+      // flag: false,
+      // searchList: [],
+      // searchList2: [],
+    };
+  },
+  onload() {
+    this.getSearchList();
+  },
+  methods: {
+    //请求
+    async getSearchList(ming = {}) {
+      const res = await this.$myRequest({
+        url: "Index/getSiteDropDownBox",
+        data: ming,
+      });
+      res.data.data.forEach((item) => {
+        this.searchList.push(item.siteName);
+        this.searchList2.push(item.siteName);
+      });
+    },
+    // 下拉选择
+    clickSelectItem(item, index) {
+      this.getSearchList({
+        siteName: item,
+      });
+      this.flag = false;
+    },
+    handleInput() {
+      var newlist = this.searchList2.filter((item) => item.indexOf(this.searchInput) > -1);
+      this.searchList = newlist;
+    },
+
+    InputFocus(e) {
+      this.InputBottom = e.detail.height;
+    },
+    InputBlur(e) {
+      this.InputBottom = 0;
+    },
+
+    changeTab(Inv) {
+      that.navIdx = Inv;
+    },
+  },
+};
+</script>
+
+<style></style>

+ 236 - 0
src/components/yealuo-select/yealuo-select.vue

@@ -0,0 +1,236 @@
+<template>
+	<view class="yealuo-select" >
+		<view class="yealuo-background" @tap="isShow=false" v-show="isShow"></view>
+		<view class="yealuo-con" :style="inputStyle" @tap='isShow=isShow?false:nowData.length'>
+			<slot name='left'></slot>
+			<uni-easyinput type="text" v-model="theValue" :placeholder="placeholder" prefixIcon="search" @input="theInput"  @focus="theFocus" @blur="theBlur" :disabled="theDisabled"/>
+			<slot name='right' v-if="selectIco">
+				<svg class="icon" v-if="!isShow" style="width: 1.5em; height: 1.5em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="530"><path d="M512 714.666667c-8.533333 0-17.066667-2.133333-23.466667-8.533334l-341.333333-341.333333c-12.8-12.8-12.8-32 0-44.8 12.8-12.8 32-12.8 44.8 0l320 317.866667 317.866667-320c12.8-12.8 32-12.8 44.8 0 12.8 12.8 12.8 32 0 44.8L533.333333 704c-4.266667 8.533333-12.8 10.666667-21.333333 10.666667z" p-id="531"></path></svg>
+				<svg class="icon" v-else style="width: 1.5em; height: 1.5em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1927"><path d="M904.533333 674.133333l-362.666666-362.666666c-17.066667-17.066667-42.666667-17.066667-59.733334 0l-362.666666 362.666666c-17.066667 17.066667-17.066667 42.666667 0 59.733334 17.066667 17.066667 42.666667 17.066667 59.733333 0L512 401.066667l332.8 332.8c8.533333 8.533333 19.2 12.8 29.866667 12.8s21.333333-4.266667 29.866666-12.8c17.066667-17.066667 17.066667-42.666667 0-59.733334z" p-id="1928"></path></svg>
+			</slot>
+		</view>
+		<view class="yealuo-select" v-show="show" :style="selectStyle">
+			<view class="data">
+				<radio-group v-if="checkType=='radio'"  @change="selectCheckbox">
+				<view class="select-item" :class="'item-'+overflow" v-for="(item, index) in nowData" :key="index" >
+					<label class="item-text" :class="{active: theValue==item.value}">
+					<radio name="name1" checked v-if="theValue==item.value" :value="item.value+'|'+item.id"></radio>
+					<radio name="name1" v-else :value="item.value+'|'+item.id"></radio>
+					{{item.value}}
+					</label>
+				</view>
+				</radio-group>
+				<checkbox-group v-else-if="checkType=='checkbox'" @change="selectCheckbox">
+				<view class="select-item" :class="'item-'+overflow" v-for="(item, index) in nowData" :key="index" >
+					<label class="item-text" :class="{active: theValue.indexOf(item.value)!=-1 }">
+					<checkbox name="name1" checked v-if="theValue.indexOf(item.value)!=-1 " :value="item.value+'|'+item.id"></checkbox>
+					<checkbox name="name1" v-else :value="item.value+'|'+item.id"></checkbox>
+					{{item.value}}
+					</label>
+				</view>
+				</checkbox-group>
+				<radio-group v-else  @change="selectCheckbox">
+				<view class="select-item" :class="'item-'+overflow" v-for="(item, index) in nowData" :key="index" >
+					<label class="item-text" :class="{active: theValue==item.value}">
+					<radio name="name1" style="display: none;" checked v-if="theValue==item.value" :value="item.value+'|'+item.id"></radio>
+					<radio name="name1" style="display: none;" v-else :value="item.value+'|'+item.id"></radio>
+					{{item.value}}
+					</label>
+				</view>
+				</radio-group>
+			</view>
+			<view class="item-close" @tap="isShow=false">收起</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * v1.0.3
+	 * 最后修改: 2021.02.02
+	 * 创建: 2020.9.30
+	 * Author:yealuo.com
+	 * contact:470797533@qq.com
+	 */
+	let fontUnit = 'upx'
+	// #ifdef MP-WEIXIN
+	fontUnit = 'rpx'
+	// #endif
+	export default {
+		name: 'yealuoInputs',
+		props: {
+			placeholder: {
+				type: String,
+				default: ''
+			},
+			value: {
+				type: String,
+				default: ''
+			},
+			checkType: {
+				type: String,
+				default: ''
+			},
+			itemKey: {
+				type: String,
+				default: ''
+			},
+			width: {
+				type: String,
+				default: '600'
+			},
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			inputStyle: {
+				type: String,
+				default: ''
+			},
+			selectStyle: {
+				type: String,
+				default: ''
+			},
+			overflow: {
+				type: String,
+				default: 'auto'
+			},
+			tags: {
+				type: String,
+				default: ''
+			},
+			
+			binData:{
+				type:Array,
+				default: ''
+			},
+			selectIco:{
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				odData:this.binData,
+				nowData:this.binData,
+				isShow: false,
+				theValue: this.value,
+				theDisabled: this.disabled
+			}
+		},
+		watch: {
+			value(val){
+				this.theValue = val;
+			},
+			//监听数据变化
+			nowData:{
+				handler:function(){
+				 this.nowData=this.binData;
+				},
+				deep:true
+			}
+			
+		},
+		computed: {
+			show(){
+				return this.isShow && this.nowData.length
+			}
+			
+		},
+		methods: {
+			//获取焦点
+			theFocus(e){
+				this.nowData=this.odData;
+			},
+			//失去焦点
+			theBlur(e){
+				this.$emit('blur',e)
+			},
+			//获取输入值
+			theInput(e) {
+				var val=e.detail.value;
+				let data = [];
+				var odData=this.odData;
+				for(var i=0;i<odData.length;i++){
+					var isHas=false;
+					if(odData[i].value.indexOf(val)!=-1){
+						data.push(odData[i])
+						if(odData[i].value==val){
+							isHas=true;
+							var arr=[];
+							arr.push(odData[i].value+"|"+odData[i].id)
+						  this.$emit('getBackVal',arr);
+						}
+					}
+					if(!isHas){
+						var arr=[];
+						arr.push(val)
+						 this.$emit('getBackVal',arr);
+					}
+				}
+				this.nowData=data;
+			},
+			//下拉选中
+			selectCheckbox(e){
+				var val=e.target.value;
+				var str=val;
+				if(typeof(str)!="string"){
+					str="";
+					for(var i=0;i<val.length;i++){
+						var vt=val[i].split("|");
+						str+=i>0?","+vt[0]:vt[0];
+					}
+				}
+				else{
+					this.isShow = false;
+					str=str.split("|")[0];
+				}
+				this.$emit('getBackVal',val+"|"+this.tags)
+				this.theValue = str;
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+.yealuo-select{
+	max-width: 100%;
+	position: relative;
+	.yealuo-background{position: fixed;top:0;left:0;width: 750upx;height: 100%;}
+	.yealuo-con{display: flex;align-items: center;justify-content: center;
+		input{flex: 1;margin: 0 6upx;}
+	}
+	
+	.yealuo-select {
+		border: 1px solid #f3f3f4;
+		position: absolute;
+		z-index: 999;
+		background: #fff;
+		width: 100%;
+		.data{
+			max-height: 600upx;
+			padding: 10upx;
+			overflow: auto;
+			.select-item {width: 100%;color:#666;
+				.item-text{ width:100%; display:block;}
+				.active{font-weight: bold;}
+			}
+			.item-auto{overflow: auto;
+				.item-text{width: max-content;}
+			}
+			.item-hide .item-text{
+				overflow: hidden;
+				text-overflow:ellipsis;
+				white-space: nowrap;
+			}
+		}
+		.item-close {
+			padding: 20upx;
+			text-align: center;
+			font-size: 32upx;
+			border-top: 1px solid #f3f3f4;
+			color:#8F8F94;
+		}
+	}
+}
+</style>

+ 115 - 0
src/components/zzlb-mutiselect/zzlb-mutiselect.vue

@@ -0,0 +1,115 @@
+<template>
+  <view class="" style="width: 100%">
+    <view class="flex flex-wrap">
+      <view v-for="(item, index) in items" :key="index" class="cu-tag bg-cyan radius">
+        <slot name="default" v-bind:item="item">{{ item }}</slot>
+        <text class="ml15 line-blue cuIcon-close round bg-red" @click="onremove(index)"></text>
+      </view>
+      <view class="flex">
+        <uni-easyinput type="text" v-model="query" placeholder="输入..." prefixIcon="search" @input="onshuru" @click="onquery" @keyup="onquery" style="width: 60px;" />
+        <text
+          v-if="query"
+          class="cuIcon-close"
+          @click="
+            query = '';
+            onquery();
+          "
+        ></text>
+        <text v-if="visible" class="cu-btn sm" @click="onqueren">确认</text>
+      </view>
+    </view>
+    <view v-if="visible" class="fixed bg-white">
+      <!-- <view class="flex justify-between ">
+        <button class="cu-btn " @click="visible = 0">取消</button>
+        <button class="cu-btn " @click="onqueren">确认</button>
+      </view> -->
+      <picker-view class="picker-view" @change="(e) => (pvvalue = mlist[e.detail.value[0]])" indicator-style1="height:40px" :style1="{ height: (mlist.length > 6 ? 6 : mlist.length / 2) * 34 + 'px' }">
+        <picker-view-column>
+          <view class="item" v-for="(item, index) in mlist" :key="index">
+            <slot name="option" v-bind:item="item">{{ item }}</slot>
+          </view>
+        </picker-view-column>
+      </picker-view>
+    </view>
+  </view>
+</template>
+<script>
+export default {
+  props: {
+    url: { type: String, default: "" },
+    list: {
+      type: Array,
+      default: function () {
+        return [];
+      },
+    },
+    value: {
+      type: Array,
+      default: function () {
+        return [];
+      },
+    },
+  },
+  data() {
+    return {
+      visible: 0,
+      items: [],
+      query: "",
+      mlist: [],
+      pvvalue: [],
+    };
+  },
+  computed: {},
+  watch: {
+    value() {
+      this.items = this.value || [];
+    },
+  },
+  methods: {
+    onquery() {
+      console.log(this.query);
+      this.visible = 1;
+      this.pvvalue = this.mlist[0];
+
+      if (this.list && this.list.length > 0) {
+        const l = this.list.filter((f) => f.indexOf(this.query) > -1);
+        if (l.indexOf(this.pvvalue) === -1) {
+          this.pvvalue = l[0];
+        }
+        this.mlist = l;
+        return;
+      }
+      this.$http.get(this.url, { params: { title: this.query } }).then((r) => {
+        r.data.list;
+        this.mlist = r.data.list;
+        this.pvvalue = this.mlist[0];
+      });
+    },
+    onremove(index) {
+      this.items.splice(index, 1);
+      this.$emit("input", this.items);
+    },
+    onshuru() {
+      this.visible = 1;
+    },
+    onqueren() {
+      this.visible = 0;
+      if (this.pvvalue) this.items.push(this.pvvalue);
+      this.$emit("input", this.items);
+    },
+  },
+};
+</script>
+<style>
+.picker-view {
+  width: 100%;
+  height: 100px;
+  /* margin-top: 20rpx; */
+}
+.item {
+  /* height: 50px; */
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+}
+</style>

+ 53 - 0
src/config.js

@@ -0,0 +1,53 @@
+import manifest from './manifest.json'
+
+// 应用全局配置
+export default {
+  ip: "192.168.10.165:9300",
+
+  //#ifdef APP-PLUS || MP-WEIXIN
+  baseUrl: "https://gateWay.usky.cn/prod-api",
+  //#endif
+
+  //#ifdef H5
+  baseUrl: import.meta.env.MODE === "production" ? `https://${window.location.host}/prod-api` : `http://192.168.10.165:801/dev-api`,
+  //#endif
+
+  websiteUrl: "https://qhome.usky.cn",
+  // 应用信息
+  appInfo: {
+    // 应用id
+    appid: manifest.appid,
+    // 应用名称
+    name: manifest.name,
+    // 应用版本
+    version: uni.getSystemInfoSync().appWgtVersion || uni.getSystemInfoSync().appVersion,
+    // 应用logo
+    logo: "/static/logo.png",
+    // 官网邮箱
+    email: "admin@chinausky.com",
+    // 服务热线
+    hotline: "021-65376655",
+    // 官方网站
+    site_url: "http://www.usky.cn/",
+    // 版权
+    copyright: "Copyright © 2021- 2025 Usky. All Rights Reserved. 永天股份 版权所有",
+    // 备案编号
+    filingInfo: {
+      code: "ICP备案号:沪ICP备09005762号-15A",
+      href: "https://beian.miit.gov.cn/#/Integrated/index"
+    },
+    // 政策协议
+    agreements: [
+      {
+        title: "用户服务协议",
+        content: "暂无数据",
+        url: "http://file.usky.cn/uskycloud/user_protocol.html",
+      },
+      {
+        title: "隐私政策",
+        content: "暂无数据",
+        url: "http://file.usky.cn/uskycloud/privacy_protocol.html",
+      },
+    ],
+  },
+};

+ 63 - 0
src/main.js

@@ -0,0 +1,63 @@
+import { createSSRApp } from "vue";
+import App from "./App";
+import store from "./store"; // store
+import plugins from "./plugins"; // plugins
+// import "./permission"; // permission
+
+// 引入json导出组件
+import JsonExcel from "vue-json-excel";
+
+// 引入UI组件
+import uviewPlus from "@/uni_modules/uview-plus";
+import oaCalendar from "@/components/oa-calendar/uni-calendar";
+import oaTabbar from "@/components/oa-tabbar/index";
+import oaTimeLine from "@/components/oa-timeLine/index"
+import oaTimeLineItem from "@/components/oa-timeLine-item/index"
+import oaUpload from "@/components/oa-upload/index"
+import oaScroll from "@/components/oa-scroll/index"
+import oaTouch from "@/components/oa-touch/index"
+import oaDropdown from "@/components/oa-dropdown/index"
+import oaUpgrade from "@/components/oa-upgrade/index"
+import oaTransForm from "@/components/oa-transForm/index"
+import oaTtsAudio from "@/components/oa-ttsAudio/index"
+import oaWeather from "@/components/oa-weather/index"
+import oaSteps from "@/components/oa-steps/index"
+
+
+// import hideHead from "./utils/hideHead.js";
+
+export function createApp() {
+  const app = createSSRApp(App);
+  // app.mixin(hideHead);// 隐藏头部双重标题
+
+
+  //挂载全局组件
+  app.component('oa-calendar', oaCalendar)
+  app.component('oa-tabbar', oaTabbar)
+  app.component('oa-timeLine', oaTimeLine)
+  app.component('oa-timeLine-item', oaTimeLineItem)
+  app.component('oa-upload', oaUpload)
+  app.component('oa-scroll', oaScroll)
+  app.component('oa-touch', oaTouch)
+  app.component('oa-dropdown', oaDropdown)
+  app.component('oa-upgrade', oaUpgrade)
+  app.component('oa-transForm', oaTransForm)
+  app.component('oa-ttsAudio', oaTtsAudio)
+  app.component('oa-weather', oaWeather)
+  app.component('oa-steps', oaSteps)
+
+  // 挂载全局json导出
+  app.component("downloadExcel", JsonExcel);
+  // 添加全局变量
+  app.provide("$store", store);
+  app.config.globalProperties.$store = store;
+
+  app
+    .use(uviewPlus)
+    .use(store)
+    .use(plugins);
+
+  return {
+    app,
+  };
+}

+ 254 - 0
src/manifest.json

@@ -0,0 +1,254 @@
+{
+    "name": "智能门禁",
+    "appid": "__UNI__8D6E9FD",
+    "description": "智能门禁APP,是一款集成了现代信息技术和智能化管理功能的移动应用程序。",
+    "versionName": "1.0.1",
+    "versionCode": 2,
+    "transformPx": false,
+    "app-plus": {
+        "compatible": {
+            "ignoreVersion": true
+        },
+        "kernel": {
+            "ios": "WKWebview"
+        },
+        "usingComponents": true,
+        "nvueStyleCompiler": "uni-app",
+        "compilerVersion": 3,
+        "splashscreen": {
+            "alwaysShowBeforeRender": true,
+            "waiting": true,
+            "autoclose": true,
+            "delay": 0
+        },
+        "screenOrientation": [
+            "portrait-primary",
+            "portrait-secondary",
+            "landscape-primary",
+            "landscape-secondary"
+        ],
+        "modules": {
+            "VideoPlayer": {},
+            "iBeacon": {},
+            "Geolocation": {},
+            "Maps": {},
+            "Barcode": {},
+            "Fingerprint": {},
+            "Camera": {},
+            "LivePusher": {}
+        },
+        "distribute": {
+            "android": {
+                "permissions": [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.BIND_DREAM_SERVICE\"/>",
+                    "<uses-permission android:name=\"android.permission.BIND_NFC_SERVICE\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.INSTALL_PACKAGES\"/>",
+                    "<uses-permission android:name=\"android.permission.INTERNET\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.NFC\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>",
+                    "<uses-permission android:name=\"android.permission.USE_FINGERPRINT\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\"/>",
+                    "<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\"/>",
+                    "<uses-permission android:name=\"android.permission.BLUETOOTH\"/>",
+                    "<uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\"/>",
+                    "<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
+                    "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>"
+                ],
+                "minSdkVersion": 21,
+                "abiFilters": [
+                    "armeabi-v7a",
+                    "arm64-v8a"
+                ],
+                "targetSdkVersion": 30
+            },
+            "ios": {
+                "dSYMs": false,
+                "privacyDescription": {
+                    "NSPhotoLibraryUsageDescription": "该应用需要读取你的相册,用于上传头像",
+                    "NSPhotoLibraryAddUsageDescription": "该应用需要读取你的相册,用于拍照保存图片",
+                    "NSCameraUsageDescription": "该应用需要你的相机,用于你拍摄上传头像信息",
+                    "NSMicrophoneUsageDescription": "该应用需要使用你的麦克风,用于语音播放",
+                    "NSLocationWhenInUseUsageDescription": "该应用需要你的地理位置,用于天气、签到等功能",
+                    "NSLocationAlwaysUsageDescription": "该应用需要持续获取用户地理位置,用于天气、签到等功能",
+                    "NSLocationAlwaysAndWhenInUseUsageDescription": "该应用需要你的地理位置,用于天气、签到等功能",
+                    "NSCalendarsUsageDescription": "该应用需要获取你的日历,以便更好的体验",
+                    "NSContactsUsageDescription": "该应用需要读取你的通讯录,以便联系同事",
+                    "NSBluetoothPeripheralUsageDescription": "该应用需要你的蓝牙,以便读取相关蓝牙设备",
+                    "NFCReaderUsageDescription": "",
+                    "NSBluetoothAlwaysUsageDescription": "该应用需要你的蓝牙,以便读取相关蓝牙设备"
+                },
+                "UIBackgroundModes": ""
+            },
+            "sdkConfigs": {
+                "ad": {},
+                "maps": {
+                    "amap": {
+                        "appkey_ios": "fb35d03fbb17cbf7a8743a522da3c7fc",
+                        "appkey_android": "ffc71dfd4e576596027f8f45a1b8fb2f",
+                        "name": "amapBOujshtbA"
+                    }
+                },
+                "geolocation": {
+                    "system": {
+                        "__platform__": [
+                            "ios",
+                            "android"
+                        ]
+                    },
+                    "amap": {
+                        "__platform__": [
+                            "ios",
+                            "android"
+                        ],
+                        "appkey_ios": "fb35d03fbb17cbf7a8743a522da3c7fc",
+                        "appkey_android": "ffc71dfd4e576596027f8f45a1b8fb2f",
+                        "name": "amapBOujshtbA"
+                    },
+                    "tencent": {
+                        "__platform__": [
+                            "ios",
+                            "android"
+                        ],
+                        "apikey_ios": "EGOBZ-74ZET-ST7XS-VYICT-RBLHZ-KLFEX",
+                        "apikey_android": "EGOBZ-74ZET-ST7XS-VYICT-RBLHZ-KLFEX"
+                    }
+                },
+                "push": {
+                    "unipush": {
+                        "version": "2",
+                        "offline": true,
+                        "icons": {
+                            "small": {
+                                "hdpi": "unpackage/res/push/36x36.png",
+                                "ldpi": "unpackage/res/push/18x18.png",
+                                "mdpi": "unpackage/res/push/24x24.png",
+                                "xhdpi": "unpackage/res/push/48x48.png",
+                                "xxhdpi": "unpackage/res/push/72x72.png"
+                            }
+                        }
+                    }
+                }
+            },
+            "icons": {
+                "android": {
+                    "hdpi": "unpackage/res/icons/72x72.png",
+                    "xhdpi": "unpackage/res/icons/96x96.png",
+                    "xxhdpi": "unpackage/res/icons/144x144.png",
+                    "xxxhdpi": "unpackage/res/icons/192x192.png"
+                },
+                "ios": {
+                    "appstore": "unpackage/res/icons/1024x1024.png",
+                    "ipad": {
+                        "app": "unpackage/res/icons/76x76.png",
+                        "app@2x": "unpackage/res/icons/152x152.png",
+                        "notification": "unpackage/res/icons/20x20.png",
+                        "notification@2x": "unpackage/res/icons/40x40.png",
+                        "proapp@2x": "unpackage/res/icons/167x167.png",
+                        "settings": "unpackage/res/icons/29x29.png",
+                        "settings@2x": "unpackage/res/icons/58x58.png",
+                        "spotlight": "unpackage/res/icons/40x40.png",
+                        "spotlight@2x": "unpackage/res/icons/80x80.png"
+                    },
+                    "iphone": {
+                        "app@2x": "unpackage/res/icons/120x120.png",
+                        "app@3x": "unpackage/res/icons/180x180.png",
+                        "notification@2x": "unpackage/res/icons/40x40.png",
+                        "notification@3x": "unpackage/res/icons/60x60.png",
+                        "settings@2x": "unpackage/res/icons/58x58.png",
+                        "settings@3x": "unpackage/res/icons/87x87.png",
+                        "spotlight@2x": "unpackage/res/icons/80x80.png",
+                        "spotlight@3x": "unpackage/res/icons/120x120.png"
+                    }
+                }
+            },
+            "splashscreen": {
+                "androidStyle": "default",
+                "android": {
+                    "hdpi": "src/static/images/bg.png"
+                },
+                "iosStyle": "common"
+            }
+        },
+        "nativePlugins": {},
+        "safearea": {
+            "offset": "none"
+        }
+    },
+    "quickapp": {},
+    "mp-weixin": {
+        "appid": "",
+        "setting": {
+            "urlCheck": false,
+            "checkSiteMap": false
+        },
+        "usingComponents": true,
+        "navigateToMiniProgramAppIdList": [
+            "wxf5ad8734295d43f8"
+        ]
+    },
+    "mp-alipay": {
+        "usingComponents": true
+    },
+    "mp-baidu": {
+        "usingComponents": true
+    },
+    "mp-toutiao": {
+        "usingComponents": true
+    },
+    "uniStatistics": {
+        "enable": false
+    },
+    "h5": {
+        "publicPath": "./",
+        "title": "智能门禁",
+        "router": {
+            "mode": "hash"
+        },
+        "devServer": {
+            "https": false,
+            "proxy": {}
+        },
+        "sdkConfigs": {
+            "maps": {
+                "amap": {
+                    "key": "d4d73a7d572b6ff6028d5f67de62029a",
+                    "securityJsCode": "be916fcd16d0b33d228c49f0ff096b17",
+                    "serviceHost": ""
+                }
+            }
+        },
+        "optimization": {
+            "treeShaking": {
+                "enable": true
+            }
+        },
+        "template": "index.html"
+    },
+    "vueVersion": "3",
+    "locale": "zh-Hans"
+}

+ 64 - 0
src/pages.json

@@ -0,0 +1,64 @@
+{
+    "pages": [
+        {
+            "path": "pages/door/index",
+            "style": {
+                "navigationBarTitleText": "门禁识别",
+                "enablePullDownRefresh": false,
+                "navigationStyle": "custom",
+                "app-plus": {
+                    "bounce": "none",
+                    "titleNView": false
+                }
+            }
+        },
+        {
+            "path": "pages/door/setting/index",
+            "style": {
+                "navigationBarTitleText": "",
+                "enablePullDownRefresh": false,
+                "navigationStyle": "custom",
+                "app-plus": {
+                    "bounce": "none",
+                    "titleNView": false
+                }
+            }
+        },
+        {
+            "path": "pages/face/index",
+            "style": {
+                "navigationBarTitleText": "人脸识别",
+                "enablePullDownRefresh": false,
+                "navigationStyle": "custom",
+                "app-plus": {
+                    "bounce": "none",
+                    "titleNView": false
+                }
+            }
+        }
+    ],
+    "subPackages": [],
+    "globalStyle": {
+        "navigationBarTextStyle": "white",
+        "navigationBarTitleText": "uni-app",
+        "backgroundColor": "#F8F8F8"
+    },
+    "tabBar": {
+        "fontSize": "12px",
+        "color": "#a9a9a9",
+        "selectedColor": "#000000",
+        "borderStyle": "white",
+        "backgroundColor": "#ffffff",
+        "list": []
+    },
+    "condition": {
+        "current": 0,
+        "list": [
+            {
+                "name": "",
+                "path": "pages/door/index",
+                "query": ""
+            }
+        ]
+    }
+}

+ 252 - 0
src/pages/door/index.vue

@@ -0,0 +1,252 @@
+<template>
+  <web-view
+    v-show="!controlStore.modal.show"
+    ref="faceView"
+    id="faceView"
+    class="faceView"
+    src="/static/face/door.html"
+    bindmessage="receiveMessage"
+    :webview-styles="webviewStyles"
+    @message="onMessage"
+  >
+  </web-view>
+
+  <u-modal
+    :show="controlStore.modal.show" 
+    title=""
+    cancelText="退出应用"
+    confirmText="确认"
+    :zoom="false"
+    :showConfirmButton="true"
+    :showCancelButton="true"
+    :closeOnClickOverlay="true"
+    @confirm="controlStore.modalConfirm()"
+    @cancel="controlStore.modalCancel()"
+    @close="controlStore.modalClose()"
+  >
+    <view class="slot-content">
+      <u-subsection class="mb20" :list="controlStore.subsection.list" :current="controlStore.subsection.value" @change="controlStore.sectionChange"></u-subsection>
+
+      <view v-if="controlStore.subsection.value == 0">
+        <view class="mb10 required">服务器地址</view>
+        <view class="mb20">
+          <u-input v-model="controlStore.form.linkUrl" placeholder="服务器地址(必填)" border="bottom" style="padding: 6px 0px" />
+        </view>
+
+        <view class="mb10">服务器端口</view>
+        <view class="mb20">
+          <u-input v-model="controlStore.form.port" placeholder="服务器端口(非必填)" border="bottom" style="padding: 6px 0px" />
+        </view>
+      </view>
+
+      <view v-if="controlStore.subsection.value == 1">
+        <view class="mb10">绑定门禁</view>
+        <view>
+          <u-input
+            v-model="controlStore.form.doorList.name"
+            placeholder="门禁(必选)"
+            suffixIcon="arrow-right"
+            suffixIconStyle="color: #909399"
+            border="none"
+            disabledColor="transparent"
+            disabled
+            @click="controlStore.handlePicker('绑定门禁')"
+          />
+        </view>
+      </view>
+
+      <u-cell-group v-if="controlStore.subsection.value == 2">
+        <!-- <u-cell title="导航栏显示">
+          <template #value>
+            <u-switch v-model="setting.navBarNew" size="15" @change="navBarNewChange"></u-switch>
+          </template>
+        </u-cell> -->
+        <u-cell title="软件版本号" :value="version"></u-cell>
+        <!-- <u-cell title="设备型号" :value="getAndroidModle()"></u-cell>
+        <u-cell title="设备序列号" :value="getSerialno()"></u-cell> -->
+        <u-cell title="检查更新" @click="handleToUpgrade()">
+          <template #value> <view class="iconfont oaIcon-jianchagengxin menu-item-icon mr2" style="color: #2979ff"></view> </template>
+        </u-cell>
+        <!-- <u-cell title="设备重启" @click="rebootNow()">
+          <template #value> <u-icon name="reload" color="#2979ff" size="20"></u-icon> </template>
+        </u-cell> -->
+      </u-cell-group>
+    </view>
+  </u-modal>
+
+  <u-picker
+    :show="controlStore.picker.show"
+    :columns="controlStore.picker.list"
+    :title="'请选择' + controlStore.picker.title"
+    keyName="name"
+    visibleItemCount="6"
+    :defaultIndex="[controlStore.picker.defaultIndex]"
+    :closeOnClickOverlay="true"
+    @close="controlStore.picker.show = false"
+    @cancel="controlStore.picker.show = false"
+    @confirm="controlStore.pickerConfirm"
+  ></u-picker>
+
+  <!-- <u-popup :show="popup.show" @close="popupClose" @open="popupOpen" bgColor="#474747" :zIndex="10074">
+    <oaPassBody :show="popup.show"></oaPassBody>
+  </u-popup> -->
+</template>
+<script setup>
+/*----------------------------------依赖引入-----------------------------------*/
+import config from "@/config";
+import { onLoad, onShow, onReady, onHide, onLaunch, onUnload, onNavigationBarButtonTap, onPageScroll } from "@dcloudio/uni-app";
+import { ref, reactive, computed, getCurrentInstance, toRefs, inject, nextTick, watch } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
+import { doorApi } from "@/api/business/door.js";
+/*----------------------------------组件引入-----------------------------------*/
+import oaPassBody from "@/components/oa-passBody/index";
+/*----------------------------------store引入-----------------------------------*/
+import { controlStores } from "@/store/modules/index";
+/*----------------------------------公共方法引入-----------------------------------*/
+const { proxy } = getCurrentInstance();
+const controlStore = controlStores();
+/*----------------------------------公共变量-----------------------------------*/
+const state = reactive({
+  version: computed(() => {
+    return config.appInfo.version;
+  }),
+  webviewStyles: {
+    width: "100%",
+    height: "100%",
+  },
+  inter: {
+    doorDom: null,
+  },
+  popup: {
+    show: false,
+  },
+});
+const { version, webviewStyles, popup } = toRefs(state);
+
+// 初始化
+function init() {
+  controlStore.pageFunction = ["门禁"];
+  controlStore.initCamera();
+  controlStore.initNfc();
+  controlStore.initData();
+  controlStore.initHandle();
+
+  if (!state.inter.doorDom) {
+    getDoorList();
+    state.inter.doorDom = setInterval(() => {
+      getDoorList();
+    }, 1000 * 3);
+  }
+}
+
+function popupOpen() {}
+
+function popupClose() {}
+
+function getDoorList() {
+  if (controlStore.form.domain) {
+    doorApi()
+      .Select({
+        current: 1, //页数
+        size: 2000, //条数
+        domain: controlStore.form.domain, //域名
+        deviceName: controlStore.form.doorList.name,
+      })
+      .then((requset) => {
+        if (requset.data.records.length > 0) {
+          controlStore.handleChildren({
+            funcName: "初始化数据",
+            data: JSON.stringify(requset.data.records[0]),
+          });
+        }
+      });
+  }
+}
+
+/**
+ * @接收子页面传过来的值
+ */
+function onMessage(e) {
+  controlStore.analysisData(e.detail.data[0]);
+}
+// #ifdef H5
+window.onmessage = function (event) {
+  controlStore.analysisData(event.data);
+};
+// #endif
+
+/**
+ * @检查更新
+ */
+function handleToUpgrade() {
+  proxy.$settingStore.handleToUpgrade({
+    success: (res) => {
+      proxy.$refs["oaUpgradeRef"].openUpgrade({
+        modalArray: res.data,
+      });
+    },
+  });
+}
+ 
+onLoad((options) => {
+  setTimeout(() => {
+    init();
+  }, 500);
+
+  //#ifdef APP-PLUS
+  // const myActivityClass = plus.android.importClass("com.example.serialPort.UniSerialPort"); // 替换为你的包名和Activity名
+  // const myActivity = new myActivityClass(); // 创建插件类的实例
+
+  // console.log(myActivity);
+
+  // myActivity.test();
+
+  // myActivity.open((res) => {
+  //   console.log("open", res);
+  // });
+
+  // myActivity.onMessage((res) => {
+  //   console.log(res);
+  // });
+
+  // myActivity.getAllDeviceList((res) => {
+  //   console.log("getAllDeviceList", res);
+  // });
+
+  // console.log(myActivity.isOpen());
+  //#endif
+});
+
+onShow(() => {});
+
+onUnload(() => {
+  clearInterval(state.inter.doorDom); //销毁之前定时器
+});
+</script>
+<style>
+.faceView {
+  width: 100% !important;
+  height: 100% !important;
+}
+
+iframe {
+  width: 100% !important;
+  height: 100% !important;
+  border-width: 0;
+}
+</style>
+<style lang="scss" scoped>
+:deep() {
+  .u-modal {
+    width: 20rem !important;
+
+    &__title {
+      font-size: 18px !important;
+    }
+    .slot-content {
+      font-size: 16px;
+      width: 100%;
+    }
+  }
+}
+</style>

+ 175 - 0
src/pages/door/setting.vue

@@ -0,0 +1,175 @@
+<template>
+  <oa-scroll
+    customClass="doorSetting-container scroll-height"
+    :customStyle="{
+      //#ifdef APP-PLUS || MP-WEIXIN
+      height: `calc(100vh - (0px))`,
+      //#endif
+      //#ifdef H5
+      height: `calc(100vh - (0px))`,
+      //#endif
+    }"
+    :refresherLoad="false"
+    :refresherEnabled="false"
+    :refresherDefaultStyle="'none'"
+    :refresherBackground="'#f5f6f7'"
+    :data-theme="'theme-' + proxy.$settingStore.themeColor.name"
+  >
+    <template #default>
+      <text class="iconfont oaIcon-exit" @click="handleExit()"></text>
+
+      <view class="Grid">
+        <view class="Grid-item">
+          <image class="Grid-item-image" src="@/static/face/img/serveSetting.png"></image>
+          <view class="Grid-item-lable">服务器配置</view>
+        </view>
+        <view class="Grid-item">
+          <image class="Grid-item-image" src="@/static/face/img/otherSetting.png"></image>
+          <view class="Grid-item-lable">其它配置</view>
+        </view>
+        <view class="Grid-item">
+          <image class="Grid-item-image" src="@/static/face/img/sysetmSetting.png"></image>
+          <view class="Grid-item-lable">系统配置</view>
+        </view>
+        <view class="Grid-item"></view>
+      </view>
+
+      <u-subsection class="mb20" :list="controlStore.subsection.list" :current="controlStore.subsection.value" @change="controlStore.sectionChange"></u-subsection>
+
+      <view v-if="controlStore.subsection.value == 0">
+        <view class="mb10 required">服务器地址</view>
+        <view class="mb20">
+          <u-input v-model="controlStore.form.linkUrl" placeholder="服务器地址(必填)" border="bottom" style="padding: 6px 0px" />
+        </view>
+
+        <view class="mb10">服务器端口</view>
+        <view class="mb20">
+          <u-input v-model="controlStore.form.port" placeholder="服务器端口(非必填)" border="bottom" style="padding: 6px 0px" />
+        </view>
+      </view>
+
+      <view v-if="controlStore.subsection.value == 1">
+        <view class="mb10">绑定门禁</view>
+        <view>
+          <u-input
+            v-model="controlStore.form.doorList.name"
+            placeholder="门禁(必选)"
+            suffixIcon="arrow-right"
+            suffixIconStyle="color: #909399"
+            border="none"
+            disabledColor="transparent"
+            disabled
+            @click="controlStore.handlePicker('绑定门禁')"
+          />
+        </view>
+      </view>
+
+      <u-cell-group v-if="controlStore.subsection.value == 2">
+        <!-- <u-cell title="导航栏显示">
+          <template #value>
+            <u-switch v-model="setting.navBarNew" size="15" @change="navBarNewChange"></u-switch>
+          </template>
+        </u-cell> -->
+        <u-cell title="软件版本号" :value="version"></u-cell>
+        <!-- <u-cell title="设备型号" :value="getAndroidModle()"></u-cell>
+        <u-cell title="设备序列号" :value="getSerialno()"></u-cell> -->
+        <u-cell title="检查更新" @click="handleToUpgrade()">
+          <template #value> <view class="iconfont oaIcon-jianchagengxin menu-item-icon mr2" style="color: #2979ff"></view> </template>
+        </u-cell>
+        <!-- <u-cell title="设备重启" @click="rebootNow()">
+          <template #value> <u-icon name="reload" color="#2979ff" size="20"></u-icon> </template>
+        </u-cell> -->
+      </u-cell-group>
+    </template>
+  </oa-scroll>
+</template>
+<script setup>
+/*----------------------------------依赖引入-----------------------------------*/
+import config from "@/config";
+import { onLoad, onShow, onReady, onHide, onLaunch, onUnload, onNavigationBarButtonTap, onPageScroll } from "@dcloudio/uni-app";
+import { ref, reactive, computed, getCurrentInstance, toRefs, inject, nextTick, watch } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
+import { doorApi } from "@/api/business/door.js";
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+import { controlStores } from "@/store/modules/index";
+/*----------------------------------公共方法引入-----------------------------------*/
+const { proxy } = getCurrentInstance();
+const controlStore = controlStores();
+/*----------------------------------公共变量-----------------------------------*/
+const state = reactive({});
+const {} = toRefs(state);
+
+function handleExit() {
+  proxy.$tab.navigateBack(1);
+}
+</script>
+<style lang="scss">
+.doorSetting-container {
+  background: url(@/static/face/img/face_bg.png) no-repeat;
+  background-size: 100% 100%;
+  color: #ffffff;
+  padding: 16px;
+
+  .iconfont {
+    display: block;
+    margin-bottom: 16px;
+  }
+
+  .Grid {
+    display: flex;
+    justify-content: center; /* 左对齐 */
+    flex-wrap: wrap; /* 换行 */
+
+    &-item {
+      flex: 0 0 25%;
+
+      &-image {
+        display: block;
+        width: 40px;
+        height: 40px;
+        margin: auto;
+      }
+
+      &-lable {
+        text-align: center;
+        margin: auto;
+        margin-top: 16px;
+      }
+    }
+  }
+}
+
+@media (min-width: 500px) {
+  .doorSetting-container {
+    font-size: 0.5rem !important;
+    padding: 1rem !important;
+
+    .iconfont {
+      font-size: 1.5rem !important;
+      margin-bottom: 1.5rem !important;
+    }
+
+    .Grid {
+      display: flex;
+      justify-content: center; /* 左对齐 */
+      flex-wrap: wrap; /* 换行 */
+
+      &-item {
+        flex: 0 0 25%;
+        // justify-content: center;
+        // margin: auto;
+
+        &-image {
+          width: 2.5rem;
+          height: 2.5rem;
+        }
+
+        &-lable {
+          margin-top: 0.5rem !important;
+        }
+      }
+    }
+  }
+}
+</style>

+ 188 - 0
src/pages/door/setting/index.vue

@@ -0,0 +1,188 @@
+<template>
+  <oa-scroll
+    customClass="doorSetting-container scroll-height"
+    :customStyle="{
+      //#ifdef APP-PLUS || MP-WEIXIN
+      height: `calc(100vh - (0px))`,
+      //#endif
+      //#ifdef H5
+      height: `calc(100vh - (0px))`,
+      //#endif
+    }"
+    :refresherLoad="false"
+    :refresherEnabled="false"
+    :refresherDefaultStyle="'none'"
+    :refresherBackground="'#f5f6f7'"
+    :data-theme="'theme-' + proxy.$settingStore.themeColor.name"
+  >
+    <template #default>
+      <text class="iconfont oaIcon-exit" @click="handleExit()"></text>
+
+      <view class="Grid">
+        <view class="Grid-item" @click="handle('serve')">
+          <image class="Grid-item-image" src="@/static/face/img/serveSetting.png"></image>
+          <view class="Grid-item-lable">服务器配置</view>
+        </view>
+        <view class="Grid-item" @click="handle('other')">
+          <image class="Grid-item-image" src="@/static/face/img/otherSetting.png"></image>
+          <view class="Grid-item-lable">门禁配置</view>
+        </view>
+        <view class="Grid-item" @click="handle('sysetm')">
+          <image class="Grid-item-image" src="@/static/face/img/sysetmSetting.png"></image>
+          <view class="Grid-item-lable">系统配置</view>
+        </view>
+        <view class="Grid-item"></view>
+      </view>
+
+      <u-subsection class="mb20" :list="controlStore.subsection.list" :current="controlStore.subsection.value" @change="controlStore.sectionChange"></u-subsection>
+
+      <view v-if="controlStore.subsection.value == 0">
+        <view class="mb10 required">服务器地址</view>
+        <view class="mb20">
+          <u-input v-model="controlStore.form.linkUrl" placeholder="服务器地址(必填)" border="bottom" style="padding: 6px 0px" />
+        </view>
+
+        <view class="mb10">服务器端口</view>
+        <view class="mb20">
+          <u-input v-model="controlStore.form.port" placeholder="服务器端口(非必填)" border="bottom" style="padding: 6px 0px" />
+        </view>
+      </view>
+
+      <view v-if="controlStore.subsection.value == 1">
+        <view class="mb10">绑定门禁</view>
+        <view>
+          <u-input
+            v-model="controlStore.form.doorList.name"
+            placeholder="门禁(必选)"
+            suffixIcon="arrow-right"
+            suffixIconStyle="color: #909399"
+            border="none"
+            disabledColor="transparent"
+            disabled
+            @click="controlStore.handlePicker('绑定门禁')"
+          />
+        </view>
+      </view>
+
+      <u-cell-group v-if="controlStore.subsection.value == 2">
+        <!-- <u-cell title="导航栏显示">
+            <template #value>
+              <u-switch v-model="setting.navBarNew" size="15" @change="navBarNewChange"></u-switch>
+            </template>
+          </u-cell> -->
+        <u-cell title="软件版本号" :value="version"></u-cell>
+        <!-- <u-cell title="设备型号" :value="getAndroidModle()"></u-cell>
+          <u-cell title="设备序列号" :value="getSerialno()"></u-cell> -->
+        <u-cell title="检查更新" @click="handleToUpgrade()">
+          <template #value> <view class="iconfont oaIcon-jianchagengxin menu-item-icon mr2" style="color: #2979ff"></view> </template>
+        </u-cell>
+        <!-- <u-cell title="设备重启" @click="rebootNow()">
+            <template #value> <u-icon name="reload" color="#2979ff" size="20"></u-icon> </template>
+          </u-cell> -->
+      </u-cell-group>
+    </template>
+  </oa-scroll>
+</template>
+<script setup>
+/*----------------------------------依赖引入-----------------------------------*/
+import config from "@/config";
+import { onLoad, onShow, onReady, onHide, onLaunch, onUnload, onNavigationBarButtonTap, onPageScroll } from "@dcloudio/uni-app";
+import { ref, reactive, computed, getCurrentInstance, toRefs, inject, nextTick, watch } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
+import { doorApi } from "@/api/business/door.js";
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+import { controlStores } from "@/store/modules/index";
+/*----------------------------------公共方法引入-----------------------------------*/
+const { proxy } = getCurrentInstance();
+const controlStore = controlStores();
+/*----------------------------------公共变量-----------------------------------*/
+const state = reactive({});
+const {} = toRefs(state);
+
+function handle(type) {
+  if (type == "serve") {
+  } else if (type == "other") {
+    if (!controlStore.form.linkUrl) {
+      proxy.$modal.msg("请先配置服务器信息!");
+      return;
+    }
+
+    
+  } else if (type == "sysetm") {
+  }
+}
+
+function handleExit() {
+  proxy.$tab.navigateBack(1);
+}
+</script>
+<style lang="scss">
+.doorSetting-container {
+  background: url(@/static/face/img/face_bg.png) no-repeat;
+  background-size: 100% 100%;
+  color: #ffffff;
+  padding: 16px;
+
+  .iconfont {
+    display: block;
+    margin-bottom: 16px;
+  }
+
+  .Grid {
+    display: flex;
+    justify-content: center; /* 左对齐 */
+    flex-wrap: wrap; /* 换行 */
+
+    &-item {
+      flex: 0 0 25%;
+
+      &-image {
+        display: block;
+        width: 40px;
+        height: 40px;
+        margin: auto;
+      }
+
+      &-lable {
+        text-align: center;
+        margin: auto;
+        margin-top: 16px;
+      }
+    }
+  }
+}
+
+@media (min-width: 500px) {
+  .doorSetting-container {
+    font-size: 0.5rem !important;
+    padding: 1rem !important;
+
+    .iconfont {
+      font-size: 1.5rem !important;
+      margin-bottom: 1.5rem !important;
+    }
+
+    .Grid {
+      display: flex;
+      justify-content: center; /* 左对齐 */
+      flex-wrap: wrap; /* 换行 */
+
+      &-item {
+        flex: 0 0 25%;
+        // justify-content: center;
+        // margin: auto;
+
+        &-image {
+          width: 2.5rem;
+          height: 2.5rem;
+        }
+
+        &-lable {
+          margin-top: 0.5rem !important;
+        }
+      }
+    }
+  }
+}
+</style>

+ 122 - 0
src/pages/door/setting/serve/index.vue

@@ -0,0 +1,122 @@
+<template>
+  <oa-scroll
+    customClass="doorSettingServe-container scroll-height"
+    :customStyle="{
+      //#ifdef APP-PLUS || MP-WEIXIN
+      height: `calc(100vh - (0px))`,
+      //#endif
+      //#ifdef H5
+      height: `calc(100vh - (0px))`,
+      //#endif
+    }"
+    :refresherLoad="false"
+    :refresherEnabled="false"
+    :refresherDefaultStyle="'none'"
+    :refresherBackground="'#f5f6f7'"
+    :data-theme="'theme-' + proxy.$settingStore.themeColor.name"
+  >
+    <template #default>
+      <text class="iconfont oaIcon-exit" @click="handleExit()"></text>
+
+      <view class="mb10 required">服务器地址</view>
+      <view class="mb20">
+        <u-input v-model="controlStore.form.linkUrl" placeholder="服务器地址(必填)" border="bottom" style="padding: 6px 0px" />
+      </view>
+
+      <view class="mb10">服务器端口</view>
+      <view class="mb20">
+        <u-input v-model="controlStore.form.port" placeholder="服务器端口(非必填)" border="bottom" style="padding: 6px 0px" />
+      </view>
+    </template>
+  </oa-scroll>
+</template>
+<script setup>
+/*----------------------------------依赖引入-----------------------------------*/
+import config from "@/config";
+import { onLoad, onShow, onReady, onHide, onLaunch, onUnload, onNavigationBarButtonTap, onPageScroll } from "@dcloudio/uni-app";
+import { ref, reactive, computed, getCurrentInstance, toRefs, inject, nextTick, watch } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
+import { doorApi } from "@/api/business/door.js";
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+import { controlStores } from "@/store/modules/index";
+/*----------------------------------公共方法引入-----------------------------------*/
+const { proxy } = getCurrentInstance();
+const controlStore = controlStores();
+/*----------------------------------公共变量-----------------------------------*/
+const state = reactive({});
+const {} = toRefs(state);
+
+function handleExit() {
+  proxy.$tab.navigateBack(1);
+}
+</script>
+<style lang="scss">
+.doorSetting-container {
+  background: url(@/static/face/img/face_bg.png) no-repeat;
+  background-size: 100% 100%;
+  color: #ffffff;
+  padding: 16px;
+
+  .iconfont {
+    display: block;
+    margin-bottom: 16px;
+  }
+
+  .Grid {
+    display: flex;
+    justify-content: center; /* 左对齐 */
+    flex-wrap: wrap; /* 换行 */
+
+    &-item {
+      flex: 0 0 25%;
+
+      &-image {
+        display: block;
+        width: 40px;
+        height: 40px;
+        margin: auto;
+      }
+
+      &-lable {
+        text-align: center;
+        margin: auto;
+        margin-top: 16px;
+      }
+    }
+  }
+}
+
+@media (min-width: 500px) {
+  .doorSetting-container {
+    font-size: 0.5rem !important;
+    padding: 1rem !important;
+
+    .iconfont {
+      font-size: 1.5rem !important;
+      margin-bottom: 1.5rem !important;
+    }
+
+    .Grid {
+      display: flex;
+      justify-content: center; /* 左对齐 */
+      flex-wrap: wrap; /* 换行 */
+
+      &-item {
+        flex: 0 0 25%;
+        // justify-content: center;
+        // margin: auto;
+
+        &-image {
+          width: 2.5rem;
+          height: 2.5rem;
+        }
+
+        &-lable {
+          margin-top: 0.5rem !important;
+        }
+      }
+    }
+  }
+}
+</style>

+ 426 - 0
src/pages/face/index.vue

@@ -0,0 +1,426 @@
+<template>
+  <web-view
+    v-show="!controlStore.modal.show"
+    ref="faceView"
+    id="faceView"
+    class="faceView"
+    src="/static/face/meeting.html"
+    bindmessage="receiveMessage"
+    :webview-styles="webviewStyles"
+    @message="onMessage"
+  >
+  </web-view>
+
+  <u-modal
+    :show="controlStore.modal.show"
+    title=""
+    :cancelText="'退出应用'"
+    :zoom="false"
+    :showConfirmButton="true"
+    :showCancelButton="true"
+    :closeOnClickOverlay="true"
+    @confirm="controlStore.modalConfirm(), getMeetingRoomReservationList()"
+    @cancel="controlStore.modalCancel()"
+    @close="controlStore.modalClose()"
+  >
+    <view class="slot-content">
+      <u-subsection class="mb20" :list="controlStore.subsection.list" :current="controlStore.subsection.value" @change="controlStore.sectionChange"></u-subsection>
+
+      <view v-if="controlStore.subsection.value == 0">
+        <view class="mb10 required">服务器地址</view>
+        <view class="mb20">
+          <u-input v-model="controlStore.form.linkUrl" placeholder="服务器地址(必填)" border="bottom" style="padding: 6px 0px" />
+        </view>
+
+        <view class="mb10">服务器端口</view>
+        <view class="mb20">
+          <u-input v-model="controlStore.form.port" placeholder="服务器端口(非必填)" border="bottom" style="padding: 6px 0px" />
+        </view>
+      </view>
+
+      <view v-if="controlStore.subsection.value == 1">
+        <view class="mb10 required">绑定会议室</view>
+        <view class="mb20">
+          <u-input
+            v-model="controlStore.form.meetingName"
+            placeholder="会议室(必选)"
+            suffixIcon="arrow-right"
+            suffixIconStyle="color: #909399"
+            border="bottom"
+            style="padding: 6px 0px"
+            disabledColor="transparent"
+            disabled
+            @click="controlStore.handlePicker('绑定会议室')"
+          />
+        </view>
+
+        <view class="mb10 required">绑定门禁</view>
+        <view>
+          <u-input
+            v-model="controlStore.form.doorList.name"
+            placeholder="门禁(必选)"
+            suffixIcon="arrow-right"
+            suffixIconStyle="color: #909399"
+            border="none"
+            disabledColor="transparent"
+            disabled
+            @click="controlStore.handlePicker('绑定门禁')"
+          />
+        </view>
+      </view>
+
+      <u-cell-group v-if="controlStore.subsection.value == 2">
+        <u-cell title="导航栏显示">
+          <template #value>
+            <u-switch v-model="setting.navBarNew" size="15" @change="navBarNewChange"></u-switch>
+          </template>
+        </u-cell>
+        <u-cell title="软件版本号" :value="version"></u-cell>
+        <u-cell title="设备型号" :value="getAndroidModle()"></u-cell>
+        <u-cell title="设备序列号" :value="getSerialno()"></u-cell>
+        <u-cell title="检查更新" @click="handleToUpgrade()">
+          <template #value> <view class="iconfont oaIcon-jianchagengxin menu-item-icon mr2" style="color: #2979ff"></view> </template>
+        </u-cell>
+        <u-cell title="设备重启" @click="rebootNow()">
+          <template #value> <u-icon name="reload" color="#2979ff" size="20"></u-icon> </template>
+        </u-cell>
+      </u-cell-group>
+    </view>
+  </u-modal>
+
+  <u-picker
+    :show="controlStore.picker.show"
+    :columns="controlStore.picker.list"
+    :title="'请选择' + controlStore.picker.title"
+    keyName="name"
+    visibleItemCount="6"
+    :defaultIndex="[controlStore.picker.defaultIndex]"
+    :closeOnClickOverlay="true"
+    @close="controlStore.picker.show = false"
+    @cancel="controlStore.picker.show = false"
+    @confirm="controlStore.pickerConfirm"
+  ></u-picker>
+
+  <oa-upgrade ref="oaUpgradeRef" :themesColor="proxy.$settingStore.themeColor.color" />
+</template>
+<script setup>
+/*----------------------------------依赖引入-----------------------------------*/
+import config from "@/config";
+import { onLoad, onShow, onReady, onHide, onLaunch, onUnload, onNavigationBarButtonTap, onPageScroll } from "@dcloudio/uni-app";
+import { ref, reactive, computed, getCurrentInstance, toRefs, inject, nextTick, watch } from "vue";
+/*----------------------------------接口引入-----------------------------------*/
+import { meetingApi, signOnOut } from "@/api/business/meeting.js";
+/*----------------------------------组件引入-----------------------------------*/
+/*----------------------------------store引入-----------------------------------*/
+import { controlStores } from "@/store/modules/index";
+/*----------------------------------公共方法引入-----------------------------------*/
+/*----------------------------------公共变量-----------------------------------*/
+const { proxy } = getCurrentInstance();
+const controlStore = controlStores();
+/*----------------------------------公共变量-----------------------------------*/
+const state = reactive({
+  version: computed(() => {
+    return config.appInfo.version;
+  }),
+  webviewStyles: {
+    width: "100%",
+    height: "100%",
+  },
+  inter: {
+    meeting: null,
+    rebootNow: null,
+  },
+
+  setting: {
+    navBarNew: true,
+    serialno: "", //设备序列号
+  },
+});
+const { version, webviewStyles, inter, setting } = toRefs(state);
+
+// 初始化
+function init() {
+  controlStore.pageFunction = ["门禁", "会议"];
+  controlStore.initCamera();
+  // controlStore.initNfc();
+  controlStore.initData();
+  if (!inter.meeting) {
+    getMeetingRoomReservationList();
+    inter.meeting = setInterval(() => {
+      getMeetingRoomReservationList();
+    }, 1000 * 3);
+  }
+
+  timingRebootNow(); //调用定时重启
+}
+
+/**
+ * @会议室详情列表
+ */
+function getMeetingRoomReservationList() {
+  controlStore.meetingTimeList = [];
+  controlStore.meetingReservaList.thisVenueData = [];
+  controlStore.meetingReservaList.thisVenueTime = {};
+  controlStore.meetingReservaList.nextSceneData = [];
+  controlStore.meetingReservaList.nextSceneTime = {};
+
+  for (let i = 0; i <= 23.5; i += 0.5) {
+    var time = "";
+    if (i % 1 === 0.5) {
+      if (i < 10) {
+        time = "0" + (i - 0.5) + ":30";
+      } else {
+        time = i - 0.5 + ":30";
+      }
+    } else {
+      if (i < 10) {
+        time = "0" + i + ":00";
+      } else {
+        time = i + ":00";
+      }
+    }
+
+    controlStore.meetingTimeList.push({
+      startTime: time,
+      endTime: time,
+      isEnd: 0,
+      isHave: 0,
+      isReservation: 0,
+    });
+  }
+
+  meetingApi()
+    .GetMeetingRoomReservationList({
+      domain: controlStore.form.domain,
+      meetingRoomId: controlStore.form.meetingId,
+      date: proxy.$dayjs().format("YYYY-MM-DD") + " 00:00:00",
+    })
+    .then((requset) => {
+      if (requset.data.length > 0) {
+        controlStore.meetingReservaList.dataAll = requset.data[0];
+        controlStore.meetingReservaList.dataAll.dmMeetingList.forEach((e, index) => {
+          //判断开始时间和结束时间是否包含当前时间
+          if (proxy.$dayjs().isBetween(e.startDate, e.endDate, null, "[]")) {
+            controlStore.meetingReservaList.thisVenueData.push(e);
+            controlStore.meetingReservaList.thisVenueTime = proxy.$time.timeRestructuring(controlStore.meetingReservaList.dataAll.meetingRoomUsage[index]);
+          }
+          //判断当前时间是否相同或在其之前
+          if (proxy.$dayjs().isSameOrBefore(e.startDate) && controlStore.meetingReservaList.nextSceneData.length < 1) {
+            controlStore.meetingReservaList.nextSceneData.push(e);
+            controlStore.meetingReservaList.nextSceneTime = proxy.$time.timeRestructuring(controlStore.meetingReservaList.dataAll.meetingRoomUsage[index]);
+          }
+        });
+
+        controlStore.meetingReservaList.timeList = showTimeSegments(controlStore.meetingTimeList);
+        controlStore.handleChildren({
+          funcName: "初始化数据",
+          data: JSON.stringify(controlStore.meetingReservaList),
+        });
+      } else {
+        controlStore.meetingReservaList.timeList = controlStore.meetingTimeList;
+        controlStore.handleChildren({
+          funcName: "初始化数据",
+          data: JSON.stringify(controlStore.meetingReservaList),
+        });
+      }
+    })
+    .catch((err) => {});
+}
+
+// 显示时间段的函数
+function showTimeSegments(times) {
+  const timesXleList = JSON.parse(JSON.stringify(times));
+
+  for (var i = 0; i < timesXleList.length; i++) {
+    const timeValue = new Date(`${proxy.$dayjs().format("YYYY-MM-DD")}T${timesXleList[i].startTime}`);
+
+    controlStore.meetingReservaList.dataAll.meetingRoomUsage.forEach((item) => {
+      const timeList = proxy.$time.timeRestructuring(item);
+      const startValue = new Date(`${proxy.$dayjs().format("YYYY-MM-DD")}T${timeList.startTime}`);
+      const endValue = new Date(`${proxy.$dayjs().format("YYYY-MM-DD")}T${timeList.endTime}`);
+
+      if (timeValue.getTime() >= startValue.getTime() && timeValue.getTime() < endValue.getTime()) {
+        timesXleList.splice(i--, 1);
+      }
+    });
+  }
+
+  controlStore.meetingReservaList.dataAll.meetingRoomUsage.forEach((item) => {
+    const timeList = proxy.$time.timeRestructuring(item);
+    const startValue = new Date(`${proxy.$dayjs().format("YYYY-MM-DD")}T${timeList.startTime}`);
+    const endValue = new Date(`${proxy.$dayjs().format("YYYY-MM-DD")}T${timeList.endTime}`);
+
+    if (proxy.$dayjs().isBetween(startValue, endValue, null, "[]")) {
+      timesXleList.push({
+        ...timeList,
+        isEnd: 0,
+        isHave: 1,
+        isReservation: 0,
+      });
+    } else {
+      if (proxy.$dayjs().isSameOrAfter(startValue)) {
+        timesXleList.push({
+          ...timeList,
+          isEnd: 1,
+          isHave: 0,
+          isReservation: 0,
+        });
+      }
+      if (proxy.$dayjs().isSameOrBefore(startValue)) {
+        timesXleList.push({
+          ...timeList,
+          isEnd: 0,
+          isHave: 0,
+          isReservation: 1,
+        });
+      }
+    }
+  });
+
+  var newTimesXleList = proxy.$common
+    .uniq(timesXleList, "startTime")
+    .sort((a, b) => new Date(`${proxy.$dayjs().format("YYYY-MM-DD")}T${a.startTime}`) - new Date(`${proxy.$dayjs().format("YYYY-MM-DD")}T${b.startTime}`));
+  return newTimesXleList;
+}
+
+/**
+ * @接收子页面传过来的值
+ */
+function onMessage(e) {
+  controlStore.analysisData(e.detail.data[0]);
+}
+// #ifdef H5
+window.onmessage = function (event) {
+  controlStore.analysisData(event.data);
+};
+// #endif
+
+function handleButton(value) {
+  //#ifdef APP-PLUS
+  const myActivityClass = plus.android.importClass("com.example.yxDevice.test"); // 替换为你的包名和Activity名
+  const myActivity = new myActivityClass(); // 创建插件类的实例
+  myActivity.setLed(value);
+  //#endif
+}
+
+// 获取设备序列号
+function getAndroidModle() {
+  //#ifdef APP-PLUS
+  const myActivityClass = plus.android.importClass("com.example.yxDevice.test"); // 替换为你的包名和Activity名
+  const myActivity = new myActivityClass(); // 创建插件类的实例
+  return myActivity.getAndroidModle();
+  //#endif
+}
+
+// 获取设备序列号
+function getSerialno() {
+  //#ifdef APP-PLUS
+  const myActivityClass = plus.android.importClass("com.example.yxDevice.test"); // 替换为你的包名和Activity名
+  const myActivity = new myActivityClass(); // 创建插件类的实例
+  return myActivity.getSerialno();
+  //#endif
+}
+
+// 隐藏导航栏
+function navBarNewChange(value) {
+  //#ifdef APP-PLUS
+  const myActivityClass = plus.android.importClass("com.example.yxDevice.test"); // 替换为你的包名和Activity名
+  const myActivity = new myActivityClass(); // 创建插件类的实例
+  myActivity.setNavBarNew(value);
+  //#endif
+}
+
+// 设备重启
+function rebootNow() {
+  //#ifdef APP-PLUS
+  const myActivityClass = plus.android.importClass("com.example.yxDevice.test"); // 替换为你的包名和Activity名
+  const myActivity = new myActivityClass(); // 创建插件类的实例
+  myActivity.rebootNow();
+  //#endif
+}
+
+// 定时重启
+function timingRebootNow() {
+  if (!inter.rebootNow) {
+    checkMorningSix();
+    inter.rebootNow = setInterval(() => {
+      checkMorningSix();
+    }, 1000);
+  }
+
+  // 判断是否是早上6点钟
+  function checkMorningSix() {
+    const now = new Date();
+    // 设置时间为早上6点,忽略分钟和秒
+    const targetTime = new Date();
+    targetTime.setHours(6, 0, 0, 0);
+
+    // 比较当前时间是否等于早上6点
+    if (now.getTime() === targetTime.getTime()) {
+      rebootNow();
+    }
+  }
+}
+
+/**
+ * @检查更新
+ */
+function handleToUpgrade() {
+  proxy.$settingStore.handleToUpgrade({
+    success: (res) => {
+      proxy.$refs["oaUpgradeRef"].openUpgrade({
+        modalArray: res.data,
+      });
+    },
+  });
+}
+
+onLoad((options) => {
+  setTimeout(() => {
+    init();
+  }, 500);
+});
+
+onShow(() => {});
+
+onUnload(() => {
+  clearInterval(inter.meeting); //销毁之前定时器
+  clearInterval(inter.rebootNow); //销毁之前定时器
+});
+</script>
+<style>
+.faceView {
+  width: 100% !important;
+  height: 100% !important;
+}
+
+iframe {
+  width: 100% !important;
+  height: 100% !important;
+  border-width: 0;
+}
+</style>
+<style lang="scss" scoped>
+:deep() {
+  .u-modal {
+    width: 30rem !important;
+
+    &__title {
+      font-size: 18px !important;
+    }
+    .slot-content {
+      font-size: 16px;
+      width: 100%;
+    }
+  }
+
+  .u-cell__body {
+    padding-left: 0 !important;
+    padding-right: 0 !important;
+  }
+
+  .u-line {
+    border-bottom: 1px solid #dadbde !important;
+  }
+}
+</style>

+ 62 - 0
src/permission.js

@@ -0,0 +1,62 @@
+import { getToken } from "@/utils/auth";
+import setting from "@/plugins/setting.plugins";
+
+// 登录页面
+const loginPage = "/pages/login";
+
+// 页面白名单
+const whiteList = [
+  "/pages/login",//登录
+  "/pages/register",//注册
+  "/pages/serveConfig",//服务器配置
+  "/pages/serveConfigSelect",//服务器配置
+  "/pages/common/textview/index",//浏览文本
+  "/pages/common/webview/index",
+  "/pages/business/mhxf/unitInfoCollection/index",//单位信息采集
+  "/pages/common/invoicing/index",//开票管理
+  "/pages/common/success/index",//成功
+  "/pages/common/phoneVerify/index",//手机号验证
+  "/pages/business/fireIot/repairReport/index",//报修申请
+  "/pages/business/fireIot/repairReport/record",//报修历史
+  "/pages/common/evaluate/record",//服务评价
+  "/pages/common/NFC/index",//NFC读取
+  "/pages/common/appMessage/details",//消息详情
+  "/pages/face/index",//人脸识别
+  "/pages/door/index",//门禁识别
+  "/pages/door/setting/index",//门禁配置
+];
+
+// 检查地址白名单
+function checkWhite(url) {
+  const path = url.split("?")[0];
+  return whiteList.indexOf(path) !== -1;
+}
+
+// 页面跳转验证拦截器
+let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"];
+list.forEach((item) => {
+  uni.addInterceptor(item, {
+    invoke(to) {
+      // #ifdef APP-PLUS
+      setting.formatSize(); //获取缓存大小
+      // #endif
+
+      if (getToken()) {
+        if (to.url === loginPage) {
+          uni.reLaunch({ url: "/pages/index" });
+        }
+        return true;
+      } else {
+
+        if (checkWhite(to.url)) {
+          return true;
+        }
+        uni.reLaunch({ url: loginPage });
+        return false;
+      }
+    },
+    fail(err) {
+      console.log(err);
+    },
+  });
+});

+ 60 - 0
src/plugins/auth.plugins.js

@@ -0,0 +1,60 @@
+import store from '@/store'
+
+function authPermission(permission) {
+  const all_permission = "*:*:*"
+  const permissions = store.getters && store.getters.permissions
+  if (permission && permission.length > 0) {
+    return permissions.some(v => {
+      return all_permission === v || v === permission
+    })
+  } else {
+    return false
+  }
+}
+
+function authRole(role) {
+  const super_admin = "admin"
+  const roles = store.getters && store.getters.roles
+  if (role && role.length > 0) {
+    return roles.some(v => {
+      return super_admin === v || v === role
+    })
+  } else {
+    return false
+  }
+}
+
+export default {
+  // 验证用户是否具备某权限
+  hasPermi(permission) {
+    return authPermission(permission)
+  },
+  // 验证用户是否含有指定权限,只需包含其中一个
+  hasPermiOr(permissions) {
+    return permissions.some(item => {
+      return authPermission(item)
+    })
+  },
+  // 验证用户是否含有指定权限,必须全部拥有
+  hasPermiAnd(permissions) {
+    return permissions.every(item => {
+      return authPermission(item)
+    })
+  },
+  // 验证用户是否具备某角色
+  hasRole(role) {
+    return authRole(role)
+  },
+  // 验证用户是否含有指定角色,只需包含其中一个
+  hasRoleOr(roles) {
+    return roles.some(item => {
+      return authRole(item)
+    })
+  },
+  // 验证用户是否含有指定角色,必须全部拥有
+  hasRoleAnd(roles) {
+    return roles.every(item => {
+      return authRole(item)
+    })
+  }
+}

+ 315 - 0
src/plugins/common.plugins.js

@@ -0,0 +1,315 @@
+import modal from "./modal.plugins";
+
+export default {
+  /**
+   * 参数处理
+   * @param params 参数
+   */
+  tansParams(params) {
+    let result = "";
+    for (const propName of Object.keys(params)) {
+      const value = params[propName];
+      var part = encodeURIComponent(propName) + "=";
+      if (value !== null && value !== "" && typeof value !== "undefined") {
+        if (typeof value === "object") {
+          for (const key of Object.keys(value)) {
+            if (value[key] !== null && value[key] !== "" && typeof value[key] !== "undefined") {
+              let params = propName + "[" + key + "]";
+              var subPart = encodeURIComponent(params) + "=";
+              result += subPart + encodeURIComponent(value[key]) + "&";
+            }
+          }
+        } else {
+          result += part + encodeURIComponent(value) + "&";
+        }
+      }
+    }
+    return result;
+  },
+  /**
+   * 数据映射
+   * @param reKey 需要返回的key
+   * @param isKey 需要比对的key
+   * @param value 对比值
+   * @param data 数据集
+   */
+  mapping(reKey, isKey, value, data) {
+    if (typeof value == "string" && value.indexOf(",") > -1) {
+      //value为字符串的id集合
+      let arr = value.split(",");
+      let returnValue = "";
+
+      arr.forEach((e, index) => {
+        data.forEach((f) => {
+          if (e == f[isKey]) {
+
+            returnValue = returnValue ? `${returnValue},${f[reKey]}` : f[reKey];
+          }
+        })
+      })
+
+      return returnValue;
+    } else {
+      if (!data) return;
+      for (let i = 0; i < data.length; i++) {
+        if (value == data[i][isKey]) {
+          return data[i][reKey];
+        }
+      }
+    }
+  },
+  /**
+   * @一键拨号
+   */
+  makePhoneCall(phone) {
+    uni.makePhoneCall({
+      phoneNumber: phone,
+      success: function () {
+        console.log('success');
+      },
+      fail: function () {
+      }
+    });
+  },
+  /**
+   * 树结构过滤
+   * @param {*} treeData 
+   * @param {*} ids 
+   * @returns 
+   */
+  findTreeNodes(treeData, ids) {
+    const result = [];
+    const findNodes_ = (nodes, idArray) => {
+      nodes.forEach(node => {
+        if (idArray.includes(node.id)) {
+          result.push(node);
+        }
+        if (node.children && node.children.length > 0) {
+          findNodes_(node.children, idArray);
+        }
+      });
+    };
+
+    findNodes_(treeData, ids);
+    return result;
+  },
+  /**
+   * @复制粘贴板
+   * @param {传入值} content 
+   * @returns 
+   */
+  uniCopy({ content, success, error }) {
+    if (!content) return error('复制的内容不能为空 !')
+    content = typeof content === 'string' ? content : content.toString() // 复制内容,必须字符串,数字需要转换为字符串
+    /**
+     * 小程序端 和 app端的复制逻辑
+     */
+    //#ifdef APP-PLUS || MP-WEIXIN
+    uni.setClipboardData({
+      data: content,
+      success: function () {
+        success("复制成功~")
+        console.log('success');
+      },
+      fail: function () {
+        success("复制失败~")
+      }
+    });
+    //#endif
+
+    /**
+     * H5端的复制逻辑
+     */
+    // #ifdef H5
+    if (!document.queryCommandSupported('copy')) { //为了兼容有些浏览器 queryCommandSupported 的判断
+      // 不支持
+      error('浏览器不支持')
+    }
+    let textarea = document.createElement("textarea")
+    textarea.value = content
+    textarea.readOnly = "readOnly"
+    document.body.appendChild(textarea)
+    textarea.select() // 选择对象
+    textarea.setSelectionRange(0, content.length) //核心
+    let result = document.execCommand("copy") // 执行浏览器复制命令
+    if (result) {
+      success("复制成功~")
+    } else {
+      error("复制失败,请检查h5中调用该方法的方式,是不是用户点击的方式调用的,如果不是请改为用户点击的方式触发该方法,因为h5中安全性,不能js直接调用!")
+    }
+    textarea.remove()
+    // #endif
+  },
+  /**
+   * @根据时间分类数据
+   * @param {数据集} data 
+   * @param {需要处理的时间key} timeKey 
+   * @returns 
+   */
+  groupedItems(data, timeKey) {
+    if (data <= 0) {
+      return false
+    }
+
+    const grouped = {};
+    data.forEach((item) => {
+      const date = item[timeKey].split("T")[0];
+      grouped[date] = grouped[date] || [];
+      grouped[date].push(item);
+    });
+    return grouped;
+  },
+  /**
+   * @公共获取URL中的参数
+   */
+  getUrlList() {
+    // 截取url中的list
+    var url = window.location.href;
+    var theRequest = new Object();
+    if (url.indexOf("?") != -1) {
+      var str = url.split("?")[1];
+      var strs = str.split("&");
+      for (var i = 0; i < strs.length; i++) {
+        theRequest[strs[i].split("=")[0]] = strs[i].split("=")[1];
+      }
+    }
+    return theRequest;
+  },
+  /**
+   * @数组对象排序
+   * @return
+   * @param {数据} data
+   * @param {0 从小到大 1 从大到小} sort
+   */
+  sortEvent(data, sort) {
+    let arr = [];
+    // 将需要排序的 key, 进行排列
+    let sortKeys = Object.keys(JSON.parse(JSON.stringify(data))).sort((a, b) => {
+      return sort == 0 ? JSON.parse(JSON.stringify(data))[a].sort - JSON.parse(JSON.stringify(data))[b].sort : JSON.parse(JSON.stringify(data))[b].sort - JSON.parse(JSON.stringify(data))[a].sort;
+    });
+    // 循环排列好的 key, 重新组成一个新的数组
+    for (var sortIndex in sortKeys) {
+      arr.push(JSON.parse(JSON.stringify(data))[sortKeys[sortIndex]]);
+    }
+
+    return arr;
+  },
+  /**
+   * @数组对象去重
+   * @methods data 需要去重的数据
+   * @methods objectName 需要去重的对象名称
+   */
+  uniq(data, objectName) {
+    if (!objectName) {
+      var newArr = [...new Set(data)]
+
+      return newArr;
+    } else {
+      let obj = {};
+
+      let peon = data.reduce((cur, next) => {
+        obj[next[objectName]] ? "" : obj[next[objectName]] = true && cur.push(next);
+        return cur;
+      }, []) //设置cur默认类型为数组,并且初始值为空的数组
+
+      return peon;
+    }
+  },
+  /**
+   * @判断当前是否有网络
+   */
+  isNetwork() {
+    let status = true
+    // 获取网络状态
+    uni.getNetworkType({
+      success: function (res) {
+        if (res.networkType === "none") {
+          modal.msg("网络异常,请稍后重试!");
+          status = false
+        } else {
+          status = true
+        }
+      },
+    });
+
+    return status
+  },
+  /**
+   * @判断用户拒绝权限是否超过48小时
+   */
+  isExpirationTime() {
+    let sotrTime = uni.getStorageSync("expirationTime");
+    if (sotrTime) {
+      if (sotrTime + 3600 * 24 * 2 <= Date.parse(new Date()) / 1000) {
+        return true
+      } else {
+        return false
+      }
+    } else {
+      return true
+    }
+  },
+  /**
+   * @判断是否为微信公众号
+   */
+  isWechatMp() {
+    var ua = navigator?.userAgent.toLowerCase();
+    if (ua?.match(/MicroMessenger/i) == 'micromessenger') {
+      return true;
+    } else {
+      // 不在微信内置浏览器中
+      return false;
+    }
+  },
+  /**
+   * @是否显示容器
+   * @判断是否为微信公众号
+   */
+  isVisible() {
+    let visible = true;
+    //#ifdef H5
+    visible = !this.isWechatMp();
+    //#endif
+
+    //#ifdef APP-PLUS || MP-WEIXIN
+    visible = true;
+    //#endif
+    return visible;
+  },
+  /**
+   * 图片点击放大
+   * @param {*类型} type (1:string:图片地址  2:array(数组对象))
+   * @param {*数据} data
+   */
+  imgEnlarge(type, data) {
+    let param = {}
+    if (type == 1) {
+      param = {
+        urls: [data],
+        current: data
+      }
+    }
+    if (type == 2) {
+      let arr = []
+      for (let i = 0; i < data.length; i++) {
+        arr.push(data[i].url)
+      }
+      param = {
+        urls: arr,
+        current: arr[0]
+      }
+    }
+    if (data && data.length > 0) {
+      uni.previewImage(param)
+    }
+  },
+  /**
+   * 获取本地图片
+   * @param {图片路径} url
+   * @returns
+   */
+  getAssetsFile(url) {
+    var img = `/static/${url}`
+    return new URL(img, import.meta.url).href;
+  },
+};

+ 277 - 0
src/plugins/constData.plugins.js

@@ -0,0 +1,277 @@
+// 首页底部导航栏
+let homeTabbar = [
+	{
+		pagePath: "/pages/index",
+		iconClass: "oaIcon-tab-home",
+		iconPath: "/static/images/tabBar/tab-home.png",
+		selectedIconPath: "/static/images/tabBar/tab-home-blue.png",
+		text: "工作台",
+		dot: false,
+	},
+	// {
+	//   "pagePath": "pages/analyse/analyse",
+	//   "iconPath": "/static/images/tabBar/analyse.png",
+	//   "selectedIconPath": "/static/images/tabBar/analyse-selected.png",
+	//   "text": "分析"
+	// },
+	{
+		pagePath: "/pages/info",
+		iconClass: "oaIcon-tab-info",
+		iconPath: "/static/images/tabBar/tab-info.png",
+		selectedIconPath: "/static/images/tabBar/tab-info-blue.png",
+		text: "消息",
+		dot: false,
+	},
+	{
+		pagePath: "/pages/mine",
+		iconClass: "oaIcon-tab-mine",
+		iconPath: "/static/images/tabBar/tab-my.png",
+		selectedIconPath: "/static/images/tabBar/tab-my-blue.png",
+		text: "我的",
+		dot: false,
+	},
+]
+
+// 工作报告底部导航栏
+let projectTabbar = [
+	{
+		pagePath: "/pages/business/common/projectMange/record/index",
+		iconClass: "oaIcon-tab-reportRecord",
+		iconPath: "",
+		selectedIconPath: "",
+		text: "报告记录",
+		dot: false,
+	},
+	{
+		pagePath: "/pages/business/common/projectMange/write/index",
+		iconClass: "oaIcon-tab-reportInsert",
+		iconPath: "",
+		selectedIconPath: "",
+		text: "报告填写",
+		dot: false,
+	},
+	{
+		pagePath: "/pages/business/common/projectMange/statistics/index",
+		iconClass: "oaIcon-tab-reportCount",
+		iconPath: "",
+		selectedIconPath: "",
+		text: "报告统计",
+		dot: false,
+	},
+	{
+		pagePath: "/pages/business/common/projectMange/list/index",
+		iconClass: "oaIcon-tab-projectsList",
+		iconPath: "",
+		selectedIconPath: "",
+		text: "项目列表",
+		dot: false,
+	},
+	// {
+	// 	pagePath: "/pages/mine",
+	//  iconClass: "oaIcon-tab-reportInsert",
+	// 	iconPath: "/static/images/tabBar/tab-my.png",
+	// 	selectedIconPath: "/static/images/tabBar/tab-my-blue.png",
+	// 	text: "统计导出",
+	// 	dot: false,
+	// },
+	// {
+	// 	pagePath: "/pages/info",
+	//  iconClass: "oaIcon-tab-reportInsert",
+	// 	iconPath: "/static/images/tabBar/tab-info.png",
+	// 	selectedIconPath: "/static/images/tabBar/tab-info-blue.png",
+	// 	text: "模板管理",
+	// 	dot: false,
+	// },
+]
+
+// OA-我的申请导航栏
+let oaApprovalTabbar = [
+	{
+		pagePath: "/pages/business/oa/approval/index",
+		iconClass: "oaIcon-tab-reportRecord",
+		iconPath: "/static/images/tabBar/tab-approval-gray.png",
+		selectedIconPath: "/static/images/tabBar/tab-approval-blue.png",
+		text: "我的申请",
+		dot: false,
+	},
+	{
+		pagePath: "/pages/business/oa/toDo/index",
+		iconClass: "oaIcon-tab-reportInsert",
+		iconPath: "/static/images/tabBar/tab-toDo-gray.png",
+		selectedIconPath: "/static/images/tabBar/tab-toDo-blue.png",
+		text: "我的待办",
+		dot: false,
+	}
+]
+
+
+// 门禁管理底部导航栏
+let doorTabbar = [
+	{
+		pagePath: "/pages/business/doorManage/list/index",
+		iconClass: "oaIcon-tab-projectsList",
+		iconPath: "",
+		selectedIconPath: "",
+		text: "首页",
+		dot: false,
+	},
+	{
+		pagePath: "/pages/business/doorManage/record/index",
+		iconClass: "oaIcon-tab-reportRecord",
+		iconPath: "",
+		selectedIconPath: "",
+		text: "记录",
+		dot: false,
+	},
+]
+
+
+export default {
+	// 主题列表
+	themeList: [
+		{
+			title: '官方',
+			name: 'rf',
+			color: '#149eff',
+			tabList: [
+				'/static/images/tabBar/tab-home-blue.png',
+				'/static/images/tabBar/tab-info-blue.png',
+				'/static/images/tabBar/tab-my-blue.png'
+			]
+		},
+		{
+			title: '海蓝',
+			name: 'blue',
+			color: '#0081ff',
+			tabList: [
+				'/static/images/tabBar/tab-home-blue.png',
+				'/static/images/tabBar/tab-info-blue.png',
+				'/static/images/tabBar/tab-my-blue.png'
+			]
+		},
+		{
+			title: '嫣红',
+			name: 'red',
+			color: '#e54d42',
+			tabList: [
+				'/static/images/tabBar/tab-home-red.png',
+				'/static/images/tabBar/tab-info-red.png',
+				'/static/images/tabBar/tab-my-red.png'
+			]
+		},
+		{
+			title: '桔橙',
+			name: 'orange',
+			color: '#f37b1d',
+			tabList: [
+				'/static/images/tabBar/tab-home-orange.png',
+				'/static/images/tabBar/tab-info-orange.png',
+				'/static/images/tabBar/tab-my-orange.png'
+			]
+		},
+		{
+			title: '明黄',
+			name: 'yellow',
+			color: '#fbbd08',
+			tabList: [
+				'/static/images/tabBar/tab-home-yellow.png',
+				'/static/images/tabBar/tab-info-yellow.png',
+				'/static/images/tabBar/tab-my-yellow.png'
+			]
+		},
+		{
+			title: '橄榄',
+			name: 'olive',
+			color: '#8dc63f',
+			tabList: [
+				'/static/images/tabBar/tab-home-olive.png',
+				'/static/images/tabBar/tab-info-olive.png',
+				'/static/images/tabBar/tab-my-olive.png'
+			]
+		},
+		{
+			title: '森绿',
+			name: 'green',
+			color: '#39b54a',
+			tabList: [
+				'/static/images/tabBar/tab-home-green.png',
+				'/static/images/tabBar/tab-info-green.png',
+				'/static/images/tabBar/tab-my-green.png'
+			]
+		},
+		{
+			title: '天青',
+			name: 'cyan',
+			color: '#1cbbb4',
+			tabList: [
+				'/static/images/tabBar/tab-home-cyan.png',
+				'/static/images/tabBar/tab-info-cyan.png',
+				'/static/images/tabBar/tab-my-cyan.png'
+			]
+		},
+		{
+			title: '姹紫',
+			name: 'purple',
+			color: '#6739b6',
+			tabList: [
+				'/static/images/tabBar/tab-home-purple.png',
+				'/static/images/tabBar/tab-info-purple.png',
+				'/static/images/tabBar/tab-my-purple.png'
+			]
+		},
+		{
+			title: '木槿',
+			name: 'mauve',
+			color: '#9c26b0',
+			tabList: [
+				'/static/images/tabBar/tab-home-mauve.png',
+				'/static/images/tabBar/tab-info-mauve.png',
+				'/static/images/tabBar/tab-my-mauve.png'
+			]
+		},
+		{
+			title: '桃粉',
+			name: 'pink',
+			color: '#e03997',
+			tabList: [
+				'/static/images/tabBar/tab-home-pink.png',
+				'/static/images/tabBar/tab-info-pink.png',
+				'/static/images/tabBar/tab-my-pink.png'
+			]
+		},
+		{
+			title: '棕褐',
+			name: 'brown',
+			color: '#a5673f',
+			tabList: [
+				'/static/images/tabBar/tab-home-brown.png',
+				'/static/images/tabBar/tab-info-brown.png',
+				'/static/images/tabBar/tab-my-brown.png'
+			]
+		},
+		{
+			title: '玄灰',
+			name: 'grey',
+			color: '#8799a3',
+			tabList: [
+				'/static/images/tabBar/tab-home-grey.png',
+				'/static/images/tabBar/tab-info-grey.png',
+				'/static/images/tabBar/tab-my-grey.png'
+			]
+		},
+		{
+			title: '墨黑',
+			name: 'black',
+			color: '#333333',
+			tabList: [
+				'/static/images/tabBar/tab-home-black.png',
+				'/static/images/tabBar/tab-info-black.png',
+				'/static/images/tabBar/tab-my-black.png'
+			]
+		}
+	],
+	homeTabbar: homeTabbar,
+	projectTabbar: projectTabbar,
+	oaApprovalTabbar:oaApprovalTabbar,
+	doorTabbar:doorTabbar
+};

+ 71 - 0
src/plugins/index.js

@@ -0,0 +1,71 @@
+import tab from "./tab.plugins";
+import auth from "./auth.plugins";
+import modal from "./modal.plugins";
+import common from "./common.plugins";
+import setting from "./setting.plugins";
+import time from "./time.plugins.js";
+import constData from "./constData.plugins.js";
+import nfc from "./nfc.plugins.js";
+import keyListen from "./keyListen.plugins.js";
+import permission from "./permission.plugins.js";
+
+import config from "@/config"; // config
+import { useDict } from '@/utils/dict'
+import { settingStores } from "@/store/modules/index";
+
+import dayjs from 'dayjs'
+import isBetween from 'dayjs/plugin/isBetween';
+import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
+import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
+dayjs.extend(isBetween);
+dayjs.extend(isSameOrAfter)
+dayjs.extend(isSameOrBefore)
+
+export default {
+  install(app) {
+    // 公共请求路径
+    app.provide("$BASE_URL", config.baseUrl);
+    app.config.globalProperties.$BASE_URL = config.baseUrl;
+    // 公共website路径
+    app.provide("$websiteUrl", config.websiteUrl);
+    app.config.globalProperties.$websiteUrl = config.websiteUrl;
+    // 页签操作
+    app.provide("$tab", tab);
+    app.config.globalProperties.$tab = tab;
+    // 认证对象
+    app.provide("$auth", auth);
+    app.config.globalProperties.$auth = auth;
+    // 模态框对象
+    app.provide("$modal", modal);
+    app.config.globalProperties.$modal = modal;
+    // 数据处理
+    app.provide("$common", common);
+    app.config.globalProperties.$common = common;
+    // 公共设置方法
+    app.provide("$setting", setting);
+    app.config.globalProperties.$setting = setting;
+    // 公共时间处理方法
+    app.provide("$time", time);
+    app.config.globalProperties.$time = time;
+    // 公共默认数据存储
+    app.provide("$constData", constData);
+    app.config.globalProperties.$constData = constData;
+    // 公共NFC
+    app.provide("$nfc", nfc);
+    app.config.globalProperties.$nfc = nfc;
+    // 公共设置stroe
+    app.provide("$settingStore", settingStores());
+    app.config.globalProperties.$settingStore = settingStores();
+    // 公共时间处理方法dayjs
+    app.provide("$dayjs", dayjs);
+    app.config.globalProperties.$dayjs = dayjs;
+    // 公共字典模块
+    app.config.globalProperties.useDict = useDict
+    // 公共物理按钮监听
+    app.provide("$keyListen", keyListen);
+    app.config.globalProperties.$keyListen = keyListen;
+    // App权限判断
+    app.provide("$permission", permission);
+    app.config.globalProperties.$permission = permission;
+  },
+};

+ 72 - 0
src/plugins/jsencrypt.js

@@ -0,0 +1,72 @@
+import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
+
+// 密钥对生成 http://web.chacuo.net/netrsakeypair
+
+const publicKey = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxPWP0HTBE9vEeM34Qx03U8oVm
+C6xIqWPRuI5t8J0zEDQudAgXKPjy8E0Q3cX800UNBTx2gUfRRNrONqALKDnJ1SE6
+qCUDeXOez8sa95GQ9d4BX7pSjZLrPfnCBTBtb5LGkY5zmlmtpG2AV9eJr+kQqhs/
+r0c4njwaDjVG4kF3ZQIDAQAB`
+
+const privateKey = `MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALE9Y/QdMET28R4z
+fhDHTdTyhWYLrEipY9G4jm3wnTMQNC50CBco+PLwTRDdxfzTRQ0FPHaBR9FE2s42
+oAsoOcnVITqoJQN5c57Pyxr3kZD13gFfulKNkus9+cIFMG1vksaRjnOaWa2kbYBX
+14mv6RCqGz+vRziePBoONUbiQXdlAgMBAAECgYBjSDdAXEVYrFdeiouYjHwdyAhP
+pERKo5BFvzMRhJIaM353cwnBJ3NkapVQ2Fn6iMIKTB+VZk+7eu1yTAkUluDfLowd
+REZS4ipOBY5UuNnjbXmSOoUQw6vRnox0X4x6S1vd4FBHgpVe1VkiE7Nz5U7Clyd5
+yw2P1lHwMyB/guAH4QJBAN3dGkMASj0jm23maHOfehp/zlACB8HpMKuV4z/bEg45
+nC9Hw5NloUHrXdzEXP1+S46MCH2THflxDVYtnZTRLO0CQQDMgp3Jrn7kkKtNceZF
+R08hLbVmfNlatgONgFJ5JnR+GTQ6o2gwM6SLyoBkfAIiEDpr6c6nBXTU09GOYxBk
++h1ZAkB32pXxVBrG5JF20V3j+GcyIZEGz9H5A0xzpUlambIrVRv2vsH8wo5W2hue
+w8Woe629mBCOJgevVU9rGsFiP44RAkEApbTYAQjAjJakFpZJjKzg8vNEXoye2R9N
+9aOaL8v27A2kAjdRPm050IL+UW0hlVQs4i+KYE7NgX03+PVP3WHD0QJBANLo4PRw
+7Y+dLPAzuazsD3/5SYaSh+KSD/+tVbc6CFvLyfFUKp/a4PzzvGaLo/Ky/ffOY5k0
+hmavbHCKcg+r+hg=`
+
+
+const publicKey2 = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9GjiPyz3FfHcEAJu5mgYkaAyt
+xs1kSRIg7j9nFB4cUOKn5flsTwHjn+lR5D0MVc4xS9gtECgOMJsazqsxF35L5TKW
+i0GiX5zw9NhQz6Lv6P30mcm5mJs2UHOyeqr7rtDQn5Uun7Q9cfymQQ+ln7I54rCr
+GhTrkQzMw8+vhTVPKQIDAQAB`
+
+const privateKey2 = `MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL0aOI/LPcV8dwQA
+m7maBiRoDK3GzWRJEiDuP2cUHhxQ4qfl+WxPAeOf6VHkPQxVzjFL2C0QKA4wmxrO
+qzEXfkvlMpaLQaJfnPD02FDPou/o/fSZybmYmzZQc7J6qvuu0NCflS6ftD1x/KZB
+D6WfsjnisKsaFOuRDMzDz6+FNU8pAgMBAAECgYAsJCzD1AttR+p4MrQnFP8ODIO5
+JMjY+f2TVknSg+2ram4eBx67241zVuadyQs/D+B7vVqO2lCfT1VFYqyvXu6J3YX0
+yagkQRiVLJLSMmP14ZCOlVA6jmit0MnMxG0XZltWnjpWyMmlGiNFnmK1Y84b913W
+k1H0mXKnCV2v5xD0AQJBAPQitqPCEPtLECXzdw3DBKNj8w1ldMNHU61dGKF/xDsQ
+6pbX5UZGeHBueqGKB2VqFFMtND6IlEVZ9kVTN1VIYEECQQDGStc+EMn3Xbixk2Al
+cMyfbGtpRQuGUk77uBf0HP7VMoL5j+t8KW3KflgKnY1AJa2VxoCk/Fx1tsdu2CbY
+qLTpAkAZ9PbmQmP70+dLoa8uz2VW2fGlovvfJ15GOHe99A+xGANZNmsyJZv4mEaB
+lmNi7PxsO4oqe+sH1KDSjh57+s4BAkEAp9YK73aobB7AyFT0iVw7ZikPlS+ivJOI
+VNkSNUYhj/TMFU3yxMoQKtfbz0hhmU0K6v30PzF3VQ9bKH/+CV5qmQJBAKhH5m6E
+af/baa6dQJbCCS8aVeiFe0AAUQKamAjkRdfUa1/W6O71A3ePsr41l0X4TGpXPapG
+dc/Ul5Q8wSTXkYE=`
+
+// 加密
+export function encrypt(txt) {
+    const encryptor = new JSEncrypt()
+    encryptor.setPublicKey(publicKey) // 设置公钥
+    return encryptor.encrypt(txt) // 对数据进行加密
+}
+
+// 解密
+export function decrypt(txt) {
+    const encryptor = new JSEncrypt()
+    encryptor.setPrivateKey(privateKey) // 设置私钥
+    return encryptor.decrypt(txt) // 对数据进行解密
+}
+
+// 加密c++业务(詹)
+export function encrypt2(txt) {
+    const encryptor = new JSEncrypt()
+    encryptor.setPublicKey(publicKey2) // 设置公钥
+    return encryptor.encrypt(txt) // 对数据进行加密
+}
+
+// 解密c++业务(詹)
+export function decrypt2(txt) {
+    const encryptor = new JSEncrypt()
+    encryptor.setPrivateKey(privateKey2) // 设置私钥
+    return encryptor.decrypt(txt) // 对数据进行解密
+}

+ 128 - 0
src/plugins/keyListen.plugins.js

@@ -0,0 +1,128 @@
+const SYSTEM_REASON = "reason";
+//Home键
+const SYSTEM_HOME_KEY = "homekey";
+//最近使用的应用键
+const SYSTEM_RECENT_APPS = "recentapps";
+
+let receiver, Intent = false;
+export default {
+    startListen: function (needStopSystem = false) {//开始监听,初始化
+        //参数needStopSystem默认不阻止系统响应,如需阻止调用startListen(true)
+        /*特别提醒,全面屏手势Home和recent部分机型是监听不到的,自行测试,如遇卡死电脑控制台直接重新运行项目即可解决!*/
+        let that = this;
+        let main = plus.android.runtimeMainActivity();
+        that.openListen();
+        if (needStopSystem) {
+            plus.key.addEventListener("backbutton", function (e) {
+                // main.stopLockTask()//按返回键恢复
+                // main.startLockTask()//阻止系统home建和近期任务键
+                // main.unregisterReceiver(receiver); //同时停止接收home和recent点击
+                e.preventDefault();
+            });
+        } else {
+        }
+
+    },
+    openListen: function () {//注册监听
+        let that = this;
+        try {
+            let main = plus.android.runtimeMainActivity();
+            Intent = plus.android.importClass('android.content.Intent');
+            let IntentFilter = plus.android.importClass('android.content.IntentFilter');
+            var filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_SCREEN_ON);
+            filter.addAction(Intent.ACTION_SCREEN_OFF);
+            filter.addAction(Intent.ACTION_USER_PRESENT);
+            filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+            filter.addAction('android.intent.action.MEDIA_BUTTON');
+            receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
+                onReceive: function (context, intent) { //实现onReceiver回调函数  
+                    let act = intent.getAction();
+                    if (act == Intent.ACTION_SCREEN_ON) {
+                        console.log('开屏')
+                    } else if (act == Intent.ACTION_SCREEN_OFF) {
+                        console.log('锁屏')
+                    } else if (act == Intent.ACTION_USER_PRESENT) {
+                        console.log('解锁')
+                    } else if (act == Intent.ACTION_CLOSE_SYSTEM_DIALOGS) {
+                        let systemReason = intent.getStringExtra(SYSTEM_REASON);
+                        console.log("关闭原因", systemReason);
+                        if (systemReason != null) {
+                            if (systemReason == SYSTEM_HOME_KEY) {
+                                // System.out.println("按下HOME键");
+                                console.log('按下HOME键')
+                                that.launchApp();//启动APP
+
+                            } else if (systemReason == SYSTEM_RECENT_APPS) {
+                                // System.out.println("按下多任务键");
+                                console.log('按下多任务键')
+                                that.launchApp();//启动APP
+                            }
+                        }
+                    }
+                    // main.unregisterReceiver(receiver);
+                    /*在这里取消会出现第二次监听无效,可根据不同业务要求
+                    在不同时刻停止接收两个按键的广播,我的处理是在返回的时候停*/
+                    // context.unregisterReceiver(receiver);
+                }
+            });
+            main.registerReceiver(receiver, filter); //注册监听  
+        } catch (e) {
+            console.error(e);
+            that.toast('初始化错误');
+        }
+    },
+    // 全屏函数
+    fullScreen() {
+        // Android全屏
+        if (uni.getSystemInfoSync().platform === "android") {
+            plus.android.invoke("setFullScreen", true);
+        }
+
+        // iOS全屏
+        if (uni.getSystemInfoSync().platform === "ios") {
+            // iOS无法通过API直接全屏,可以尝试隐藏所有导航栏
+            plus.navigator.hideSystemBar();
+        }
+    },
+    // 进入固定屏幕模式
+    stopHomeEmit() {
+        let main = plus.android.runtimeMainActivity();
+        main.stopLockTask()//按返回键恢复
+        main.startLockTask()//阻止系统home建和近期任务键
+    },
+    // 启动APP
+    launchApp() {
+        var isApp = plus.runtime.isApplicationExist({
+            //查看安卓系统手机有没有下载这款app
+            pname: 'android.dcloud.uskyMobile' //本地浏览器的包名
+        })
+
+        if (isApp) {
+            //安装了app则运行
+            plus.runtime.launchApplication(
+                {
+                    pname: "android.dcloud.uskyMobile",
+                    newTask: false
+                },
+                (e) => {
+                    console.log("e", e);
+                }
+            );
+        }
+        plus.runtime.launcher('shortcut');
+        plus.runtime.restart();
+    },
+    // 退出APP
+    quitApp() {
+        plus.runtime.quit();
+    },
+    // 提示
+    toast(content) {
+        uni.showToast({
+            title: content,
+            icon: 'none'
+
+        })
+    }
+}

+ 94 - 0
src/plugins/modal.plugins.js

@@ -0,0 +1,94 @@
+export default {
+  // 消息提示
+  msg(content) {
+    uni.showToast({
+      title: content,
+      icon: "none",
+    });
+  },
+  // 错误消息
+  msgError(content) {
+    uni.showToast({
+      title: content,
+      icon: "error",
+    });
+  },
+  // 成功消息
+  msgSuccess(content) {
+    uni.showToast({
+      title: content,
+      icon: "success",
+    });
+  },
+  // 隐藏消息
+  hideMsg(content) {
+    uni.hideToast();
+  },
+  // 弹出提示
+  alert(title, content) {
+    uni.showModal({
+      title: title,
+      content: content,
+      showCancel: false,
+      mask: true,
+    });
+  },
+  // 确认窗体
+  confirm(content) {
+    return new Promise((resolve, reject) => {
+      uni.showModal({
+        title: "系统提示",
+        content: content,
+        cancelText: "取消",
+        confirmText: "确定",
+        success: function (res) {
+          if (res.confirm) {
+            resolve(res.confirm);
+          }
+        },
+      });
+    });
+  },
+  // 确认窗体带输入框
+  confirmInput(content,placeholderText) {
+    return new Promise((resolve, reject) => {
+      uni.showModal({
+        title: content,
+        editable: true,
+        inputType: "text",
+        placeholderText: placeholderText,
+        cancelText: "取消",
+        confirmText: "确定",
+        success: function (res) {
+          if (res.confirm) {
+            resolve(res);
+          }
+        },
+      });
+    });
+  },
+  // 提示信息
+  showToast(option) {
+    if (typeof option === "object") {
+      uni.showToast(option);
+    } else {
+      uni.showToast({
+        title: option,
+        icon: "none",
+        duration: 2500,
+      });
+    }
+  },
+  // 打开遮罩层
+  loading(content) {
+    uni.showLoading({
+      title: content,
+      icon: "none",
+      mask: true,
+    });
+  },
+  // 关闭遮罩层
+  closeLoading() {
+    uni.hideLoading();
+  },
+};

+ 298 - 0
src/plugins/nfc.plugins.js

@@ -0,0 +1,298 @@
+import tab from "./tab.plugins";
+import { settingStores } from "@/store/modules/index";
+
+var NfcAdapter;
+var NdefRecord;
+var NdefMessage;
+var waiting;
+var readyWriteData = false; //开启写
+var readyRead = false; //开启读
+var whetherNFC = false; //是否有NFC
+var techListsArray = [
+    ["android.nfc.tech.IsoDep"],
+    ["android.nfc.tech.NfcA"],
+    ["android.nfc.tech.NfcB"],
+    ["android.nfc.tech.NfcF"],
+    ["android.nfc.tech.Nfcf"],
+    ["android.nfc.tech.NfcV"],
+    ["android.nfc.tech.NdefFormatable"],
+    ["android.nfc.tech.MifareClassic"],
+    ["android.nfc.tech.MifareUltralight"],
+];
+
+export default {
+    initNFC() {
+        if (uni.getSystemInfoSync().platform == "android") {
+            listenNFCStatus()
+        }
+
+        if (uni.getSystemInfoSync().platform == "ios") {
+            uni.showToast({
+                title: "设备不支持NFC!",
+                icon: "none",
+            });
+            return;
+        }
+    },
+    readNFC(callback) {
+        return new Promise((resolve, reject) => {
+            if (uni.getSystemInfoSync().platform == "android") {
+                plus.globalEvent.addEventListener("newintent", function () {
+                    readyRead = true; //开启读
+                    plus.device.vibrate(500); //调用手机震动
+                    settingStores().nfcWaiting = '请将手机靠近NFC标签'
+                    handleNFCData().then((event) => {
+                        resolve(event)
+                    })
+                }, false);
+            }
+        })
+    },
+    closeNFC() {
+        if (uni.getSystemInfoSync().platform == "android") {
+            closeReadAndWrite();
+        }
+    }
+}
+
+function listenNFCStatus() {
+    try {
+        var main = plus.android.runtimeMainActivity();
+        var Intent = plus.android.importClass("android.content.Intent");
+        var Activity = plus.android.importClass("android.app.Activity");
+        var PendingIntent = plus.android.importClass("android.app.PendingIntent");
+        var IntentFilter = plus.android.importClass("android.content.IntentFilter");
+        NfcAdapter = plus.android.importClass("android.nfc.NfcAdapter");
+        var nfcAdapter = NfcAdapter.getDefaultAdapter(main);
+
+        if (nfcAdapter == null) {
+            uni.showToast({
+                title: "设备不支持NFC!",
+                icon: "none",
+            });
+            return;
+        }
+
+        if (!nfcAdapter.isEnabled()) {
+            uni.showToast({
+                title: "请在系统设置中先启用NFC功能!",
+                icon: "none",
+            });
+            return;
+        }
+
+        var intent = new Intent(main, main.getClass());
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        var pendingIntent = PendingIntent.getActivity(main, 0, intent, 0);
+        var ndef = new IntentFilter("android.nfc.action.TECH_DISCOVERED");
+        ndef.addDataType("*/*");
+        var intentFiltersArray = [ndef];
+
+        plus.globalEvent.addEventListener(
+            "pause",
+            function (e) {
+                if (nfcAdapter) {
+                    //关闭前台调度系统
+                    //恢复默认状态
+                    nfcAdapter.disableForegroundDispatch(main);
+                }
+            },
+            false
+        );
+        plus.globalEvent.addEventListener(
+            "resume",
+            function (e) {
+                if (nfcAdapter) {
+                    //开启前台调度系统
+                    // 优于所有其他NFC
+                    nfcAdapter.enableForegroundDispatch(main, pendingIntent, intentFiltersArray, techListsArray);
+                }
+            },
+            false
+        );
+        nfcAdapter.enableForegroundDispatch(main, pendingIntent, intentFiltersArray, techListsArray);
+    } catch (e) {
+        console.error(e);
+    }
+}
+
+function handleNFCData() {
+    return new Promise((resolve, reject) => {
+        NdefRecord = plus.android.importClass("android.nfc.NdefRecord");
+        NdefMessage = plus.android.importClass("android.nfc.NdefMessage");
+        var main = plus.android.runtimeMainActivity();
+        var intent = main.getIntent();
+        if ("android.nfc.action.TECH_DISCOVERED" == intent.getAction()) {
+            if (readyWriteData) {
+                //__write(intent);
+                readyWriteData = false;
+            } else if (readyRead) {
+                __read(intent).then((event) => {
+                    resolve(event)
+                });
+                readyRead = false;
+            }
+        }
+    })
+}
+
+/**
+ * @NFC写入
+ * @param {*} intent
+ * @returns
+ */
+function __write(intent) {
+    try {
+        waiting.setTitle("请勿移开标签\n正在写入...");
+        var text = document.getElementById("text").value;
+        console.log("text=" + text);
+
+        var textBytes = plus.android.invoke(text, "getBytes");
+        var textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, plus.android.invoke("text/plain", "getBytes"), plus.android.invoke("", "getBytes"), textBytes);
+        var message = new NdefMessage([textRecord]);
+        var Ndef = plus.android.importClass("android.nfc.tech.Ndef");
+        var NdefFormatable = plus.android.importClass("android.nfc.tech.NdefFormatable");
+        var tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
+        var ndef = Ndef.get(tag);
+        if (ndef != null) {
+            var size = message.toByteArray().length;
+            console.log("size=" + size);
+            ndef.connect();
+            if (!ndef.isWritable()) {
+                showToast("tag不允许写入");
+                waiting.close();
+                return;
+            }
+            if (ndef.getMaxSize() < size) {
+                showToast("文件大小超出容量");
+                waiting.close();
+                return;
+            }
+
+            ndef.writeNdefMessage(message);
+            waiting.close();
+            showToast("写入数据成功.");
+            return;
+        } else {
+            var format = NdefFormatable.get(tag);
+            if (format != null) {
+                try {
+                    format.connect();
+                    format.format(message);
+                    showToast("格式化tag并且写入message");
+                    waiting.close();
+                    return;
+                } catch (e) {
+                    showToast("格式化tag失败.");
+                    waiting.close();
+                    return;
+                }
+            } else {
+                showToast("Tag不支持NDEF");
+                waiting.close();
+                return;
+            }
+        }
+    } catch (e) {
+        console.log("error=" + e);
+        waiting.close();
+        alert("写入失败");
+    }
+}
+
+/**
+ * @NFC读取
+ * @param {*} intent
+ * @returns
+ */
+function __read(intent) {
+    return new Promise((resolve, reject) => {
+        try {
+            settingStores().nfcWaiting = '请勿移开标签\n正在读取数据...'
+            // waiting.setTitle("请勿移开标签\n正在读取数据...");
+            var tag = plus.android.importClass("android.nfc.Tag");
+            tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
+            var bytesId = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
+            // waiting.close();
+            var tagid = bytesToHexString(tag.getId());
+
+            resolve(tagid)
+        } catch (e) {
+            uni.showToast({
+                title: e,
+                icon: "none",
+            });
+        }
+    })
+}
+
+/**
+ * @十六进制转换
+ * @param { 值 } inarray 
+ * @returns 
+ */
+function bytesToHexString(inarray) {
+    var i, j, x;
+    var hex = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
+    var out = "";
+    for (j = 0; j < inarray.length; ++j) {
+        x = parseInt(inarray[j]) & 0xff;
+        i = (x >> 4) & 0x0f;
+        out += hex[i];
+        i = x & 0x0f;
+        out += hex[i];
+    }
+    return out;
+}
+
+function reverseTwo(str) {
+    var str1 = "";
+    for (var i = 1; i <= str.length; i++) {
+        str1 += str[i - 1];
+        if (i % 2 == 0) {
+            if (i == str.length) {
+                break;
+            }
+            str1 += ":";
+        }
+    }
+    var str2 = "";
+    for (var i = str1.split(":").length - 1; i >= 0; i--) {
+        str2 += str1.split(":")[i];
+    }
+    return str2;
+}
+
+if (uni.getSystemInfoSync().platform == "android") {
+    //plus.globalEvent.addEventListener('plusready', listenNFCStatus, false);
+}
+
+function writeData() {
+    var textEle = plus.globalEvent.getElementById("text");
+    if (!textEle.value) {
+        uni.showToast({
+            title: "请输入要写入的内容!",
+            icon: "none",
+        });
+        return;
+    }
+    readyWriteData = true;
+    settingStores().nfcWaiting = '请将手机靠近NFC标签'
+    // waiting = plus.nativeUI.showWaiting("请将手机靠近NFC标签!");
+}
+
+function showToast(msg) {
+    plus.nativeUI.toast(msg);
+}
+
+/**
+ * @关闭NFC
+ */
+function closeReadAndWrite() {
+    readyWriteData = false;
+    readyRead = false;
+
+    if (waiting) {
+        waiting.close();
+    }
+}

+ 238 - 0
src/plugins/permission.plugins.js

@@ -0,0 +1,238 @@
+let isIos = false;
+
+// #ifdef APP-PLUS
+isIos = (plus.os.name === 'iOS')
+// #endif
+
+// 判断安卓主方法
+function requestAndroidPermission(permissionID, ifRequest) {
+	return new Promise((resolve, reject) => {
+		plus.android.requestPermissions([permissionID], onSuccess, onError)
+		function onSuccess(res) {
+			const grantedList = res.granted
+			const deniedList = res.deniedPresent
+			const deniedAlwaysList = res.deniedAlways
+			if (grantedList.includes(permissionID)) {
+				resolve(true)
+			} else {
+				resolve(false)
+				ifRequest && gotoAppPermissionSetting()
+			}
+		}
+		function onError(err) {
+			reject(err)
+		}
+	})
+}
+
+// 分别判断Ios
+function judgeIosPermissionPush(ifRequest) {
+	return new Promise(resolve => {
+		var UIApplication = plus.ios.import("UIApplication");
+		var app = UIApplication.sharedApplication();
+		var enabledTypes = 0;
+		if (app.currentUserNotificationSettings) {
+			var settings = app.currentUserNotificationSettings();
+			enabledTypes = settings.plusGetAttribute("types");
+			if (enabledTypes == 0) {
+				ifRequest && gotoAppPermissionSetting()
+				resolve(false)
+			} else {
+				resolve(true)
+			}
+			plus.ios.deleteObject(settings);
+		} else {
+			enabledTypes = app.enabledRemoteNotificationTypes();
+			if (enabledTypes == 0) {
+				ifRequest && gotoAppPermissionSetting()
+				resolve(false)
+			} else {
+				resolve(true)
+			}
+		}
+		plus.ios.deleteObject(app);
+		plus.ios.deleteObject(UIApplication);
+	})
+}
+
+// 判断定位权限是否开启
+function judgeIosPermissionLocation(ifRequest) {
+	return new Promise(resolve => {
+		var cllocationManger = plus.ios.import("CLLocationManager");
+		var status = cllocationManger.authorizationStatus();
+		if (status == 2) {
+			ifRequest && gotoAppPermissionSetting()
+			resolve(false)
+		} else {
+			resolve(true)
+		}
+		plus.ios.deleteObject(cllocationManger);
+	})
+}
+
+// 判断麦克风权限是否开启
+function judgeIosPermissionRecord(ifRequest) {
+	return new Promise(resolve => {
+		var avaudiosession = plus.ios.import("AVAudioSession");
+		var avaudio = avaudiosession.sharedInstance();
+		var permissionStatus = avaudio.recordPermission();
+		if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
+			ifRequest && gotoAppPermissionSetting()
+			resolve(false)
+		} else {
+			resolve(true)
+		}
+		plus.ios.deleteObject(avaudiosession);
+	})
+}
+
+// 判断相机权限是否开启
+function judgeIosPermissionCamera(ifRequest) {
+	return new Promise(resolve => {
+		var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
+		var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
+		if (authStatus == 3) {
+			resolve(true)
+		} else {
+			ifRequest && gotoAppPermissionSetting()
+			resolve(false)
+		}
+		plus.ios.deleteObject(AVCaptureDevice);
+	})
+}
+
+// 判断相册权限是否开启
+function judgeIosPermissionPhotoLibrary(ifRequest) {
+	return new Promise(resolve => {
+		var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
+		var authStatus = PHPhotoLibrary.authorizationStatus();
+		if (authStatus == 3) {
+			resolve(true)
+		} else {
+			ifRequest && gotoAppPermissionSetting()
+			resolve(false)
+		}
+		plus.ios.deleteObject(PHPhotoLibrary);
+	})
+}
+
+// 判断通讯录权限是否开启
+function judgeIosPermissionContact(ifRequest) {
+	return new Promise(resolve => {
+		var CNContactStore = plus.ios.import("CNContactStore");
+		var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
+		if (cnAuthStatus == 3) {
+			resolve(true)
+		} else {
+			ifRequest && gotoAppPermissionSetting()
+			resolve(false)
+		}
+		plus.ios.deleteObject(CNContactStore);
+	})
+}
+
+// 判断所有权限
+function getPermisson(permisson, ifRequest = false) {
+	switch (permisson) {
+		case "location":
+			if (isIos) {
+				return judgeIosPermissionLocation(ifRequest)
+			} else {
+				return requestAndroidPermission("android.permission.ACCESS_FINE_LOCATION", ifRequest)
+			}
+			break;
+		case "camera":
+			if (isIos) {
+				return judgeIosPermissionCamera(ifRequest)
+			} else {
+				return requestAndroidPermission("android.permission.CAMERA", ifRequest)
+			}
+			break;
+		case "photo":
+			if (isIos) {
+				return judgeIosPermissionPhotoLibrary(ifRequest)
+			} else {
+				return requestAndroidPermission("android.permission.READ_EXTERNAL_STORAGE", ifRequest)
+			}
+			break;
+		case "record":
+			if (isIos) {
+				return judgeIosPermissionRecord(ifRequest)
+			} else {
+				return requestAndroidPermission("android.permission.RECORD_AUDIO", ifRequest)
+			}
+			break;
+		case "contact":
+			if (isIos) {
+				return judgeIosPermissionContact(ifRequest)
+			} else {
+				return requestAndroidPermission("android.permission.READ_CONTACTS", ifRequest)
+			}
+			break;
+		case "call":
+			if (isIos) {
+				return Promise.resolve(true)
+			} else {
+				return requestAndroidPermission("android.permission.CALL_PHONE", ifRequest)
+			}
+			break;
+		case "push":
+			if (isIos) {
+				return judgeIosPermissionPush(ifRequest)
+			} else {
+				return Promise.resolve(true)
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+// 去设置
+function gotoAppPermissionSetting() {
+	if (isIos) {
+		var UIApplication = plus.ios.import("UIApplication");
+		var application2 = UIApplication.sharedApplication();
+		var NSURL2 = plus.ios.import("NSURL");
+		// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");		
+		var setting2 = NSURL2.URLWithString("app-settings:");
+		application2.openURL(setting2);
+
+		plus.ios.deleteObject(setting2);
+		plus.ios.deleteObject(NSURL2);
+		plus.ios.deleteObject(application2);
+	} else {
+		var Intent = plus.android.importClass("android.content.Intent");
+		var Settings = plus.android.importClass("android.provider.Settings");
+		var Uri = plus.android.importClass("android.net.Uri");
+		var mainActivity = plus.android.runtimeMainActivity();
+		var intent = new Intent();
+		intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+		var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
+		intent.setData(uri);
+		mainActivity.startActivity(intent);
+	}
+}
+
+// 检查是否开启了定位
+function checkSystemEnableLocation() {
+	if (isIos) {
+		var result = false;
+		var cllocationManger = plus.ios.import("CLLocationManager");
+		var result = cllocationManger.locationServicesEnabled();
+		plus.ios.deleteObject(cllocationManger);
+		return result;
+	} else {
+		var context = plus.android.importClass("android.content.Context");
+		var locationManager = plus.android.importClass("android.location.LocationManager");
+		var main = plus.android.runtimeMainActivity();
+		var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
+		var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
+		return result
+	}
+}
+
+export default {
+	getPermisson,
+	checkSystemEnableLocation
+}

+ 98 - 0
src/plugins/setting.plugins.js

@@ -0,0 +1,98 @@
+import { settingStores } from "@/store/modules/index";
+
+/**
+ * @获取缓存
+ */
+const formatSize = () => {
+  let currentSize = "";
+  plus.cache.calculate(function (size) {
+    let sizeCache = parseInt(size);
+    if (sizeCache == 0) {
+      currentSize = "0B";
+    } else if (sizeCache < 1024) {
+      currentSize = sizeCache + "B";
+    } else if (sizeCache < 1048576) {
+      currentSize = (sizeCache / 1024).toFixed(2) + "KB";
+    } else if (sizeCache < 1073741824) {
+      currentSize = (sizeCache / 1048576).toFixed(2) + "MB";
+    } else {
+      v = (sizeCache / 1073741824).toFixed(2) + "GB";
+    }
+
+    settingStores().currentSize = currentSize
+  });
+  return currentSize;
+};
+
+/**
+ * @清理缓存
+ */
+const clearCache = () => {
+  let os = plus.os.name;
+  if (os == "Android") {
+    let main = plus.android.runtimeMainActivity();
+    let sdRoot = main.getCacheDir();
+    let files = plus.android.invoke(sdRoot, "listFiles");
+    let len = files.length;
+
+    if (len <= 0) {
+      uni.showToast({
+        title: "暂无缓存可清理",
+        icon: "none",
+        duration: 2000,
+        mask: true,
+      });
+    }
+
+    for (let i = 0; i < len; i++) {
+      let filePath = "" + files[i]; // 没有找到合适的方法获取路径,这样写可以转成文件路径
+      plus.io.resolveLocalFileSystemURL(
+        filePath,
+        function (entry) {
+          if (entry.isDirectory) {
+            entry.removeRecursively(
+              function (entry) {
+                //递归删除其下的所有文件及子目录
+                uni.showToast({
+                  title: "缓存清理完成",
+                  duration: 2000,
+                  mask: true,
+                });
+                formatSize(); // 重新计算缓存
+              },
+              function (e) {
+                console.log(e.message);
+              }
+            );
+          } else {
+            entry.remove();
+          }
+        },
+        function (e) {
+          console.log("文件路径读取失败");
+        }
+      );
+    }
+  } else {
+    // ios
+    plus.cache.clear(function () {
+      uni.showToast({
+        title: "缓存清理完成",
+        duration: 2000,
+        mask: true,
+      });
+      formatSize();
+    });
+  }
+};
+
+export default {
+  // 设置角标
+  setBadge(value) {
+    //#ifdef APP-PLUS
+    plus.runtime.setBadgeNumber(value);
+    //#endif
+  },
+  formatSize,
+  clearCache,
+};

+ 32 - 0
src/plugins/tab.plugins.js

@@ -0,0 +1,32 @@
+export default {
+  // 关闭所有页面,打开到应用内的某个页面
+  reLaunch(url) {
+    return uni.reLaunch({
+      url: url
+    })
+  },
+  // 跳转到tabBar页面,并关闭其他所有非tabBar页面
+  switchTab(url) {
+    return uni.switchTab({
+      url: url
+    })
+  },
+  // 关闭当前页面,跳转到应用内的某个页面
+  redirectTo(url) {
+    return uni.redirectTo({
+      url: url
+    })
+  },
+  // 保留当前页面,跳转到应用内的某个页面
+  navigateTo(url) {
+    return uni.navigateTo({
+      url: url
+    })
+  },
+  // 关闭当前页面,返回上一页面或多级页面
+  navigateBack(value) {
+    return uni.navigateBack({
+      delta: value, //返回到需要执行方法的页面
+    })
+  }
+}

+ 217 - 0
src/plugins/time.plugins.js

@@ -0,0 +1,217 @@
+export default {
+    /**
+     * @param {时间处理(今日,昨日)} 
+     * @param {传入值} time 
+     * @param {是否携带时分秒} isTime
+     * @returns 
+     */
+    jktTimes(time, isTime) {
+        if (time == undefined) return "";
+        var today = new Date().getDate();//当前时间-日
+        var day = new Date(time).getDate();//传入时间-日
+
+        var newday = today - day
+        if (newday == 0) {
+            if (isTime == "否") {
+                return "今天";
+            } else {
+                var newTime = time.split(" ")[1]
+                var newTime2 = newTime.split(":")
+                return newTime2[0] + ":" + newTime2[1];
+            }
+        } else if (newday == 1) {
+            if (isTime == "否") {
+                return "昨天";
+            } else {
+                var newTime = time.split(" ")[1]
+                var newTime2 = newTime.split(":")
+                return newTime2[0] + ":" + newTime2[1];
+            }
+        } else {
+            var newTime = time.split(" ")
+
+            if (isTime == "否") {
+                return newTime[0]
+            } else {
+                var newTime2 = newTime[1].split(":")
+                return newTime2[0] + ":" + newTime2[1];
+            }
+        }
+    },
+    /**
+     * @指定获取开始时间结束时间
+     */
+    getDays(value) {
+        let oneDay = 24 * 60 * 60 * 1000;
+        let endTime = new Date(Date.now());
+        endTime = this.formatterDate(endTime, "yyyy-MM-dd");
+        let startTime = new Date(Date.now() - value * oneDay);
+        startTime = this.formatterDate(startTime, "yyyy-MM-dd");
+        const days = [startTime, endTime]
+        return days;
+    },
+    /**
+     * @处理公共日期格式
+     */
+    formatterDate(date, fmt) {
+        let nowDate = {
+            yyyy: date.getFullYear(), // 年
+            MM: date.getMonth() + 1, // 月份
+            dd: date.getDate(), //日
+            hh: date.getHours(),
+            mm: date.getMinutes(),
+            ss: date.getSeconds(),
+        };
+        if (/(y+)/.test(fmt)) {
+            fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
+        }
+        for (var k in nowDate) {
+            if (new RegExp("(" + k + ")").test(fmt)) {
+                fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? nowDate[k] : ("00" + nowDate[k]).substr(("" + nowDate[k]).length));
+            }
+        }
+        return fmt;
+    },
+    /**
+     * @获取年月日时分秒
+     * @returns
+     */
+    getFormatterDate(time3) {
+        var date = new Date(time3);
+        var Y = date.getFullYear() + "-";
+        var M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "-";
+        var D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " ";
+
+        var h = (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":";
+        var m = (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":";
+        var s = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
+        var strDate = Y + M + D + h + m + s;
+
+        return strDate;
+    },
+    /**
+     * @获取当前日期前一年的日期
+     */
+    getYearLast(dateStr) {
+        // 假设要获取的日期为dateStr,格式为"YYYY-MM-DD"
+        let date = new Date(dateStr);
+        let Y = date.getFullYear() - 1; // 获取前一年的年份
+        let M = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1; // 月
+        let D; // 日
+
+        var h = (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":";
+        var m = (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":";
+        var s = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
+
+        // 判断原日期的月份是否为2月份
+        if (date.getMonth() === 1) { // 2月份
+            // 判断前一年是否为闰年
+            if (Y % 4 === 0 && Y % 100 !== 0 || Y % 400 === 0) { // 闰年
+                D = Math.min(date.getDate(), 29); // 新日期的日期最大为29
+            } else { // 平年
+                D = Math.min(date.getDate(), 28); // 新日期的日期最大为28
+            }
+        } else { // 非2月份
+            D = date.getDate(); // 新日期的日期为原日期的日期
+        }
+        let newDateStr = Y + "-" + M + "-" + (D < 10 ? "0" + D : D) + " " + h + m + s; // 格式化日期字符串
+        return newDateStr
+    },
+    /**
+     * @统计两个日期之间的月份
+     */
+    getMonths(date1, date2) {
+        //用-分成数组
+        date1 = date1.split("-");
+        date2 = date2.split("-");
+        //获取年,月数
+        var year1 = parseInt(date1[0]),
+            month1 = parseInt(date1[1]),
+            year2 = parseInt(date2[0]),
+            month2 = parseInt(date2[1]),
+            //通过年,月差计算月份差
+            months = (year2 - year1) * 12 + (month2 - month1) + 1;
+        return months;
+    },
+    /**
+     * @获取年月日星期几
+     */
+    getDate() {
+        var date = new Date();
+        var year = date.getFullYear(); //  返回的是年份
+        var month = date.getMonth() + 1; //  返回的月份上个月的月份,记得+1才是当月
+        if (month < 10) {
+            month = "0" + month;
+        }
+        var dates = date.getDate(); //  返回的是几号
+        if (dates < 10) {
+            dates = "0" + dates;
+        }
+        var day = date.getDay(); //  周一返回的是1,周六是6,但是周日是0
+        var arr = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
+        return { year, month, dates, day, arr, };
+    },
+    /**
+     * @获取当前周开始结束日期
+     */
+    getCurrentWeekDate() {
+        const now = new Date();
+        const startOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay() + 1); // 本周开始日期
+        const endOfWeek = new Date(startOfWeek);
+        endOfWeek.setDate(endOfWeek.getDate() + 6); // 本周结束日期
+
+        const startDate = this.getFormatterDate(startOfWeek).split(' ')[0];
+        const endDate = this.getFormatterDate(endOfWeek).split(' ')[0];
+
+        return {
+            startDate: startDate,
+            endDate: endDate
+        }
+    },
+    /**
+     * @时间重组
+     * @param { 时间段 } chooseTime 
+     */
+    timeRestructuring(chooseTime) {
+        var startTime = "";
+        var endTime = "";
+        let start = chooseTime[0];
+        let end = chooseTime[1];
+
+        if (start == 0 && end == 0) {
+            startTime = "00:00";
+            endTime = "00:30";
+        }
+
+        if ((!start && start !== 0) || !end) {
+            startTime = "";
+            endTime = "";
+            return;
+        }
+
+        if (start % 1 === 0.5) {
+            start = (start - 0.5 < 10 ? "0" : "") + (start - 0.5) + ":30";
+        } else {
+            start = (start < 10 ? "0" : "") + start + ":00";
+        }
+
+        if (Number.isInteger(end)) {
+            if (end === 24) {
+                end = "23:30";
+            } else {
+                end = (end - 0.5 < 10 ? "0" : "") + end + ":00";
+            }
+        } else {
+            if (end === 24) {
+                end = "23:30";
+            } else {
+                end = (end < 10 ? "0" : "") + (end - 0.5) + ":30";
+            }
+        }
+
+        return {
+            startTime: start,
+            endTime: end,
+        };
+    }
+}

+ 8 - 0
src/store/getters.js

@@ -0,0 +1,8 @@
+const getters = {
+  token: state => state.user.token,
+  avatar: state => state.user.avatar,
+  name: state => state.user.name,
+  roles: state => state.user.roles,
+  permissions: state => state.user.permissions
+}
+export default getters

+ 8 - 0
src/store/index.js

@@ -0,0 +1,8 @@
+import { createPinia } from "pinia";
+import { createUnistorage } from '@/uni_modules/pinia-plugin-unistorage'
+
+const store = createPinia()
+
+store.use(createUnistorage())
+
+export default store

+ 129 - 0
src/store/modules/common.js

@@ -0,0 +1,129 @@
+import { defineStore } from "pinia";
+
+const commonStore = defineStore("common", {
+  state: () => ({
+    mapBool: 1, //1.高德 2.测绘院
+    streetTownList: [
+      { value: "", label: "全区" },
+      { value: "华漕镇", label: "华漕镇" },
+      { value: "虹桥镇", label: "虹桥镇" },
+      { value: "梅陇镇", label: "梅陇镇" },
+      { value: "七宝镇", label: "七宝镇" },
+      { value: "颛桥镇", label: "颛桥镇" },
+      { value: "马桥镇", label: "马桥镇" },
+      { value: "吴泾镇", label: "吴泾镇" },
+      { value: "浦江镇", label: "浦江镇" },
+      { value: "新虹街道", label: "新虹街道" },
+      { value: "古美路街道", label: "古美路街道" },
+      { value: "江川路街道", label: "江川路街道" },
+      { value: "浦锦街道", label: "浦锦街道" },
+      { value: "莘庄镇", label: "莘庄镇" },
+    ],
+    timeList: [
+      new Date().getFullYear() +
+      "-" +
+      (new Date().getMonth() + 1 < 10 ? "0" + (new Date().getMonth() + 1) : "" + (new Date().getMonth() + 1)) +
+      "-" +
+      (new Date().getDate() < 10 ? "0" + new Date().getDate() : new Date().getDate()) +
+      " 00:00:00",
+      new Date().getFullYear() +
+      "-" +
+      (new Date().getMonth() + 1 < 10 ? "0" + (new Date().getMonth() + 1) : "" + (new Date().getMonth() + 1)) +
+      "-" +
+      (new Date().getDate() < 10 ? "0" + new Date().getDate() : new Date().getDate()) +
+      " 23:59:59",
+    ],
+
+    mhjz: [
+      { streetTown: "全区", id: 12, x: 121.4175597, y: 31.119248 },
+      { streetTown: "江川路街道", id: 1201, x: 121.399126538181, y: 31.0099719391863 },
+      { streetTown: "新虹街道", id: 1217, x: 121.319329296294, y: 31.1983901916889 },
+      { streetTown: "古美路街道", id: 1206, x: 121.388451866936, y: 31.1478233480159 },
+      { streetTown: "浦锦街道", id: 1218, x: 121.483929120352, y: 31.089967318558 },
+      { streetTown: "浦江镇", id: 1215, x: 121.524058543447, y: 31.0540039472667 },
+      { streetTown: "吴泾镇", id: 1213, x: 121.454076463728, y: 31.04860402113 },
+      { streetTown: "马桥镇", id: 1214, x: 121.352680027718, y: 31.0213512298508 },
+      { streetTown: "颛桥镇", id: 1209, x: 121.40607138504, y: 31.0612972443508 },
+      { streetTown: "莘庄镇", id: 1207, x: 121.37064864047, y: 31.1154549548722 },
+      { streetTown: "梅陇镇", id: 1212, x: 121.421346814491, y: 31.1069718313722 },
+      { streetTown: "七宝镇", id: 1208, x: 121.350366186317, y: 31.1553292680362 },
+      { streetTown: "虹桥镇", id: 1211, x: 121.37956256207, y: 31.1806219953212 },
+      { streetTown: "华漕镇", id: 1210, x: 121.277541517147, y: 31.2289121171624 },
+      { streetTown: "莘庄工业区", id: 1216, x: 121.376508452784, y: 31.0678185611843 },
+    ],
+
+    deviceDetailsArray: {}, //设备详情页面-数据存储
+
+    facilitiesGatherType: "", //设施采集类型-数据存储
+    facilitiesGatherArray: {
+      department: "", //所属部门
+      facilityType: "", //设施类型
+      facilityTypeName: "", //设施类型名称
+      facilityName: "", //设施名称
+      status: "0", //设施状态
+      address: "", //设施地址
+      longitude: "", //经度
+      latitude: "", //纬度
+      province: "", //省
+      city: "", //市
+      area: "", //区(县)
+      facilityAddress: "", //详细地址
+      streetTown: null, //所属街镇
+      gpsAreas: [], //经纬度集合
+      contact: "", //联系人
+      contactPhone: "", //联系方式
+      imagesUrl: "", //图⽚地址URL
+      facilityDesc: "", //备注
+      extendData: [], //扩展属性
+      recordPictureList: [],
+      typeGuise: "", //点线面类型
+    }, //设施采集页面-数据存储
+  }),
+  actions: {
+    /**
+     * @计算当前坐标和G点坐标的偏差值
+     * @returns
+     */
+    caculateLL(lat1, lng1, lat2, lng2) {
+      var radLat1 = (lat1 * Math.PI) / 180.0;
+      var radLat2 = (lat2 * Math.PI) / 180.0;
+      var a = radLat1 - radLat2;
+      var b = (lng1 * Math.PI) / 180.0 - (lng2 * Math.PI) / 180.0;
+      var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
+      s = s * 6378.137;
+      s = Math.round(s * 10000) / 10;
+      console.log(s);
+      return s;
+    },
+    /**
+     * @公共添加服务器列表
+     */
+    setServeList(linkUrl, content) {
+      let serveList = uni.getStorageSync("serveList");
+      if (serveList.length <= 0) {
+        uni.setStorageSync("serveList", [
+          {
+            radiolist: [
+              {
+                id: 1,
+                linkUrl: linkUrl,
+                content: content,
+              },
+            ],
+            radiovalue: 1,
+          },
+        ]);
+      } else {
+        serveList[0].radiolist.push({
+          id: serveList[0].radiolist[serveList[0].radiolist.length - 1].id + 1,
+          linkUrl: linkUrl,
+          content: content,
+        });
+
+        uni.setStorageSync("serveList", serveList);
+      }
+    },
+  },
+});
+
+export default commonStore;

+ 424 - 0
src/store/modules/control.js

@@ -0,0 +1,424 @@
+import { defineStore } from "pinia";
+import { doorApi } from "@/api/business/door.js";
+import { faceApi } from "@/api/business/face.js";
+import { meetingApi, signOnOut } from "@/api/business/meeting.js";
+import { getToken, setToken, removeToken } from "@/utils/auth";
+import dayjs from 'dayjs'
+import config from "@/config";
+import tab from "@/plugins/tab.plugins";
+import nfc from "@/plugins/nfc.plugins.js";
+import modal from "@/plugins/modal.plugins.js";
+import keyListen from "@/plugins/keyListen.plugins.js";
+import permission from "@/plugins/permission.plugins.js";
+
+const controlStore = defineStore("control", {
+    state: () => ({
+        pageFunction: [], //被包含的功能
+        isClicked: false, //按钮是否被点击
+        form: {
+            linkUrl: "",
+            port: "",
+            domain: undefined,
+            doorList: {
+                id: undefined,
+                name: undefined,
+            },
+        },
+        modal: {
+            show: false,
+        },
+        picker: {
+            show: false,
+            title: "",
+            list: [[]],
+            defaultIndex: 0,
+        },
+        subsection: {
+            list: ["服务器配置", "其它配置", "系统设置"],
+            value: 0,
+        },
+
+        doooList: [],
+        meetingDoorList: [],
+        meetingRoomList: [],
+        meetingTimeList: [],
+        meetingReservaList: {
+            dataAll: {},
+            thisVenueData: [],
+            thisVenueTime: {},
+            nextSceneData: [],
+            nextSceneTime: {},
+            timeList: [],
+        },
+    }),
+    actions: {
+        /**
+         * @初始化缓存数据
+         */
+        initData() {
+            var that = this
+            var storage = uni.getStorageSync("storage_face");
+            if (storage) {
+                config.baseUrl = "http://" + storage.domain + "/prod-api";
+                that.form.domain = storage.domain;
+                that.form.linkUrl = storage.linkUrl.indexOf(":") != -1 ? storage.linkUrl.split(":")[0] : storage.linkUrl;
+                that.form.port = storage.port ? storage.port : "";
+                that.form.meetingId = storage.meetingId || undefined;
+                that.form.meetingName = storage.meetingName || undefined;
+                that.form.doorList = {
+                    id: storage.doorList.id || undefined,
+                    name: storage.doorList.name || undefined,
+                }
+            }
+        },
+        /**
+         * @初始化摄像头
+         */
+        initCamera() {
+            var that = this
+            //#ifdef APP-PLUS
+            permission.getPermisson("camera").then((res) => {
+                res ? that.handleChildren({ funcName: "开启摄像头", data: {} }) : "";
+            });
+            //#endif
+        },
+        /**
+         * @初始化NFC
+         */
+        initNfc() {
+            var that = this
+            //#ifdef APP-PLUS
+            nfc.initNFC();
+            nfc.readNFC().then((e) => {
+                that.openDoor();
+                that.initNfc();
+            });
+            //#endif
+        },
+        /**
+         * @初始化事件
+         */
+        initHandle() {
+            this.handleChildren({ funcName: "初始化事件", data: {} })
+        },
+        /**
+         * @弹窗确定按钮事件
+         */
+        modalConfirm(rules) {
+            if (!this.form.linkUrl) {
+                modal.msg("请输入链接地址");
+                return;
+            }
+
+            if (!/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}(?:\.[a-zA-Z0-9]{2,})+$/.test(this.form.linkUrl)) {
+                modal.msg("请输入正确的链接地址");
+                return;
+            }
+
+            if (!this.form.meetingName && this.pageFunction.includes('会议')) {
+                modal.msg("请选择绑定会议室");
+                return;
+            }
+
+            if (!this.form.doorList.name && this.pageFunction.includes('门禁')) {
+                modal.msg("请选择绑定门禁");
+                return;
+            }
+
+            uni.setStorageSync("storage_face", this.form);
+            this.modalClose();
+        },
+        /**
+         * @弹窗退出按钮事件
+         */
+        modalCancel() {
+            this.modal.show = false;
+            //#ifdef APP-PLUS
+            keyListen.quitApp();
+            //#endif
+        },
+        /**
+         * @弹窗关闭事件
+         */
+        modalClose() {
+            this.handleChildren({ funcName: "开启摄像头", data: {} });
+            this.modal.show = false;
+        },
+        /**
+         * @section回调事件
+         */
+        sectionChange(e) {
+            if (e == 1) {
+                if (!this.form.linkUrl) {
+                    modal.msg("请输入链接地址");
+                    return;
+                }
+
+                if (!/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}(?:\.[a-zA-Z0-9]{2,})+$/.test(this.form.linkUrl)) {
+                    modal.msg("请输入正确的链接地址");
+                    return;
+                }
+
+                var domain = "";
+                if (this.form.linkUrl) {
+                    domain = this.form.linkUrl;
+                    if (this.form.port) {
+                        domain += ":" + this.form.port;
+                    }
+                }
+
+                this.form.domain = domain;
+                config.baseUrl = "http://" + this.form.domain + "/prod-api";
+                if (this.pageFunction.includes('会议')) {
+                    this.getMeetingRoomList();
+                }
+                if (this.pageFunction.includes('门禁')) {
+                    this.getdoorList();
+                }
+            }
+
+            this.subsection.value = e;
+        },
+
+        /**
+         * @action弹出框点击事件
+         */
+        handlePicker(value, index, ind) {
+            if (value == "绑定会议室") {
+                this.picker.title = "绑定会议室";
+                this.picker.list = [this.meetingRoomList];
+                this.picker.defaultIndex = 0;
+            } else if (value == "绑定门禁") {
+                this.picker.title = "绑定门禁";
+                this.picker.list = [this.doooList];
+                this.picker.defaultIndex = 0;
+            }
+            this.picker.show = true;
+        },
+
+        /**
+         * @action弹出框选择事件
+         */
+        pickerConfirm(e) {
+            if (this.picker.title == "绑定会议室") {
+                this.form.meetingId = e.value[0].value;
+                this.form.meetingName = e.value[0].name;
+            } else if (this.picker.title == "绑定门禁") {
+                this.form.doorList.id = e.value[0].value;
+                this.form.doorList.name = e.value[0].name;
+            }
+            this.picker.show = false;
+        },
+
+        /**
+         * @会议室下拉列表
+         */
+        getMeetingRoomList() {
+            var that = this
+            that.meetingRoomList = [];
+            meetingApi()
+                .GetMeetingRoomList({
+                    domain: that.form.domain,
+                })
+                .then((requset) => {
+                    if (requset.data.length > 0) {
+                        requset.data.forEach((e) => {
+                            that.meetingRoomList.push({
+                                value: e.roomId,
+                                name: e.roomName,
+                            });
+                        });
+                    }
+                });
+        },
+        /**
+         * @门禁下拉列表
+         */
+        getdoorList() {
+            doorApi()
+                .Select({
+                    current: 1, //页数
+                    size: 2000, //条数
+                    domain: this.form.domain, //域名
+                })
+                .then((requset) => {
+                    if (requset.data.records.length > 0) {
+                        requset.data.records.forEach((e) => {
+                            this.doooList.push({
+                                value: e.deviceUuid,
+                                name: e.deviceName,
+                            });
+                        });
+                    }
+                });
+        },
+        /**
+         * @门禁开门
+         */
+        openDoor(event) {
+            var that = this
+
+            if (!this.form.doorList.name) {
+                modal.msg("请先绑定门禁!");
+                return;
+            }
+
+            if (this.isClicked) {
+                modal.msg("请勿重复点击!");
+                return;
+            } else {
+                this.isClicked = true;
+            }
+
+            doorApi()
+                .control({
+                    domain: !getToken() ? this.form.domain : undefined, //域名
+                    userId: !getToken() ? event.userId : undefined,
+                    userName: !getToken() ? event.userName : undefined,
+                    productCode: "502_USKY",
+                    deviceUuid: !getToken() ? this.form.doorList.id : event.deviceUuid,
+                    commandCode: "door_onoff",
+                    commandValue: 1,
+                })
+                .then((item) => {
+                    modal.msg("开门成功");
+                    setTimeout(() => {
+                        this.isClicked = false;
+                    }, 2000);
+                    that.insertDoorRecord(event, "成功");
+                })
+                .catch((err) => {
+                    console.log(err);
+                    setTimeout(() => {
+                        this.isClicked = false;
+                    }, 2000);
+                    that.insertDoorRecord(event, "失败:" + err);
+                });
+        },
+        /**
+         * @新增开门记录
+         */
+        insertDoorRecord(event, msg) {
+            doorApi()
+                .RecordInsert({
+                    domain: !getToken() ? this.form.domain : undefined, //域名
+                    userName: event.userName, //用户姓名
+                    deviceUuid: !getToken() ? this.form.doorList.id : event.deviceUuid,//设备Uuid
+                    passType: 3, //通行方式(1、人脸 2、刷卡 3、手机)
+                    passTime: dayjs().format("YYYY-MM-DDTHH:mm:ss"), //通行时间
+                    passResult: msg, //通行结果
+                })
+                .then((item) => {
+                    console.log(msg);
+                })
+                .catch((err) => {
+                    console.log(err);
+                });
+        },
+        /**
+         * @人脸验证
+         */
+        faceVerify(imageBase) {
+            var that = this
+            faceApi()
+                .faceVef({
+                    domain: that.form.domain,
+                    imageBase: imageBase,
+                })
+                .then((item) => {
+                    if (item.data.code === 200 || item.data.code === 201) {
+                        item.data.userName = item.data.faceName;
+                        if (that.pageFunction.includes('会议')) {
+                            that.meetingVerify(item.data);
+                        } else {
+                            that.openDoor(item.data);
+                        }
+                    }
+
+                    if (item.data.msg != "人脸验证接口返回异常") {
+                        modal.msg(item.data.msg);
+                    }
+
+                    that.handleChildren({ funcName: "人脸冷却", data: {} });
+                })
+                .catch((err) => {
+                    that.handleChildren({ funcName: "人脸冷却", data: {} });
+                });
+        },
+
+        /**
+         * @会议验证
+         */
+        meetingVerify(event) {
+            var that = this
+            if (that.meetingReservaList.thisVenueData.length > 0) {
+                meetingApi()
+                    .Attendee({
+                        domain: this.form.domain,
+                        meetingId: this.meetingReservaList.thisVenueData[0].meetingId,
+                        userId: event.userId,
+                        userName: event.userName,
+                    })
+                    .then((item) => {
+                        if (item.data.status == "1") {
+                            that.openDoor(event);
+                            that.meetingSign(event);
+                        }
+                        modal.msg(item.data.msg);
+                    });
+            }
+        },
+        /**
+         * @会议签到
+         */
+        meetingSign(event) {
+            signOnOut({
+                domain: this.form.domain,
+                meetingId: this.meetingReservaList.thisVenueData[0].meetingId,
+                userId: event.userId, //参会人Id
+                mothodType: 0, //签到签退类别(0.签到 1.签退)
+                signType: 1, //签到签退方式(0.人工 1.人脸)
+            }).then((item) => { });
+        },
+        /**
+         * @解析父页面传回的数据
+         */
+        analysisData(event) {
+            if ("funcName" in event) {
+                if (event.funcName == "打开配置") {
+                    this.handleChildren({ funcName: "关闭摄像头", data: {} });
+
+
+                    tab.navigateTo("/pages/door/setting/index")
+                    // this.modal.show = true;
+                } else if (event.funcName == "人脸识别") {
+                    this.faceVerify(event.data.imageBase);
+                } else if (event.funcName == "点击开门") {
+                    this.openDoor({
+                        userId: 99,
+                        userName: "方惠圣",
+                    });
+                }
+            }
+        },
+        /**
+         * @向子页面发送数据
+         */
+        handleChildren(data) {
+            // #ifdef APP-PLUS
+            var pages = getCurrentPages();
+            var currentWebview = pages[pages.length - 1].$getAppWebview();
+            var wv = currentWebview.children()[0];
+            wv.evalJS(`receiveData(${JSON.stringify(data)})`);
+            // #endif
+
+            // #ifdef H5
+            var iframe = document.getElementById("faceView");
+            iframe.contentWindow.postMessage(data, "*");
+            // #endif
+        }
+
+    },
+});
+
+export default controlStore;

+ 11 - 0
src/store/modules/index.js

@@ -0,0 +1,11 @@
+import commonStores from "@/store/modules/common.js";
+import settingStores from "@/store/modules/setting.js";
+import systemStores from "@/store/modules/system.js";
+import controlStores from "@/store/modules/control.js";
+
+export {
+    commonStores,
+    settingStores,
+    systemStores,
+    controlStores
+};

+ 230 - 0
src/store/modules/setting.js

@@ -0,0 +1,230 @@
+import { defineStore } from "pinia";
+import { storage } from "@/utils/storage";
+import { getToken, setToken, removeToken } from "@/utils/auth";
+import wx from 'weixin-js-sdk'
+// 接口引用
+import { checkUpdates } from "@/api/system/setting";
+// 组件引用
+import config from "@/config";
+import tab from "@/plugins/tab.plugins.js";
+import modal from "@/plugins/modal.plugins.js";
+import common from "@/plugins/common.plugins.js";
+import setting from "@/plugins/setting.plugins.js";
+import constData from "@/plugins/constData.plugins.js";
+
+const settingStores = defineStore("storage-setting", {
+    state: () => ({
+        pushClientId: undefined,//应用消息推送cid
+        currentSize: "",//APP缓存
+        barHeight: 0,//微信小程序顶部安全距离
+        webViewHeight: "",//webView整体高度-铺满
+        StatusBarHeight: "",//APP顶部安全距离
+        tabBarHeight: "",//APP底部安全距离
+        deviceList: {
+            networkType: null, //网络类型
+            deviceBrand: null, //设备品牌
+            deviceId: null, //设备编号
+            deviceModel: null, //设备型号
+            userName: null, //用户账号
+            longitude: "", //经度
+            latitude: "", //纬度
+            deviceStatus: 0,//设备状态
+        },
+
+        themeColor: storage.get("themeColor"),//主题
+        nfcWaiting: "请将手机靠近NFC标签", //nfc读取页面-提示文字-数据存储
+        webSocket: null,//巡检webSocket
+    }),
+    actions: {
+        /**
+         * @动态获取屏幕头部高度
+         */
+        systemHeightTop() {
+            let systemInfo = uni.getSystemInfoSync();
+            this.webViewHeight = systemInfo.safeArea.bottom  //高度+安全区域的高度
+            this.StatusBarHeight = systemInfo.statusBarHeight + "px"
+            this.tabBarHeight = common.isWechatMp() ? "0px" : systemInfo.screenHeight - systemInfo.safeArea.bottom + "px"
+
+            //#ifdef MP
+            let custom = wx.getMenuButtonBoundingClientRect();
+            let CustomBar = custom.bottom + custom.top - systemInfo.statusBarHeight;
+            this.barHeight = CustomBar || 0
+            //#endif
+        },
+
+        /**
+         * @初始化WebSocket
+         */
+        initWebSocket() {
+            var _this = this
+
+            const systemInfo = uni.getSystemInfoSync();
+            _this.webSocket = uni.connectSocket({
+                // url: `wss://gateway.usky.cn/wss${systemInfo.deviceId},${new Date().getTime()}`,
+                url: `ws://120.26.164.249:9891/webSocketPatrol/${systemInfo.deviceId},${new Date().getTime()}`,
+            });
+        },
+        /**
+         * @开启WebSocket
+         */
+        openWebSocket() {
+            var _this = this
+
+            if (uni.getStorageSync("serveUrl").indexOf("xf.usky.cn:13212") == -1) {
+                return false
+            }
+
+            _this.initWebSocket()
+
+            uni.onSocketOpen(function (res) {
+                console.log('WebSocket连接已打开!', res);
+            });
+
+            uni.onSocketMessage(function (res) {
+                console.log('收到服务器内容:' + res.data);
+                _this.baseAppInfo();
+            });
+
+            uni.onSocketClose(function (res) {
+                console.log('WebSocket 已关闭!', res);
+                _this.closeWebSocket();
+                _this.initWebSocket()
+            });
+
+            uni.onSocketError(function (res) {
+                console.log('WebSocket连接打开失败,请检查!');
+                _this.initWebSocket()
+            });
+        },
+        /**
+         * @关闭WebSocket
+         */
+        closeWebSocket() {
+            uni.closeSocket();
+        },
+        /**
+         * @清理缓存
+         */
+        handleCleanTmp() {
+            // #ifdef H5
+            uni.showToast({
+                title: "缓存清理完成",
+                duration: 2000,
+                mask: true,
+            });
+            // #endif
+
+            // #ifdef APP-PLUS
+            setting.clearCache();//清除应用访问缓存
+            // #endif
+        },
+        /**
+         * @检查更新
+         */
+        handleToUpgrade({ success, error }) {
+            let platform = uni.getSystemInfoSync().platform; //手机平台
+            let data = {
+                _api_key: "fba7440cd37400b6ff46e303896af4df",
+                appKey: "",
+                buildVersion: config.appInfo.version,
+            };
+
+            // 综合智慧云
+            if (config.appInfo.appid === "__UNI__36DE3A0") {
+                data.appKey = platform === "android" ? "cbd3508235d03365f4253f6aae6b68ab" : "4b858fce6367652f5c0959a0444a4bea"
+            }
+            // 智能巡更
+            else if (config.appInfo.appid === "__UNI__BF1A1F0") {
+                data.appKey = platform === "android" ? "d67e04ab2f4b8a1fa01a773692fc666c" : "d67e04ab2f4b8a1fa01a773692fc666c"
+            }
+            // 智能会议
+            else if (config.appInfo.appid === "__UNI__F3963F8") {
+                data.appKey = platform === "android" ? "455032139442b1fe00b75b231474792e" : "455032139442b1fe00b75b231474792e"
+            }
+            // 智能门禁
+            else if (config.appInfo.appid === "__UNI__8D6E9FD") {
+                data.appKey = platform === "android" ? "625265d978931967076754b784b676fe" : "625265d978931967076754b784b676fe"
+            }
+
+            modal.loading("加载中");
+            checkUpdates(data)
+                .then((res) => {
+                    if (res.code == 0 && res.data.buildHaveNewVersion == true) {
+                        //安卓手机弹窗升级
+                        if (platform === "android") {
+                            success(res)
+                        }
+                        //IOS无法在线升级提示到商店下载
+                        else {
+                            //#ifdef APP-PLUS 
+                            let appleId = 6449016600
+                            plus.runtime.launchApplication({
+                                action: `itms-apps://itunes.apple.com/cn/app/id${appleId}`,
+                            })
+                            //#endif
+                        }
+                    } else {
+                        modal.msg("您的软件版本已是最新");
+                    }
+                    modal.closeLoading();
+                }).catch((err) => {
+                    modal.closeLoading();
+                });
+        },
+        /**
+         * @初始化默认主题
+         */
+        initThemeColor(themeColor) {
+            if (!themeColor) {
+                this.themeColor = constData.themeList[0];
+                storage.set("themeColor", constData.themeList[0]);
+            } else {
+                this.themeColor = themeColor;
+                storage.set("themeColor", themeColor);
+            }
+        },
+        /**
+         * @系统主题颜色
+         */
+        systemThemeColor(type) {
+            setTimeout(() => {
+                if (type.includes(2)) {
+                    uni.setTabBarStyle({
+                        selectedColor: this.themeColor.color,
+                        borderStyle: "white",
+                    });
+
+                    this.themeColor.tabList.forEach((selectedIconPath, index) => {
+                        uni.setTabBarItem({
+                            index,
+                            selectedIconPath,
+                        });
+                    });
+                }
+
+                if (type.includes(1)) {
+                    uni.setNavigationBarColor({
+                        frontColor: "#ffffff",
+                        backgroundColor: this.themeColor.color,
+                        animation: {
+                            duration: 400,
+                            timingFunc: "easeIn",
+                        },
+                    });
+                }
+            }, 0);
+        },
+        SET_FINGERPRINT(array) {
+            this.fingerprintUserList = array;
+            storage.set("fingerprintUserList", array);
+        },
+        SET_STORAGE_OBJECT_KEYS(LIST) {
+            Object.keys(LIST).forEach(function (key) {
+                storage.set(key, LIST[key]);
+                settingStores()[key] = LIST[key];
+            });
+        },
+    },
+});
+
+export default settingStores;

+ 21 - 0
src/store/modules/system.js

@@ -0,0 +1,21 @@
+// 组件引用
+import { defineStore } from "pinia";
+// 接口引用
+// 公共方法引用
+import { storage } from "@/utils/storage";
+
+const systemStores = defineStore(`storage-system`, {
+    state: () => ({
+        homeList: storage.get("homeList"),
+        mallList: {
+            activeUserList: []
+        },
+        meetingList: {
+            form: {}
+        }
+    }),
+    unistorage: true,
+    actions: {},
+});
+
+export default systemStores;

+ 67 - 0
src/uni.scss

@@ -0,0 +1,67 @@
+/**
+ * uni-app内置的常用样式变量
+ */
+
+/* 颜色变量 */
+@import "@/uni_modules/uview-plus/theme.scss";
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color: #333; //基本色
+$uni-text-color-inverse: #fff; //反色
+$uni-text-color-grey: #999; //辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable: #c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color: #ffffff;
+$uni-bg-color-grey: #f8f8f8;
+$uni-bg-color-hover: #f1f1f1; //点击状态颜色
+$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color: #e5e5e5;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm: 12px;
+$uni-font-size-base: 14px;
+$uni-font-size-lg: 16px;
+
+/* 图片尺寸 */
+$uni-img-size-sm: 20px;
+$uni-img-size-base: 26px;
+$uni-img-size-lg: 40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2c405a; // 文章标题颜色
+$uni-font-size-title: 20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle: 26px;
+$uni-color-paragraph: #3f536e; // 文章段落颜色
+$uni-font-size-paragraph: 15px;

+ 13 - 0
src/utils/auth.js

@@ -0,0 +1,13 @@
+const TokenKey = 'App-Token'
+
+export function getToken() {
+  return uni.getStorageSync(TokenKey)
+}
+
+export function setToken(token) {
+  return uni.setStorageSync(TokenKey, token)
+}
+
+export function removeToken() {
+  return uni.removeStorageSync(TokenKey)
+}

+ 17 - 0
src/utils/dict.js

@@ -0,0 +1,17 @@
+import { getDicts } from '@/api/system/dict.js'
+import { ref, toRefs } from "vue";
+/**
+ * 获取字典数据
+ */
+export function useDict(...args) {
+    const res = ref({});
+    return (() => {
+        args.forEach((d, index) => {
+            res.value[d] = [];
+            getDicts(d).then(resp => {
+                res.value[d] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
+            })
+        })
+        return toRefs(res.value);
+    })()
+}

+ 48 - 0
src/utils/hideHead.js

@@ -0,0 +1,48 @@
+// 页面白名单
+const whiteList = [
+	"pages/index",//登录
+	"pages/info",//消息
+];
+
+export default {
+	mounted() {
+		if (this.isWeiXinBrowser() || this.isQQBrowser()) {
+			this.navTitle()
+		}
+	},
+	methods: {
+		isWeiXinBrowser() { //判断是否为微信
+			let ua = navigator?.userAgent.toLowerCase()
+			if (ua) {
+				return ua.indexOf('micromessenger') != -1
+			}
+		},
+		isQQBrowser() { //判断是否为qq
+			var ua = navigator?.userAgent.toLowerCase()
+			if (ua?.match(/QQ/i) == "qq") {
+				return true
+			} else {
+				return false
+			}
+		},
+		navTitle() {
+			this.$nextTick(() => {
+				const pages = getCurrentPages(); // 获取当前页面栈
+				const currentPage = pages[pages.length - 1]; // 最后一个元素即为当前页面
+
+				if (whiteList.includes(currentPage?.route)) {
+					return
+				}
+
+				let navTitleDom = document.getElementsByTagName('uni-page-head')
+				if (navTitleDom.length) {
+					navTitleDom[0].style.display = 'none'
+				}
+				let navTitleDom1 = document.querySelector('.u-navbar')
+				if (navTitleDom1) {
+					navTitleDom1.style.display = 'none'
+				}
+			})
+		},
+	}
+}

+ 38 - 0
src/utils/jsencrypt.js

@@ -0,0 +1,38 @@
+import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
+
+// 密钥对生成 http://web.chacuo.net/netrsakeypair
+
+const publicKey = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxPWP0HTBE9vEeM34Qx03U8oVm
+C6xIqWPRuI5t8J0zEDQudAgXKPjy8E0Q3cX800UNBTx2gUfRRNrONqALKDnJ1SE6
+qCUDeXOez8sa95GQ9d4BX7pSjZLrPfnCBTBtb5LGkY5zmlmtpG2AV9eJr+kQqhs/
+r0c4njwaDjVG4kF3ZQIDAQAB`
+
+const privateKey = `MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALE9Y/QdMET28R4z
+fhDHTdTyhWYLrEipY9G4jm3wnTMQNC50CBco+PLwTRDdxfzTRQ0FPHaBR9FE2s42
+oAsoOcnVITqoJQN5c57Pyxr3kZD13gFfulKNkus9+cIFMG1vksaRjnOaWa2kbYBX
+14mv6RCqGz+vRziePBoONUbiQXdlAgMBAAECgYBjSDdAXEVYrFdeiouYjHwdyAhP
+pERKo5BFvzMRhJIaM353cwnBJ3NkapVQ2Fn6iMIKTB+VZk+7eu1yTAkUluDfLowd
+REZS4ipOBY5UuNnjbXmSOoUQw6vRnox0X4x6S1vd4FBHgpVe1VkiE7Nz5U7Clyd5
+yw2P1lHwMyB/guAH4QJBAN3dGkMASj0jm23maHOfehp/zlACB8HpMKuV4z/bEg45
+nC9Hw5NloUHrXdzEXP1+S46MCH2THflxDVYtnZTRLO0CQQDMgp3Jrn7kkKtNceZF
+R08hLbVmfNlatgONgFJ5JnR+GTQ6o2gwM6SLyoBkfAIiEDpr6c6nBXTU09GOYxBk
++h1ZAkB32pXxVBrG5JF20V3j+GcyIZEGz9H5A0xzpUlambIrVRv2vsH8wo5W2hue
+w8Woe629mBCOJgevVU9rGsFiP44RAkEApbTYAQjAjJakFpZJjKzg8vNEXoye2R9N
+9aOaL8v27A2kAjdRPm050IL+UW0hlVQs4i+KYE7NgX03+PVP3WHD0QJBANLo4PRw
+7Y+dLPAzuazsD3/5SYaSh+KSD/+tVbc6CFvLyfFUKp/a4PzzvGaLo/Ky/ffOY5k0
+hmavbHCKcg+r+hg=`
+
+
+// 加密
+export function encrypt(txt) {
+    const encryptor = new JSEncrypt()
+    encryptor.setPublicKey(publicKey) // 设置公钥
+    return encryptor.encrypt(txt) // 对数据进行加密
+}
+
+// 解密
+export function decrypt(txt) {
+    const encryptor = new JSEncrypt()
+    encryptor.setPrivateKey(privateKey) // 设置私钥
+    return encryptor.decrypt(txt) // 对数据进行解密
+}

+ 57 - 0
src/utils/jssdk.js

@@ -0,0 +1,57 @@
+import config from "@/config";
+
+//#ifdef H5
+const jweixin = import('weixin-js-sdk')
+//#endif
+export function configWeiXin(callback) {
+	const url = ""
+
+	//#ifdef H5
+	if (window.location.host) {
+		url = window.location.host;
+	}
+
+	if (uni.getStorageSync("serveUrl")) {
+		url = uni.getStorageSync("serveUrl");
+	}
+	//#endif
+
+	uni.request({
+		url: "https://qhome.usky.cn/USKYZHAF/sign.php",
+		header: {
+			'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
+		},
+		data: {
+			"url": url
+		},
+		method: 'GET',
+		success: (res) => {
+			// console.log('请求的签名的参数')
+			// console.log(res)
+
+			let apiList = [ // 可能需要用到的能力 需要啥就写啥。多写也没有坏处
+				'openLocation',
+				'getLocation',
+				'scanQRCode'
+			];
+			let info = {
+				debug: false, // 调试,发布的时候改为false
+				appId: res.data.appid,
+				nonceStr: res.data.nonceStr,
+				timestamp: parseInt(res.data.timestamp),
+				signature: res.data.sha_str,
+				jsApiList: apiList
+			};
+
+			jweixin.config(info);
+			jweixin.error(err => {
+				alert('config fail:', err);
+				return
+			});
+
+			jweixin.ready(res => {
+				if (callback) callback(jweixin);
+			});
+		}
+	});
+}

+ 132 - 0
src/utils/permission.js

@@ -0,0 +1,132 @@
+import store from '@/store'
+
+/**
+ * 字符权限校验
+ * @param {Array} value 校验值
+ * @returns {Boolean}
+ */
+function checkPermi(value) {
+  if (value && value instanceof Array && value.length > 0) {
+    const permissions = store.getters && store.getters.permissions
+    const permissionDatas = value
+    const all_permission = "*:*:*"
+
+    const hasPermission = permissions.some(permission => {
+      return all_permission === permission || permissionDatas.includes(permission)
+    })
+
+    if (!hasPermission) {
+      return false
+    }
+    return true
+  } else {
+    console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`)
+    return false
+  }
+}
+
+/**
+ * 角色权限校验
+ * @param {Array} value 校验值
+ * @returns {Boolean}
+ */
+function checkRole(value) {
+  if (value && value instanceof Array && value.length > 0) {
+    const roles = store.getters && store.getters.roles
+    const permissionRoles = value
+    const super_admin = "admin"
+
+    const hasRole = roles.some(role => {
+      return super_admin === role || permissionRoles.includes(role)
+    })
+
+    if (!hasRole) {
+      return false
+    }
+    return true
+  } else {
+    console.error(`need roles! Like checkRole="['admin','editor']"`)
+    return false
+  }
+}
+
+/// null = 未请求,1 = 已允许,0 = 拒绝|受限, 2 = 系统未开启
+function requestIOS(permissionID) {
+  return new Promise((resolve, reject) => {
+    var result = 0;
+    var cllocationManger = plus.ios.import(permissionID);
+    var enable = cllocationManger.locationServicesEnabled();
+    var status = cllocationManger.authorizationStatus();
+    if (!enable) {
+      result = 2;
+    } else if (status === 0) {
+      result = null;
+    } else if (status === 3 || status === 4) {
+      result = 1;
+    } else {
+      result = 0;
+    }
+    plus.ios.deleteObject(cllocationManger);
+    resolve(result);
+  });
+}
+
+function requestAndroid(permissionID) {
+  return new Promise((resolve, reject) => {
+    var result = 0;
+    plus.android.requestPermissions([permissionID], (resultObj) => {
+      for (var i = 0; i < resultObj.granted.length; i++) {
+        var grantedPermission = resultObj.granted[i];
+        console.log('已获取的权限:');
+        result = 1
+      }
+      for (var i = 0; i < resultObj.deniedPresent.length; i++) {
+        var deniedPresentPermission = resultObj.deniedPresent[i];
+        console.log('拒绝本次申请的权限:');
+        result = 0
+      }
+      for (var i = 0; i < resultObj.deniedAlways.length; i++) {
+        var deniedAlwaysPermission = resultObj.deniedAlways[i];
+        console.log('永久拒绝申请的权限:');
+        result = -1
+      }
+      resolve(result);
+    });
+  });
+}
+
+function gotoAppSetting() {
+  if (isIOS()) {
+    var UIApplication = plus.ios.import("UIApplication");
+    var application2 = UIApplication.sharedApplication();
+    var NSURL2 = plus.ios.import("NSURL");
+    var setting2 = NSURL2.URLWithString("app-settings:");
+    application2.openURL(setting2);
+    plus.ios.deleteObject(setting2);
+    plus.ios.deleteObject(NSURL2);
+    plus.ios.deleteObject(application2);
+  } else {
+    var Intent = plus.android.importClass("android.content.Intent");
+    var Settings = plus.android.importClass("android.provider.Settings");
+    var Uri = plus.android.importClass("android.net.Uri");
+    var mainActivity = plus.android.runtimeMainActivity();
+    var intent = new Intent();
+    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+    var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
+    intent.setData(uri);
+    mainActivity.startActivity(intent);
+  }
+}
+
+function isIOS() {
+  return uni.getSystemInfoSync().platform === 'ios' ? true : false
+}
+
+export {
+  isIOS,
+  checkPermi,
+  checkRole,
+  requestIOS,
+  requestAndroid,
+  gotoAppSetting
+}

+ 152 - 0
src/utils/request.js

@@ -0,0 +1,152 @@
+import configs from "@/config";
+import { getToken } from "@/utils/auth";
+import modal from "@/plugins/modal.plugins";
+import common from "@/plugins/common.plugins";
+
+let timeout = 10000;
+
+/**
+ * @校验结果方法
+ * @param {数据集} res 
+ * @param {结果抛出} resolve 
+ * @param {控制台打印} reject 
+ */
+function verification(res, resolve, reject) {
+  const data = typeof res.data === "string" ? JSON.parse(res.data) : res.data;
+  const code = data.code || 200;
+
+  if (code === 401) {
+    modal.closeLoading();
+    reject("无效的会话,或者会话已过期,请重新登录。");
+  } else if (code === 404 || res.statusCode === 404) {
+    if (data.msg.indexOf(":") !== -1) {
+      modal.msg(data.msg.split(":")[1]);
+      reject(data.msg.split(":")[1]);
+    } else {
+      modal.msg(data.msg);
+      reject(data.msg);
+    }
+    modal.closeLoading();
+  } else if (code === 500 || res.statusCode === 500 || code === 'BIZ-0000') {
+    if (data.msg.indexOf(":") !== -1) {
+      modal.msg(data.msg.split(":")[1]);
+      reject(data.msg.split(":")[1]);
+    } else {
+      modal.msg(data.msg);
+      reject(data.msg);
+    }
+    modal.closeLoading();
+  } else if (code !== 200 && code !== "0") {
+    reject(code);
+  }
+  resolve(data);
+}
+
+const request = (config) => {
+
+  // 是否需要设置 token
+  const isToken = (config.headers || {}).isToken === false;
+  config.header = config.header || {};
+  if (getToken() && !isToken) {
+    config.header["Authorization"] = getToken();
+  }
+
+  // get请求映射params参数
+  if (config.params) {
+    let url = config.url + "?" + common.tansParams(config.params);
+    url = url.slice(0, -1);
+    config.url = url;
+  }
+
+  // 外部接口get请求映射params参数
+  if (config.baseUrl) {
+    let url = config.url + "?" + common.tansParams(config.params);
+    url = url.slice(0, -1);
+    config.url = url;
+    config.baseUrl = config.baseUrl + url
+  }
+
+  return new Promise((resolve, reject) => {
+    uni
+      .request({
+        method: config.method || "get",
+        timeout: config.timeout || timeout,
+        url: config.baseUrl || configs.baseUrl + config.url,
+        data: config.data,
+        header: config.header,
+        dataType: "json",
+      })
+      .then((res) => {
+        if (res.error) {
+          modal.msg("后端接口连接异常");
+          reject("后端接口连接异常");
+          return;
+        }
+        verification(res, resolve, reject);//调用校验结果方法
+      })
+      .catch((error) => {
+        let { errMsg } = error;
+        if (!errMsg) return;
+        if (errMsg === "Network Error") {
+          errMsg = "后端接口连接异常";
+        } else if (errMsg.includes("timeout")) {
+          errMsg = "系统接口请求超时";
+        } else if (errMsg.includes("Request failed with status code")) {
+          errMsg = "系统接口" + errMsg.substr(errMsg.length - 3) + "异常";
+        }
+        if (errMsg != "request:fail") {
+          modal.msg(errMsg);
+        }
+        reject(error);
+      });
+  });
+};
+
+const uploads = (config) => {
+  const useStore = useStores();
+  // 是否需要设置 token
+  const isToken = (config.headers || {}).isToken === false;
+  config.header = config.header || {};
+  if (getToken() && !isToken) {
+    config.header["Authorization"] = getToken();
+  }
+  // get请求映射params参数
+  if (config.params) {
+    let url = config.url + "?" + common.tansParams(config.params);
+    url = url.slice(0, -1);
+    config.url = url;
+  }
+
+  return new Promise((resolve, reject) => {
+    uni.uploadFile({
+      timeout: config.timeout || timeout,
+      url: configs.baseUrl + config.url,
+      filePath: config.filePath,
+      name: config.name || "file",
+      header: config.header,
+      formData: config.formData,
+      success: (res) => {
+        verification(res, resolve, reject);//调用校验结果方法
+      },
+      fail: (error) => {
+        let { errMsg } = error;
+        if (errMsg == "Network Error") {
+          errMsg = "后端接口连接异常";
+        } else if (errMsg.includes("timeout")) {
+          errMsg = "系统接口请求超时";
+        } else if (errMsg.includes("Request failed with status code")) {
+          errMsg = "系统接口" + errMsg.substr(errMsg.length - 3) + "异常";
+        }
+        if (errMsg != "request:fail") {
+          modal.msg(errMsg);
+        }
+        reject(error);
+      },
+    });
+  });
+};
+
+export {
+  request,
+  uploads
+};

+ 23 - 0
src/utils/storage.js

@@ -0,0 +1,23 @@
+// 存储变量名
+let storageKey = 'storage_data'
+
+const storage = {
+  set: function (key, value) {
+    let tmp = uni.getStorageSync(storageKey)
+    tmp = tmp ? tmp : {}
+    tmp[key] = value
+    uni.setStorageSync(storageKey, tmp)
+  },
+  get: function (key) {
+    return uni.getStorageSync(storageKey)[key] || ""
+  },
+  remove: function (key) {
+    delete uni.getStorageSync(storageKey)[key]
+    uni.setStorageSync(storageKey, uni.getStorageSync(storageKey))
+  },
+  clean: function () {
+    uni.removeStorageSync(storageKey)
+  }
+}
+
+export { storage }

+ 91 - 0
src/utils/upgrade.js

@@ -0,0 +1,91 @@
+
+/**
+ * @description H5+下载App
+ * @param downloadUrl:App下载链接
+ * @param progressCallBack:下载进度回调
+ */
+export const downloadApp = (downloadUrl, progressCallBack = () => { },) => {
+    return new Promise((resolve, reject) => {
+        //创建下载任务
+        const downloadTask = plus.downloader.createDownload(downloadUrl, {
+            method: "GET"
+        }, (task, status) => {
+            console.log(status, 'status')
+            if (status == 200) { //下载成功
+                resolve(task.filename)
+            } else {
+                reject('fail')
+                uni.showToast({
+                    title: '下载失败',
+                    duration: 1500,
+                    icon: "none"
+                });
+            }
+        })
+        //监听下载过程
+        downloadTask.addEventListener("statechanged", (task, status) => {
+            switch (task.state) {
+                case 1: // 开始  
+                    break;
+                case 2: //已连接到服务器  
+                    break;
+                case 3: // 已接收到数据  
+                    let hasProgress = task.totalSize && task.totalSize > 0 //是否能获取到App大小
+                    if (hasProgress) {
+                        let current = parseInt(100 * task.downloadedSize / task.totalSize); //获取下载进度百分比
+                        progressCallBack(current)
+                    }
+                    break;
+                case 4: // 下载完成       
+                    break;
+            }
+        });
+        //开始执行下载
+        downloadTask.start();
+    })
+
+
+}
+/**
+ * @description H5+安装APP
+ * @param fileName:app文件名
+ * @param callBack:安装成功回调
+ */
+export const installApp = (fileName, callBack = () => { }) => {
+    //注册广播监听app安装情况
+    onInstallListening(callBack);
+    //开始安装
+    plus.runtime.install(plus.io.convertLocalFileSystemURL(fileName), {}, () => {
+        //成功跳转到安装界面
+    }, function (error) {
+        uni.showToast({
+            title: '安装失败',
+            duration: 1500,
+            icon: "none"
+        });
+    })
+
+}
+/**
+ * @description 注册广播监听APP是否安装成功
+ * @param callBack:安装成功回调函数
+ */
+const onInstallListening = (callBack = () => { }) => {
+
+    let mainActivity = plus.android.runtimeMainActivity(); //获取activity
+    //生成广播接收器
+    let receiver = plus.android.implements('io.dcloud.android.content.BroadcastReceiver', {
+        onReceive: (context, intent) => { //接收广播回调  
+            plus.android.importClass(intent);
+            mainActivity.unregisterReceiver(receiver); //取消监听
+            callBack()
+        }
+    });
+    let IntentFilter = plus.android.importClass('android.content.IntentFilter');
+    let Intent = plus.android.importClass('android.content.Intent');
+    let filter = new IntentFilter();
+    filter.addAction(Intent.ACTION_PACKAGE_ADDED); //监听APP安装     
+    filter.addDataScheme("package");
+    mainActivity.registerReceiver(receiver, filter); //注册广播
+
+}

+ 42 - 0
vite.config.js

@@ -0,0 +1,42 @@
+import { defineConfig } from "vite";
+import path from "path";
+import uni from "@dcloudio/vite-plugin-uni";
+
+export default defineConfig(({ mode, command }) => {
+  return {
+    base: "./", // 打包路径
+    server: {
+      hmr: true, //热更新
+      host: true, //指定服务器
+      open: true, //自动打开浏览器
+      port: 81, //启动端口
+      proxy: {
+        "/dev-api": {
+          target: "http://192.168.123.10:13200",
+          ws: true,
+          changeOrigin: true,
+          //rewrite: (p) => p.replace(/^\/dev-api/, '')
+        },
+        "/prod-api": {
+          target: "https://gateway.usky.cn",
+          ws: true,
+          changeOrigin: true,
+          //rewrite: (p) => p.replace(/^\/dev-api/, '')
+        },
+      },
+    },
+    plugins: [uni()],
+    resolve: {
+      alias: {
+        "@/": path.resolve(__dirname, "src"),
+        "@/static": path.resolve(__dirname, "src/static"),
+        "@/utils": path.resolve(__dirname, "src/utils"),
+      },
+    },
+    configureWebpack: {
+      externals: {
+        AMap: "AMap",
+      },
+    },
+  };
+});