Преглед на файлове

微信一键授权登录注册

jiuling преди 1 година
родител
ревизия
77455882bb
променени са 100 файла, в които са добавени 8626 реда и са изтрити 1503 реда
  1. 10 6
      .gitignore
  2. 73 1
      App.vue
  3. 90 0
      api/services/connect.js
  4. 39 0
      config/api.js
  5. 27 0
      config/config.js
  6. BIN
      icon.png
  7. 24 1
      main.js
  8. 21 23
      manifest.json
  9. 5 0
      package.json
  10. 21 0
      pages.json
  11. 371 0
      pages/login/forget-password.vue
  12. 305 0
      pages/login/index.vue
  13. 431 0
      pages/login/register.vue
  14. 353 302
      pages/user/index.vue
  15. 229 0
      pages/userInfo/index.vue
  16. BIN
      static/images/icon.png
  17. 41 0
      store/index.js
  18. 58 3
      unpackage/dist/dev/mp-weixin/app.js
  19. 2 0
      unpackage/dist/dev/mp-weixin/app.json
  20. 21 0
      unpackage/dist/dev/mp-weixin/app.wxss
  21. 2 3
      unpackage/dist/dev/mp-weixin/common/assets.js
  22. 1148 1023
      unpackage/dist/dev/mp-weixin/common/vendor.js
  23. 1 2
      unpackage/dist/dev/mp-weixin/pages/about/index.js
  24. 0 1
      unpackage/dist/dev/mp-weixin/pages/activity/index.js
  25. 0 1
      unpackage/dist/dev/mp-weixin/pages/ai-chat/index.js
  26. 2 3
      unpackage/dist/dev/mp-weixin/pages/dashboard/index.js
  27. 0 1
      unpackage/dist/dev/mp-weixin/pages/device/index.js
  28. 0 1
      unpackage/dist/dev/mp-weixin/pages/field/index.js
  29. 0 1
      unpackage/dist/dev/mp-weixin/pages/knowledge/index.js
  30. 0 1
      unpackage/dist/dev/mp-weixin/pages/machine/index.js
  31. 0 1
      unpackage/dist/dev/mp-weixin/pages/more/index.js
  32. 0 1
      unpackage/dist/dev/mp-weixin/pages/privacy/index.js
  33. 0 1
      unpackage/dist/dev/mp-weixin/pages/settings/index.js
  34. 78 23
      unpackage/dist/dev/mp-weixin/pages/user/index.js
  35. 1 1
      unpackage/dist/dev/mp-weixin/pages/user/index.wxml
  36. 98 90
      unpackage/dist/dev/mp-weixin/pages/user/index.wxss
  37. 3 13
      unpackage/dist/dev/mp-weixin/project.config.json
  38. 259 0
      utils/Foundation.js
  39. 537 0
      utils/filters.js
  40. 31 0
      utils/js_sdk/amap-wx.130.js
  41. 42 0
      utils/js_sdk/h5-copy/h5-copy.js
  42. 104 0
      utils/js_sdk/lili-pay/wx-pay.js
  43. 78 0
      utils/js_sdk/t-jwt/jwt.js
  44. 38 0
      utils/js_sdk/u-draw-poster/draw-poster.d.ts
  45. 194 0
      utils/js_sdk/u-draw-poster/draw-poster.js
  46. 17 0
      utils/js_sdk/u-draw-poster/extends/create-from-list/index.d.ts
  47. 140 0
      utils/js_sdk/u-draw-poster/extends/create-from-list/index.js
  48. 4 0
      utils/js_sdk/u-draw-poster/extends/create-gcanvas/index.d.ts
  49. 9 0
      utils/js_sdk/u-draw-poster/extends/create-gcanvas/index.js
  50. 12 0
      utils/js_sdk/u-draw-poster/extends/draw-function/draw-image-fit.d.ts
  51. 25 0
      utils/js_sdk/u-draw-poster/extends/draw-function/draw-image-fit.js
  52. 4 0
      utils/js_sdk/u-draw-poster/extends/draw-function/draw-image.d.ts
  53. 42 0
      utils/js_sdk/u-draw-poster/extends/draw-function/draw-image.js
  54. 4 0
      utils/js_sdk/u-draw-poster/extends/draw-function/draw-round-image.d.ts
  55. 15 0
      utils/js_sdk/u-draw-poster/extends/draw-function/draw-round-image.js
  56. 4 0
      utils/js_sdk/u-draw-poster/extends/draw-function/fill-round-rect.d.ts
  57. 7 0
      utils/js_sdk/u-draw-poster/extends/draw-function/fill-round-rect.js
  58. 4 0
      utils/js_sdk/u-draw-poster/extends/draw-function/fill-warp-text.d.ts
  59. 76 0
      utils/js_sdk/u-draw-poster/extends/draw-function/fill-warp-text.js
  60. 7 0
      utils/js_sdk/u-draw-poster/extends/draw-function/index.d.ts
  61. 15 0
      utils/js_sdk/u-draw-poster/extends/draw-function/index.js
  62. 4 0
      utils/js_sdk/u-draw-poster/extends/draw-function/round-rect.d.ts
  63. 41 0
      utils/js_sdk/u-draw-poster/extends/draw-function/round-rect.js
  64. 4 0
      utils/js_sdk/u-draw-poster/extends/draw-function/stroke-round-rect.d.ts
  65. 7 0
      utils/js_sdk/u-draw-poster/extends/draw-function/stroke-round-rect.js
  66. 101 0
      utils/js_sdk/u-draw-poster/extends/draw-painter/index.d.ts
  67. 73 0
      utils/js_sdk/u-draw-poster/extends/draw-painter/index.js
  68. 6 0
      utils/js_sdk/u-draw-poster/extends/draw-qr-code/index.d.ts
  69. 6 0
      utils/js_sdk/u-draw-poster/extends/draw-qr-code/index.js
  70. 10 0
      utils/js_sdk/u-draw-poster/extends/draw-qr-code/uQRCode.d.ts
  71. 1355 0
      utils/js_sdk/u-draw-poster/extends/draw-qr-code/uQRCode.js
  72. 11 0
      utils/js_sdk/u-draw-poster/index.d.ts
  73. 22 0
      utils/js_sdk/u-draw-poster/index.js
  74. 13 0
      utils/js_sdk/u-draw-poster/package.json
  75. 7 0
      utils/js_sdk/u-draw-poster/utils/global.d.ts
  76. 11 0
      utils/js_sdk/u-draw-poster/utils/global.js
  77. 175 0
      utils/js_sdk/u-draw-poster/utils/interface.d.ts
  78. 1 0
      utils/js_sdk/u-draw-poster/utils/interface.js
  79. 38 0
      utils/js_sdk/u-draw-poster/utils/object-sizing.d.ts
  80. 78 0
      utils/js_sdk/u-draw-poster/utils/object-sizing.js
  81. 20 0
      utils/js_sdk/u-draw-poster/utils/utils.d.ts
  82. 49 0
      utils/js_sdk/u-draw-poster/utils/utils.js
  83. 3 0
      utils/js_sdk/u-draw-poster/utils/wx-utils.d.ts
  84. 37 0
      utils/js_sdk/u-draw-poster/utils/wx-utils.js
  85. 273 0
      utils/js_sdk/wa-permission/permission.js
  86. 83 0
      utils/lib/request/adapters/index.js
  87. 51 0
      utils/lib/request/core/InterceptorManager.js
  88. 198 0
      utils/lib/request/core/Request.js
  89. 20 0
      utils/lib/request/core/buildFullPath.js
  90. 27 0
      utils/lib/request/core/defaults.js
  91. 7 0
      utils/lib/request/core/dispatchRequest.js
  92. 97 0
      utils/lib/request/core/mergeConfig.js
  93. 16 0
      utils/lib/request/core/settle.js
  94. 69 0
      utils/lib/request/helpers/buildURL.js
  95. 14 0
      utils/lib/request/helpers/combineURLs.js
  96. 14 0
      utils/lib/request/helpers/isAbsoluteURL.js
  97. 2 0
      utils/lib/request/index.js
  98. 131 0
      utils/lib/request/utils.js
  99. 219 0
      utils/md5.js
  100. 211 0
      utils/request.js

+ 10 - 6
.gitignore

@@ -1,7 +1,11 @@
+# OSX
+#
 .DS_Store
-.hbuilderx
-*.log
-unpackage
-sitemap.json
-.prettierrc.cjs
-.hbuilderx
+node_modules/
+
+#Intellij idea
+.idea/
+/idea/
+.vscode/
+/unpackage/
+.hbuilderx/launch.json

+ 73 - 1
App.vue

@@ -1,4 +1,56 @@
-<script setup>
+<script>
+import { isLoggedIn } from '@/api/services/auth.js';
+
+export default {
+  onLaunch: function() {
+    console.log('App Launch');
+    this.checkLoginStatus();
+  },
+  onShow: function() {
+    console.log('App Show');
+  },
+  onHide: function() {
+    console.log('App Hide');
+  },
+  methods: {
+    checkLoginStatus() {
+      // 登录状态检查,对敏感页面进行拦截
+      const pages = ['pages/dashboard/index', 'pages/user/index', 'pages/activity/index', 'pages/device/index']; 
+      
+      uni.addInterceptor('navigateTo', {
+        invoke(e) {
+          const url = e.url;
+          // 检查是否属于需要登录的页面
+          const needLogin = pages.some(page => url.indexOf(page) > -1);
+          
+          if (needLogin && !isLoggedIn()) {
+            uni.navigateTo({
+              url: '/pages/login/index'
+            });
+            return false;
+          }
+          return true;
+        }
+      });
+      
+      uni.addInterceptor('switchTab', {
+        invoke(e) {
+          const url = e.url;
+          // 检查是否属于需要登录的页面
+          const needLogin = pages.some(page => url.indexOf(page) > -1);
+          
+          if (needLogin && !isLoggedIn()) {
+            uni.navigateTo({
+              url: '/pages/login/index'
+            });
+            return false;
+          }
+          return true;
+        }
+      });
+    }
+  }
+}
 </script>
 
 <template>
@@ -8,4 +60,24 @@
 </template>
 
 <style>
+/* 全局基础样式 */
+page {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  font-size: 28rpx;
+  line-height: 1.5;
+  color: #333;
+  background-color: #f5f5f5;
+}
+
+/* 去除按钮默认边框 */
+button::after {
+  border: none;
+}
+
+/* 隐藏滚动条 */
+::-webkit-scrollbar {
+  width: 0;
+  height: 0;
+  color: transparent;
+}
 </style>

+ 90 - 0
api/services/connect.js

@@ -0,0 +1,90 @@
+/**
+ * 信任登录相关API
+ */
+
+import {
+	http,
+	Method
+} from '@/utils/request.js';
+  // 使用storage模块的方法设置登录状态为false
+import storage from "@/utils/storage.js";
+const request = http.request;
+
+/**
+ * 上传用户头像和昵称
+ * @param params
+ */
+export function uploadInfo(params) {
+  return http.request({
+    url: "uniapp/wechat/uploadInfo",
+    method: Method.POST,
+    needToken: true,
+    data:params,
+  });
+}
+
+
+/**
+ * 小程序自动登录
+ * @param params
+ */
+export function mpAutoLogin(params) {
+	return http.request({
+		url: `uniapp/wechat/login`,
+		method: Method.POST,
+		needToken: true,
+		data: params,
+		header: {
+		  'Authorization': `Bearer ${storage.getAccessToken()}`
+		},
+	});
+}
+/**
+ * 登出
+ * @returns {Promise} 登出结果
+ */
+export function logout() {
+  return new Promise((resolve, reject) => {
+    // 实际环境中应该调用后端登出接口
+	http.request({
+		url: "uniapp/wechat/logout",
+		method: Method.POST,
+		header: {
+		  'Authorization': `Bearer ${storage.getAccessToken()}`
+		},
+      success: (res) => {
+		  console.log("退出登录请求",res);
+        if (res.data.code === 200) {
+          clearLoginState();
+          resolve();
+        } else {
+          reject(res.data.message || '登出失败');
+        }
+      },
+      fail: () => {
+        reject('网络错误,请稍后再试');
+      },
+      complete: () => {
+        // 无论成功失败都清除本地登录状态
+        clearLoginState();
+      }
+    });
+   
+    
+    // 清除登录状态
+    clearLoginState();
+    resolve();
+  });
+}
+/**
+ * 清除登录状态
+ * 从本地存储中移除所有与登录相关的信息
+ */
+export function clearLoginState() {
+
+
+  storage.setHasLogin(false);
+  storage.setAccessToken('');
+  storage.setUserInfo('')
+}
+

+ 39 - 0
config/api.js

@@ -0,0 +1,39 @@
+/**
+ * base    : 基础业务API
+ * buyer   : 买家API
+ */
+// 开发环境
+const dev = {
+  im: "https://im-api.pickmall.cn",
+  common: "https://common-api.pickmall.cn",
+  buyer: "http://localhost:9203",
+  // common: "http://192.168.0.113:8890",
+  // buyer: "http://192.168.0.113:8888",
+  // im: "http://192.168.0.113:8885",
+};
+// 生产环境
+const prod = {
+  im: "https://im-api.pickmall.cn",
+  common: "https://common-api.pickmall.cn",
+  buyer: "http://localhost:8080",
+};
+
+//默认生产环境
+let api = dev;
+//如果是开发环境
+if (process.env.NODE_ENV == "development") {
+  api = dev;
+} else {
+  api = prod;
+}
+//微信小程序,app的打包方式建议为生产环境,所以这块直接条件编译赋值
+// #ifdef MP-WEIXIN || APP-PLUS
+api = prod;
+// #endif
+
+// api.buyer += "/buyer";
+api.common += "/common";
+api.im += "/im";
+export default {
+  ...api,
+};

+ 27 - 0
config/config.js

@@ -0,0 +1,27 @@
+const name = "lilishop"; //全局商城name
+const schemeName = "lilishop"; //唤醒app需要的schemeName
+export default {
+  name: name,
+  schemeLink: `${schemeName}://`, //唤起app地址
+  downloadLink: "https://pickmall.cn/download-page/index.html", //下载地址,下载app的地址
+  shareLink: "https://m-b2b2c.pickmall.cn", //分享地址,也就是在h5中默认的复制地址
+  appid: "wx6f10f29075dc1b0b", //小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态)
+  aMapKey: "1f78544934b66c9fbc0104117f663973", //在高德中申请Web服务key
+  scanAuthNavigation: ["https://m-b2b2c.pickmall.cn/"], //扫码认证跳转域名配置 会根据此处配置的路由进行跳转
+  iosAppId: "id1564638363", //AppStore的应用地址id 具体在分享->拷贝链接中查看
+  logo: "https://lilishop-oss.oss-cn-beijing.aliyuncs.com/4c864e133c2944efad1f7282ac8a3b9e.png", //logo地址
+  customerServiceMobile: "13161366885", //客服电话
+  customerServiceEmail: "lili@lili.com", //客服邮箱
+  imWebSrc: "https://im.pickmall.cn", //IM地址
+  baseWsUrl: "wss://im-api.pickmall.cn/lili/webSocket", // IM WS 地址
+  enableGetClipboard: false, //是否启用粘贴板获取 scanAuthNavigation 中的链接,如果匹配则会跳转到对应页面
+  enableMiniBarStartUpApp: true, //是否在h5中右侧浮空按钮点击启动app
+  /**
+   * 如需更换主题请修改此处以及uni.scss中的全局颜色
+   */
+  mainColor: "#ff3c2a", // 主题色
+  lightColor: "#ff6b35", // 高亮主题色
+  aiderLightColor: "#ff9f28", // 辅助高亮颜色
+  defaultUserPhoto: "/static/missing-face.png", // 默认用户头像
+  enableFetchMobileLogin: false // 是否启用获取手机号登录 如果微信小程序提示封禁手机号获取权限 可将此选项设置成false作为备用登录方案
+};

BIN
icon.png


+ 24 - 1
main.js

@@ -1,8 +1,31 @@
-import App from './App.vue'
 import { createSSRApp } from 'vue'
+import App from './App.vue'
+import * as filters from './utils/filters.js'
+import store from './store'
+// import uView from 'uview-ui'
+import config from '@/config/config'
 
 export function createApp() {
   const app = createSSRApp(App)
+
+  // 全局注册 filters(通过 globalProperties.$filters)
+  app.config.globalProperties.$filters = {}
+  Object.keys(filters).forEach((key) => {
+    app.config.globalProperties.$filters[key] = filters[key]
+  })
+
+  // 全局配置颜色变量(替代 Vue.prototype)
+  app.config.globalProperties.$mainColor = config.mainColor
+  app.config.globalProperties.$lightColor = config.lightColor
+  app.config.globalProperties.$aiderLightColor = config.aiderLightColor
+
+  // 使用 Vuex store
+  app.use(store)
+
+  // 使用 uView UI 框架(确保已正确安装并配置)
+  // app.use(uView)
+
+  // SSR 挂载由框架控制,这里仅返回 app 实例
   return {
     app
   }

+ 21 - 23
manifest.json

@@ -1,24 +1,22 @@
 {
-  "name": "nongxiaoyu",
-  "appid": "__UNI__1234567",
-  "description": "农小禹小程序",
-  "versionName": "1.0.0",
-  "versionCode": "100",
-  "transformPx": false,
-  "app-plus": {
-    "distribute": {
-      "android": {
-        "permissions": [
-          "INTERNET"
-        ]
-      }
-    }
-  },
-  "mp-weixin": {
-    "appid": "",
-    "setting": {
-      "urlCheck": false
-    }
-  },
-  "vueVersion": "3"
-}
+    "name" : "nongxiaoyu",
+    "appid" : "__UNI__1234567",
+    "description" : "农小禹小程序",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    "app-plus" : {
+        "distribute" : {
+            "android" : {
+                "permissions" : [ "INTERNET" ]
+            }
+        }
+    },
+    "mp-weixin" : {
+        "appid" : "wxc738cddfb96a9176",
+        "setting" : {
+            "urlCheck" : false
+        }
+    },
+    "vueVersion" : "3"
+}

+ 5 - 0
package.json

@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "uview-ui": "^3.2.0"
+  }
+}

+ 21 - 0
pages.json

@@ -1,11 +1,25 @@
 {
   "pages": [
+   
     {
       "path": "pages/dashboard/index",
       "style": {
         "navigationBarTitleText": "农小禹 - 首页"
       }
     },
+	{
+	  "path": "pages/login/index",
+	  "style": {
+	    "navigationBarTitleText": "登录",
+	    "navigationStyle": "custom"
+	  }
+	},
+	{
+	  "path": "pages/userInfo/index",
+	  "style": {
+	    "navigationBarTitleText": "用户信息"
+	  }
+	},
     {
       "path": "pages/activity/index",
       "style": {
@@ -71,6 +85,13 @@
       "style": {
         "navigationBarTitleText": "隐私政策"
       }
+    },
+    {
+    	"path" : "pages/test/test/test",
+    	"style" : 
+    	{
+    		"navigationBarTitleText" : ""
+    	}
     }
   ],
   "tabBar": {

+ 371 - 0
pages/login/forget-password.vue

@@ -0,0 +1,371 @@
+<template>
+  <view class="forget-container">
+    <view class="page-header">
+      <text class="back-icon" @click="goBack">←</text>
+      <text class="page-title">找回密码</text>
+      <text class="placeholder"></text>
+    </view>
+    
+    <view class="forget-form">
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">手</text>
+          <input 
+            class="input" 
+            type="number" 
+            placeholder="请输入手机号" 
+            v-model="formData.phone"
+            maxlength="11"
+          />
+        </view>
+      </view>
+      
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">验</text>
+          <input 
+            class="input" 
+            type="number" 
+            placeholder="请输入验证码" 
+            v-model="formData.code"
+            maxlength="6"
+          />
+          <text 
+            class="code-btn" 
+            :class="{ disabled: codeBtnDisabled }"
+            @click="getVerificationCode"
+          >
+            {{ codeBtnText }}
+          </text>
+        </view>
+      </view>
+      
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">新</text>
+          <input 
+            class="input" 
+            :type="showPassword ? 'text' : 'password'" 
+            placeholder="请设置新密码" 
+            v-model="formData.password"
+          />
+          <text class="eye-icon" @click="togglePasswordVisibility">
+            {{ showPassword ? '👁️' : '👁️‍🗨️' }}
+          </text>
+        </view>
+      </view>
+      
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">确</text>
+          <input 
+            class="input" 
+            :type="showConfirmPassword ? 'text' : 'password'" 
+            placeholder="请确认新密码" 
+            v-model="formData.confirmPassword"
+          />
+          <text class="eye-icon" @click="toggleConfirmPasswordVisibility">
+            {{ showConfirmPassword ? '👁️' : '👁️‍🗨️' }}
+          </text>
+        </view>
+      </view>
+      
+      <button class="submit-btn" @click="handleResetPassword">重置密码</button>
+      
+      <view class="login-link">
+        <text>记得密码?</text>
+        <text class="link" @click="navigateToLogin">立即登录</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { resetPassword, sendVerificationCode } from '@/api/services/auth.js';
+
+export default {
+  data() {
+    return {
+      formData: {
+        phone: '',
+        code: '',
+        password: '',
+        confirmPassword: ''
+      },
+      showPassword: false,
+      showConfirmPassword: false,
+      codeBtnText: '获取验证码',
+      codeBtnDisabled: false,
+      countdown: 60
+    }
+  },
+  methods: {
+    goBack() {
+      uni.navigateBack()
+    },
+    togglePasswordVisibility() {
+      this.showPassword = !this.showPassword
+    },
+    toggleConfirmPasswordVisibility() {
+      this.showConfirmPassword = !this.showConfirmPassword
+    },
+    navigateToLogin() {
+      uni.navigateBack()
+    },
+    getVerificationCode() {
+      if (this.codeBtnDisabled) return
+      
+      if (!this.formData.phone) {
+        uni.showToast({
+          title: '请输入手机号',
+          icon: 'none'
+        })
+        return
+      }
+      
+      if (!/^1\d{10}$/.test(this.formData.phone)) {
+        uni.showToast({
+          title: '请输入正确的手机号',
+          icon: 'none'
+        })
+        return
+      }
+      
+      // 发送验证码请求
+      uni.showLoading({ title: '发送中...' })
+      
+      // 使用授权服务发送验证码
+      sendVerificationCode({
+        phone: this.formData.phone,
+        type: 'reset'
+      })
+        .then(() => {
+          uni.showToast({
+            title: '验证码已发送',
+            icon: 'success'
+          });
+          this.startCountdown();
+        })
+        .catch(error => {
+          uni.showToast({
+            title: error,
+            icon: 'none'
+          });
+        })
+        .finally(() => {
+          uni.hideLoading();
+        });
+    },
+    startCountdown() {
+      this.codeBtnDisabled = true
+      this.codeBtnText = `${this.countdown}秒`
+      
+      const timer = setInterval(() => {
+        this.countdown--
+        this.codeBtnText = `${this.countdown}秒`
+        
+        if (this.countdown <= 0) {
+          clearInterval(timer)
+          this.codeBtnDisabled = false
+          this.codeBtnText = '获取验证码'
+          this.countdown = 60
+        }
+      }, 1000)
+    },
+    validateForm() {
+      if (!this.formData.phone) {
+        uni.showToast({
+          title: '请输入手机号',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (!/^1\d{10}$/.test(this.formData.phone)) {
+        uni.showToast({
+          title: '请输入正确的手机号',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (!this.formData.code) {
+        uni.showToast({
+          title: '请输入验证码',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (!this.formData.password) {
+        uni.showToast({
+          title: '请设置新密码',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (this.formData.password.length < 6) {
+        uni.showToast({
+          title: '密码长度不能少于6位',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (this.formData.password !== this.formData.confirmPassword) {
+        uni.showToast({
+          title: '两次输入的密码不一致',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      return true
+    },
+    handleResetPassword() {
+      if (!this.validateForm()) return
+      
+      // 调用重置密码接口
+      uni.showLoading({ title: '提交中...' })
+      
+      // 使用授权服务重置密码
+      resetPassword({
+        phone: this.formData.phone,
+        code: this.formData.code,
+        password: this.formData.password
+      })
+        .then(() => {
+          uni.showToast({
+            title: '密码重置成功',
+            icon: 'success'
+          });
+          
+          // 延迟跳转到登录页
+          setTimeout(() => {
+            uni.navigateBack();
+          }, 1500);
+        })
+        .catch(error => {
+          uni.showToast({
+            title: error,
+            icon: 'none'
+          });
+        })
+        .finally(() => {
+          uni.hideLoading();
+        });
+    }
+  }
+}
+</script>
+
+<style>
+.forget-container {
+  min-height: 100vh;
+  padding: 0 40rpx 40rpx;
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 40rpx 0;
+}
+
+.back-icon {
+  font-size: 50rpx;
+  color: #333;
+  width: 60rpx;
+}
+
+.page-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.placeholder {
+  width: 60rpx;
+}
+
+.forget-form {
+  width: 100%;
+  margin-bottom: 30rpx;
+}
+
+.form-group {
+  margin-bottom: 30rpx;
+}
+
+.input-group {
+  display: flex;
+  align-items: center;
+  border-bottom: 1rpx solid #E0E0E0;
+  padding: 20rpx 0;
+}
+
+.input-icon {
+  width: 44rpx;
+  height: 44rpx;
+  background-color: #E8F5E9;
+  color: #4CAF50;
+  font-size: 24rpx;
+  border-radius: 8rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 20rpx;
+}
+
+.input {
+  flex: 1;
+  height: 44rpx;
+  font-size: 28rpx;
+}
+
+.eye-icon {
+  padding: 0 10rpx;
+  color: #999;
+}
+
+.code-btn {
+  font-size: 28rpx;
+  color: #4CAF50;
+  padding: 0 10rpx;
+}
+
+.code-btn.disabled {
+  color: #999;
+}
+
+.submit-btn {
+  width: 100%;
+  height: 90rpx;
+  background-color: #4CAF50;
+  color: #fff;
+  border-radius: 45rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+  margin: 60rpx 0 30rpx;
+}
+
+.login-link {
+  display: flex;
+  justify-content: center;
+  font-size: 28rpx;
+  color: #666;
+}
+
+.link {
+  color: #4CAF50;
+  margin-left: 10rpx;
+}
+</style> 

+ 305 - 0
pages/login/index.vue

@@ -0,0 +1,305 @@
+<template>
+	<view class="login-container">
+		<!-- 暂不登录-->
+		<view class="page-header">
+			<text class="back-icon" @click="goBack">←</text>
+			<text class="page-title">登录</text>
+			<text class="placeholder"></text>
+		</view>
+
+		<view class="logo-section">
+			<image class="logo" src="/static/images/logo.png" mode="aspectFit"></image>
+			<text class="app-name">农小禹</text>
+			<text class="app-slogan">智慧农业,从此开始</text>
+		</view>
+
+		<view class="login-section">
+
+			<button type="primary" class="wechat-login-btn" bindtap="getUserProfile" @click="getUserProfile()">
+				<view class="wechat-icon">微</view>
+				<text>一键登录</text>
+			</button>
+		</view>
+
+		<view class="privacy-agreement">
+			<checkbox :checked="agreed" @click="toggleAgreement"></checkbox>
+			<text class="agreement-text">
+				登录即表示您已同意
+				<text class="link" @click="navigateToTerms">用户协议</text>
+				和
+				<text class="link" @click="navigateToPrivacy">隐私政策</text>
+			</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		mpAutoLogin
+	} from "@/api/services/connect.js";
+	import storage from "@/utils/storage.js";
+	export default {
+		data() {
+			return {
+				// 以下是登录页面测试功能 结束
+				agreed: false,
+				// 是否展示手机号码授权弹窗,默认第一步不展示,要先获取用户基础信息
+				phoneAuthPopup: false,
+				// 授权信息展示,商城名称
+				// projectName: config.name,
+				//微信返回信息,用于揭秘信息,获取sessionkey
+				code: "",
+				//微信昵称
+				nickName: "",
+				//微信头像
+				avatarUrl: "",
+				gender: ""
+			}
+		},
+		onLoad() {
+			// // 检查是否已登录
+			// if (isLoggedIn()) {
+			// 	this.redirectToHome();
+			// }
+			//获取code
+			uni.login({
+				success: (res) => {
+					if (res.errMsg === "login:ok") {
+						this.code = res.code
+					} else {
+						uni.showToast({
+							title: "系统异常,请联系管理员!"
+						})
+					}
+				},
+			});
+		},
+		methods: {
+			goBack() {
+				uni.navigateBack();
+			},
+			toggleAgreement() {
+				this.agreed = !this.agreed;
+				console.log("this.agreed", this.agreed);
+
+			},
+			navigateToTerms() {
+				uni.navigateTo({
+					url: '/pages/privacy/terms'
+				});
+			},
+			navigateToPrivacy() {
+				uni.navigateTo({
+					url: '/pages/privacy/index'
+				});
+			},
+			showAgreementWarning() {
+				uni.showToast({
+					title: '请同意用户协议和隐私政策',
+					icon: 'none'
+				});
+			},
+			getUserProfile(e) {
+				if (!this.agreed) {
+					uni.showToast({
+						title: "请同意用户协议和隐私政策",
+						icon: 'none'
+					})
+					return
+				}
+				this.logingFlag = true;
+
+				if (this.code) {
+					// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
+					uni.getUserProfile({
+						desc: "获取你的昵称、头像、地区及性别", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
+						success: (res) => {
+							console.log("success", res)
+							this.nickName = res.userInfo.nickName;
+							this.avatarUrl = res.userInfo.avatarUrl;
+							this.gender = res.userInfo.gender;
+
+							let iv = res.iv;
+							let encryptedData = res.encryptedData;
+
+							let code = this.code;
+							let avatarUrl = this.avatarUrl;
+							let nickName = this.nickName;
+							let gender = this.gender;
+							this.isLogin = 2
+							mpAutoLogin({
+								encryptedData,
+								iv,
+								code,
+								avatarUrl,
+								nickName,
+								gender,
+							}).then((apiRes) => {
+								console.log("apiRes", apiRes);
+								storage.setAccessToken(apiRes.data.data.token);
+								// storage.setRefreshToken(apiRes.data.result.refreshToken);
+
+								uni.showToast({
+									title: "登录成功!",
+									icon: "none",
+								});
+								//存储用户信息
+								storage.setUserInfo(apiRes.data.data.userInfo);
+								storage.setHasLogin(true);
+								// 用户手动授权头像昵称
+								if(apiRes.data.data.isNewUser){
+									// 新用户跳转上传头像昵称界面
+									uni.navigateTo({
+										url: `/pages/userInfo/index?openId=${apiRes.data.data.userInfo.openId}`
+									});
+								}else{
+									// 老用户直接跳转首页
+									wx.switchTab({
+										url: '/pages/user/index'
+									});
+								}
+								
+								// uni.navigateBack({
+								// 	delta: 1,
+								// });
+							});
+
+						},
+						fail: (res) => {
+							console.log("fail", res)
+						},
+					});
+
+					this.logingFlag = false;
+				}
+			},
+			redirectToHome() {
+				uni.navigateBack();
+			}
+		}
+	}
+</script>
+
+<style>
+	.login-container {
+		min-height: 100vh;
+		padding: 0 40rpx;
+		background-color: #fff;
+		display: flex;
+		flex-direction: column;
+	}
+
+	.page-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 40rpx 0;
+	}
+
+	.back-icon {
+		font-size: 50rpx;
+		color: #333;
+		width: 60rpx;
+	}
+
+	.page-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.placeholder {
+		width: 60rpx;
+	}
+
+	.logo-section {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		/* margin-bottom: 60rpx; */
+	}
+
+	.logo {
+		width: 150rpx;
+		height: 150rpx;
+		/* margin-bottom: 20rpx; */
+	}
+
+	.app-name {
+		font-size: 48rpx;
+		font-weight: bold;
+		color: #4CAF50;
+		/* margin-bottom: 10rpx; */
+	}
+
+	.app-slogan {
+		font-size: 28rpx;
+		color: #666;
+	}
+
+	.login-section {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding: 20rpx 0;
+		position: relative;
+	}
+
+	.login-bg {
+		width: 100%;
+		max-width: 600rpx;
+		margin: 20rpx 0 60rpx;
+	}
+
+	.wechat-login-btn {
+		width: 80%;
+		height: 90rpx;
+		background-color: #07C160;
+		color: #fff;
+		border-radius: 45rpx;
+		font-size: 32rpx;
+		font-weight: bold;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		border: none;
+		margin-bottom: 40rpx;
+		padding: 0;
+		margin-top: 20rpx;
+	}
+
+	.wechat-login-btn::after {
+		border: none;
+	}
+
+	.wechat-icon {
+		width: 40rpx;
+		height: 40rpx;
+		background-color: rgba(255, 255, 255, 0.2);
+		color: #fff;
+		border-radius: 20rpx;
+		font-size: 20rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 10rpx;
+	}
+
+	.privacy-agreement {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		/* margin-top: auto; */
+		padding: 40rpx 0;
+	}
+
+	.agreement-text {
+		font-size: 24rpx;
+		color: #666;
+		margin-left: 10rpx;
+	}
+
+	.link {
+		color: #4CAF50;
+	}
+</style>

+ 431 - 0
pages/login/register.vue

@@ -0,0 +1,431 @@
+<template>
+  <view class="register-container">
+    <view class="page-header">
+      <text class="back-icon" @click="goBack">←</text>
+      <text class="page-title">注册账号</text>
+      <text class="placeholder"></text>
+    </view>
+    
+    <view class="register-form">
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">手</text>
+          <input 
+            class="input" 
+            type="number" 
+            placeholder="请输入手机号" 
+            v-model="formData.phone"
+            maxlength="11"
+          />
+        </view>
+      </view>
+      
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">验</text>
+          <input 
+            class="input" 
+            type="number" 
+            placeholder="请输入验证码" 
+            v-model="formData.code"
+            maxlength="6"
+          />
+          <text 
+            class="code-btn" 
+            :class="{ disabled: codeBtnDisabled }"
+            @click="getVerificationCode"
+          >
+            {{ codeBtnText }}
+          </text>
+        </view>
+      </view>
+      
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">密</text>
+          <input 
+            class="input" 
+            :type="showPassword ? 'text' : 'password'" 
+            placeholder="请设置密码" 
+            v-model="formData.password"
+          />
+          <text class="eye-icon" @click="togglePasswordVisibility">
+            {{ showPassword ? '👁️' : '👁️‍🗨️' }}
+          </text>
+        </view>
+      </view>
+      
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">确</text>
+          <input 
+            class="input" 
+            :type="showConfirmPassword ? 'text' : 'password'" 
+            placeholder="请确认密码" 
+            v-model="formData.confirmPassword"
+          />
+          <text class="eye-icon" @click="toggleConfirmPasswordVisibility">
+            {{ showConfirmPassword ? '👁️' : '👁️‍🗨️' }}
+          </text>
+        </view>
+      </view>
+      
+      <view class="form-group">
+        <view class="input-group">
+          <text class="input-icon">昵</text>
+          <input 
+            class="input" 
+            type="text" 
+            placeholder="请输入昵称(选填)" 
+            v-model="formData.nickname"
+          />
+        </view>
+      </view>
+      
+      <button class="register-btn" @click="handleRegister">立即注册</button>
+      
+      <view class="login-link">
+        <text>已有账号?</text>
+        <text class="link" @click="navigateToLogin">立即登录</text>
+      </view>
+    </view>
+    
+    <view class="privacy-agreement">
+      <checkbox :checked="agreed" @click="toggleAgreement"></checkbox>
+      <text class="agreement-text">
+        注册即表示您已同意
+        <text class="link" @click="navigateToTerms">用户协议</text>
+        和
+        <text class="link" @click="navigateToPrivacy">隐私政策</text>
+      </text>
+    </view>
+  </view>
+</template>
+
+<script>
+import { register, sendVerificationCode } from '@/api/services/auth.js';
+
+export default {
+  data() {
+    return {
+      formData: {
+        phone: '',
+        code: '',
+        password: '',
+        confirmPassword: '',
+        nickname: ''
+      },
+      showPassword: false,
+      showConfirmPassword: false,
+      agreed: true,
+      codeBtnText: '获取验证码',
+      codeBtnDisabled: false,
+      countdown: 60
+    }
+  },
+  methods: {
+    goBack() {
+      uni.navigateBack()
+    },
+    togglePasswordVisibility() {
+      this.showPassword = !this.showPassword
+    },
+    toggleConfirmPasswordVisibility() {
+      this.showConfirmPassword = !this.showConfirmPassword
+    },
+    toggleAgreement() {
+      this.agreed = !this.agreed
+    },
+    navigateToLogin() {
+      uni.navigateBack()
+    },
+    navigateToTerms() {
+      uni.navigateTo({
+        url: '/pages/privacy/terms'
+      })
+    },
+    navigateToPrivacy() {
+      uni.navigateTo({
+        url: '/pages/privacy/index'
+      })
+    },
+    getVerificationCode() {
+      if (this.codeBtnDisabled) return
+      
+      if (!this.formData.phone) {
+        uni.showToast({
+          title: '请输入手机号',
+          icon: 'none'
+        })
+        return
+      }
+      
+      if (!/^1\d{10}$/.test(this.formData.phone)) {
+        uni.showToast({
+          title: '请输入正确的手机号',
+          icon: 'none'
+        })
+        return
+      }
+      
+      // 发送验证码请求
+      uni.showLoading({ title: '发送中...' })
+      
+      // 使用授权服务发送验证码
+      sendVerificationCode({
+        phone: this.formData.phone,
+        type: 'register'
+      })
+        .then(() => {
+          uni.showToast({
+            title: '验证码已发送',
+            icon: 'success'
+          });
+          this.startCountdown();
+        })
+        .catch(error => {
+          uni.showToast({
+            title: error,
+            icon: 'none'
+          });
+        })
+        .finally(() => {
+          uni.hideLoading();
+        });
+    },
+    startCountdown() {
+      this.codeBtnDisabled = true
+      this.codeBtnText = `${this.countdown}秒`
+      
+      const timer = setInterval(() => {
+        this.countdown--
+        this.codeBtnText = `${this.countdown}秒`
+        
+        if (this.countdown <= 0) {
+          clearInterval(timer)
+          this.codeBtnDisabled = false
+          this.codeBtnText = '获取验证码'
+          this.countdown = 60
+        }
+      }, 1000)
+    },
+    validateForm() {
+      if (!this.formData.phone) {
+        uni.showToast({
+          title: '请输入手机号',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (!/^1\d{10}$/.test(this.formData.phone)) {
+        uni.showToast({
+          title: '请输入正确的手机号',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (!this.formData.code) {
+        uni.showToast({
+          title: '请输入验证码',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (!this.formData.password) {
+        uni.showToast({
+          title: '请设置密码',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (this.formData.password.length < 6) {
+        uni.showToast({
+          title: '密码长度不能少于6位',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      if (this.formData.password !== this.formData.confirmPassword) {
+        uni.showToast({
+          title: '两次输入的密码不一致',
+          icon: 'none'
+        })
+        return false
+      }
+      
+      return true
+    },
+    handleRegister() {
+      if (!this.validateForm()) return
+      
+      if (!this.agreed) {
+        uni.showToast({
+          title: '请同意用户协议和隐私政策',
+          icon: 'none'
+        })
+        return
+      }
+      
+      // 调用注册接口
+      uni.showLoading({ title: '注册中...' })
+      
+      // 使用授权服务注册
+      register({
+        phone: this.formData.phone,
+        code: this.formData.code,
+        password: this.formData.password,
+        nickname: this.formData.nickname || `用户${this.formData.phone.substr(-4)}`
+      })
+        .then(() => {
+          uni.showToast({
+            title: '注册成功',
+            icon: 'success'
+          });
+          
+          // 延迟跳转到登录页
+          setTimeout(() => {
+            uni.navigateBack();
+          }, 1500);
+        })
+        .catch(error => {
+          uni.showToast({
+            title: error,
+            icon: 'none'
+          });
+        })
+        .finally(() => {
+          uni.hideLoading();
+        });
+    }
+  }
+}
+</script>
+
+<style>
+.register-container {
+  min-height: 100vh;
+  padding: 0 40rpx 40rpx;
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 40rpx 0;
+}
+
+.back-icon {
+  font-size: 50rpx;
+  color: #333;
+  width: 60rpx;
+}
+
+.page-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.placeholder {
+  width: 60rpx;
+}
+
+.register-form {
+  width: 100%;
+  margin-bottom: 30rpx;
+}
+
+.form-group {
+  margin-bottom: 30rpx;
+}
+
+.input-group {
+  display: flex;
+  align-items: center;
+  border-bottom: 1rpx solid #E0E0E0;
+  padding: 20rpx 0;
+}
+
+.input-icon {
+  width: 44rpx;
+  height: 44rpx;
+  background-color: #E8F5E9;
+  color: #4CAF50;
+  font-size: 24rpx;
+  border-radius: 8rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 20rpx;
+}
+
+.input {
+  flex: 1;
+  height: 44rpx;
+  font-size: 28rpx;
+}
+
+.eye-icon {
+  padding: 0 10rpx;
+  color: #999;
+}
+
+.code-btn {
+  font-size: 28rpx;
+  color: #4CAF50;
+  padding: 0 10rpx;
+}
+
+.code-btn.disabled {
+  color: #999;
+}
+
+.register-btn {
+  width: 100%;
+  height: 90rpx;
+  background-color: #4CAF50;
+  color: #fff;
+  border-radius: 45rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+  margin: 60rpx 0 30rpx;
+}
+
+.login-link {
+  display: flex;
+  justify-content: center;
+  font-size: 28rpx;
+  color: #666;
+}
+
+.link {
+  color: #4CAF50;
+  margin-left: 10rpx;
+}
+
+.privacy-agreement {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-top: auto;
+  padding: 40rpx 0;
+}
+
+.agreement-text {
+  font-size: 24rpx;
+  color: #666;
+  margin-left: 10rpx;
+}
+</style> 

+ 353 - 302
pages/user/index.vue

@@ -1,337 +1,388 @@
 <template>
-  <view class="container">
-    <!-- 用户信息卡片 -->
-    <view class="user-card">
-      <view class="user-header">
-        <image class="avatar" src="/static/images/default-avatar.png"></image>
-        <view class="user-detail">
-          <text class="nickname">测试用户</text>
-          <text class="user-id">ID: 888888</text>
-        </view>
-      </view>
-    </view>
+	<view class="container">
+		<!-- 用户信息卡片 -->
+		<view class="user-card">
+			<view class="user-header">
+				<image class="avatar" :src="userInfo.avatar || '/static/images/default-avatar.png'"></image>
+				<view class="user-detail" v-if="isLogin">
+					<text class="nickname">{{userInfo.nickName}}</text>
+					<text class="user-id">性别: {{userInfo.sex == '0' ? '男' :'女' || '未知'}}</text>
+				</view>
+				<view class="user-detail" v-else>
+					<text class="login-text" @click="navigateToLogin">登录/注册</text>
+				</view>
+			</view>
+		</view>
 
-    <!-- 地块信息 -->
-    <view class="info-card">
-      <view class="card-title">
-        <text>我的地块</text>
-        <text class="more" @click="navigateToPlots">查看更多 ></text>
-      </view>
-      <view class="plot-info">
-        <view class="plot-item">
-          <text class="number">{{ plotInfo.total }}</text>
-          <text class="label">总地块数</text>
-        </view>
-        <view class="plot-item">
-          <text class="number">{{ plotInfo.active }}</text>
-          <text class="label">种植中</text>
-        </view>
-        <view class="plot-item">
-          <text class="number">{{ plotInfo.idle }}</text>
-          <text class="label">空闲</text>
-        </view>
-      </view>
-    </view>
+		<!-- 地块信息 -->
+		<view class="info-card">
+			<view class="card-title">
+				<text>我的地块</text>
+				<text class="more" @click="navigateToPlots">查看更多 ></text>
+				<text class="more" @click="refresh">测试token过期 ></text>
+			</view>
+			<view class="plot-info">
+				<view class="plot-item">
+					<text class="number">{{ plotInfo.total }}</text>
+					<text class="label">总地块数</text>
+				</view>
+				<view class="plot-item">
+					<text class="number">{{ plotInfo.active }}</text>
+					<text class="label">种植中</text>
+				</view>
+				<view class="plot-item">
+					<text class="number">{{ plotInfo.idle }}</text>
+					<text class="label">空闲</text>
+				</view>
+			</view>
+		</view>
 
-    <!-- 农业服务 -->
-    <view class="info-card">
-      <view class="card-title">
-        <text>农业服务</text>
-      </view>
-      <view class="service-grid">
-        <view 
-          class="service-item" 
-          v-for="(item, index) in serviceList" 
-          :key="index"
-          @click="navigateToService(item)"
-        >
-          <view class="service-icon">
-            <text>{{ item.iconText }}</text>
-          </view>
-          <text class="service-name">{{ item.name }}</text>
-        </view>
-      </view>
-    </view>
+		<!-- 农业服务 -->
+		<view class="info-card">
+			<view class="card-title">
+				<text>农业服务</text>
+			</view>
+			<view class="service-grid">
+				<view class="service-item" v-for="(item, index) in serviceList" :key="index"
+					@click="navigateToService(item)">
+					<view class="service-icon">
+						<text>{{ item.iconText }}</text>
+					</view>
+					<text class="service-name">{{ item.name }}</text>
+				</view>
+			</view>
+		</view>
 
-    <!-- 常用功能 -->
-    <view class="info-card">
-      <view class="function-list">
-        <view class="function-item" @click="handleContact">
-          <view class="left">
-            <text class="function-icon">客</text>
-            <text>联系客服</text>
-          </view>
-          <text class="arrow">></text>
-        </view>
-        <view class="function-item" @click="navigateToAbout">
-          <view class="left">
-            <text class="function-icon">关</text>
-            <text>关于我们</text>
-          </view>
-          <text class="arrow">></text>
-        </view>
-        <view class="function-item" @click="navigateToSettings">
-          <view class="left">
-            <text class="function-icon">设</text>
-            <text>系统设置</text>
-          </view>
-          <text class="arrow">></text>
-        </view>
-        <view class="function-item" @click="handleLogout">
-          <view class="left">
-            <text class="function-icon">退</text>
-            <text>退出登录</text>
-          </view>
-          <text class="arrow">></text>
-        </view>
-      </view>
-    </view>
-  </view>
+		<!-- 常用功能 -->
+		<view class="info-card">
+			<view class="function-list">
+				<view class="function-item" @click="handleContact">
+					<view class="left">
+						<text class="function-icon">客</text>
+						<text>联系客服</text>
+					</view>
+					<text class="arrow">></text>
+				</view>
+				<view class="function-item" @click="navigateToAbout">
+					<view class="left">
+						<text class="function-icon">关</text>
+						<text>关于我们</text>
+					</view>
+					<text class="arrow">></text>
+				</view>
+				<view class="function-item" @click="navigateToSettings">
+					<view class="left">
+						<text class="function-icon">设</text>
+						<text>系统设置</text>
+					</view>
+					<text class="arrow">></text>
+				</view>
+				<view v-if="this.isLogin" class="function-item" @click="handleLogout">
+					<view class="left">
+						<text class="function-icon">退</text>
+						<text>退出登录</text>
+					</view>
+					<text class="arrow">></text>
+				</view>
+			</view>
+		</view>
+	</view>
 </template>
 
 <script>
-export default {
-  data() {
-    return {
-      plotInfo: {
-        total: 3,
-        active: 2,
-        idle: 1
-      },
-      serviceList: [
-        { 
-          name: '农资商城',
-          iconText: '商',
-          path: '/pages/service/mall'
-        },
-        { 
-          name: '农产品销售',
-          iconText: '售',
-          path: '/pages/service/sales'
-        },
-        { 
-          name: '在线专家问诊',
-          iconText: '诊',
-          path: '/pages/service/expert'
-        },
-        { 
-          name: '绿色认证申请',
-          iconText: '证',
-          path: '/pages/service/certification'
-        },
-        { 
-          name: '保险接入',
-          iconText: '保',
-          path: '/pages/service/insurance'
-        }
-      ]
-    }
-  },
-  methods: {
-    navigateToPlots() {
-      uni.navigateTo({ url: '/pages/plots/list' })
-    },
-    navigateToService(item) {
-      uni.navigateTo({ url: item.path })
-    },
-    handleContact() {
-      uni.makePhoneCall({
-        phoneNumber: '400-xxx-xxxx' // 替换为实际的客服电话
-      })
-    },
-    navigateToAbout() {
-      uni.navigateTo({ url: '/pages/about/index' })
-    },
-    navigateToSettings() {
-      uni.navigateTo({ url: '/pages/settings/index' })
-    },
-    handleLogout() {
-      uni.showModal({
-        title: '提示',
-        content: '确认退出登录?',
-        success: (res) => {
-          if (res.confirm) {
-            // 清除登录信息
-            uni.removeStorageSync('token')
-            uni.removeStorageSync('userInfo')
-            // 返回登录页
-            uni.reLaunch({
-              url: '/pages/login/index'
-            })
-          }
-        }
-      })
-    }
-  }
-}
+	import {
+		logout
+	} from '@/api/services/connect.js';
+	import storage from "@/utils/storage.js";
+	export default {
+		data() {
+			return {
+				plotInfo: {
+					total: 3,
+					active: 2,
+					idle: 1
+				},
+				serviceList: [{
+						name: '农资商城',
+						iconText: '商',
+						path: '/pages/service/mall'
+					},
+					{
+						name: '农产品销售',
+						iconText: '售',
+						path: '/pages/service/sales'
+					},
+					{
+						name: '在线专家问诊',
+						iconText: '诊',
+						path: '/pages/service/expert'
+					},
+					{
+						name: '绿色认证申请',
+						iconText: '证',
+						path: '/pages/service/certification'
+					},
+					{
+						name: '保险接入',
+						iconText: '保',
+						path: '/pages/service/insurance'
+					}
+				],
+				userInfo: {
+					nickName: '游客',
+					id: '',
+					avatar: '/static/images/icon.png'
+				},
+				isLogin: false
+			}
+		},
+		onShow() {
+			this.checkLoginStatus();
+		},
+		methods: {
+			// 检查登录状态
+			checkLoginStatus() {
+				console.log("执行Show");
+				if (storage.getHasLogin()) {
+					this.isLogin = true;
+					// 获取用户信息
+					const userInfo = storage.getUserInfo();
+					console.log("执行Show",userInfo);
+					if (userInfo) {
+						this.userInfo = userInfo;
+					}
+				} else {
+					this.isLogin = false;
+				}
+			},
+			navigateToLogin() {
+				uni.navigateTo({
+					url: '/pages/login/index'
+				});
+			},
+			navigateToPlots() {
+				uni.navigateTo({
+					url: '/pages/plots/list'
+				})
+			},
+			navigateToService(item) {
+				uni.navigateTo({
+					url: item.path
+				})
+			},
+			handleContact() {
+				uni.makePhoneCall({
+					phoneNumber: '400-xxx-xxxx' // 替换为实际的客服电话
+				})
+			},
+			navigateToAbout() {
+				uni.navigateTo({
+					url: '/pages/about/index'
+				})
+			},
+			navigateToSettings() {
+				uni.navigateTo({
+					url: '/pages/settings/index'
+				})
+			},
+			// 使用授权服务处理登出
+			handleLogout() {
+				uni.showModal({
+					title: '提示',
+					content: '确认退出登录?',
+					success: (res) => {
+						if (res.confirm) {
+							logout().then(() => {
+								this.isLogin = false;
+								this.userInfo = {
+									nickname: '游客',
+									id: '',
+									avatar: '/static/images/icon.png'
+								};
+							});
+						}
+					}
+				})
+			}
+		}
+	}
 </script>
 
 <style>
-.container {
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  padding: 20rpx;
-}
+	.container {
+		min-height: 100vh;
+		background-color: #f5f5f5;
+		padding: 20rpx;
+	}
 
-.user-card {
-  background-color: #4CAF50;
-  border-radius: 16rpx;
-  padding: 30rpx;
-  margin-bottom: 20rpx;
-}
+	.user-card {
+		background-color: #4CAF50;
+		border-radius: 16rpx;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+	}
 
-.user-header {
-  display: flex;
-  align-items: center;
-}
+	.user-header {
+		display: flex;
+		align-items: center;
+	}
 
-.avatar {
-  width: 120rpx;
-  height: 120rpx;
-  border-radius: 60rpx;
-  border: 4rpx solid #fff;
-}
+	.avatar {
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 60rpx;
+		border: 4rpx solid #fff;
+	}
 
-.user-detail {
-  margin-left: 20rpx;
-  color: #fff;
-}
+	.user-detail {
+		margin-left: 20rpx;
+		color: #fff;
+	}
 
-.nickname {
-  font-size: 32rpx;
-  font-weight: bold;
-  margin-bottom: 10rpx;
-  display: block;
-}
+	.nickname {
+		font-size: 32rpx;
+		font-weight: bold;
+		margin-bottom: 10rpx;
+		display: block;
+	}
 
-.user-id {
-  font-size: 24rpx;
-  opacity: 0.8;
-}
+	.user-id {
+		font-size: 24rpx;
+		opacity: 0.8;
+	}
 
-.info-card {
-  background-color: #fff;
-  border-radius: 16rpx;
-  padding: 30rpx;
-  margin-bottom: 20rpx;
-}
+	.info-card {
+		background-color: #fff;
+		border-radius: 16rpx;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+	}
 
-.card-title {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 30rpx;
-  font-size: 32rpx;
-  font-weight: bold;
-}
+	.card-title {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 30rpx;
+		font-size: 32rpx;
+		font-weight: bold;
+	}
 
-.more {
-  color: #666;
-  font-size: 24rpx;
-  font-weight: normal;
-}
+	.more {
+		color: #666;
+		font-size: 24rpx;
+		font-weight: normal;
+	}
 
-.plot-info {
-  display: flex;
-  justify-content: space-around;
-}
+	.plot-info {
+		display: flex;
+		justify-content: space-around;
+	}
 
-.plot-item {
-  text-align: center;
-}
+	.plot-item {
+		text-align: center;
+	}
 
-.number {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #4CAF50;
-  display: block;
-}
+	.number {
+		font-size: 36rpx;
+		font-weight: bold;
+		color: #4CAF50;
+		display: block;
+	}
 
-.label {
-  font-size: 24rpx;
-  color: #666;
-  margin-top: 10rpx;
-  display: block;
-}
+	.label {
+		font-size: 24rpx;
+		color: #666;
+		margin-top: 10rpx;
+		display: block;
+	}
 
-.service-grid {
-  display: grid;
-  grid-template-columns: repeat(4, 1fr);
-  gap: 30rpx;
-  padding: 10rpx 0;
-}
+	.service-grid {
+		display: grid;
+		grid-template-columns: repeat(4, 1fr);
+		gap: 30rpx;
+		padding: 10rpx 0;
+	}
 
-.service-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
+	.service-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+	}
 
-.service-icon {
-  width: 88rpx;
-  height: 88rpx;
-  background-color: #E8F5E9;
-  border-radius: 16rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 16rpx;
-}
+	.service-icon {
+		width: 88rpx;
+		height: 88rpx;
+		background-color: #E8F5E9;
+		border-radius: 16rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-bottom: 16rpx;
+	}
 
-.service-icon text {
-  font-size: 32rpx;
-  color: #4CAF50;
-  font-weight: bold;
-}
+	.service-icon text {
+		font-size: 32rpx;
+		color: #4CAF50;
+		font-weight: bold;
+	}
 
-.service-name {
-  font-size: 28rpx;
-  color: #666;
-  text-align: center;
-}
+	.service-name {
+		font-size: 28rpx;
+		color: #666;
+		text-align: center;
+	}
 
-.function-list {
-  width: 100%;
-}
+	.function-list {
+		width: 100%;
+	}
 
-.function-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 30rpx 0;
-  border-bottom: 1rpx solid #f5f5f5;
-}
+	.function-item {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 30rpx 0;
+		border-bottom: 1rpx solid #f5f5f5;
+	}
 
-.function-item:last-child {
-  border-bottom: none;
-}
+	.function-item:last-child {
+		border-bottom: none;
+	}
 
-.function-item .left {
-  display: flex;
-  align-items: center;
-}
+	.function-item .left {
+		display: flex;
+		align-items: center;
+	}
 
-.function-icon {
-  width: 44rpx;
-  height: 44rpx;
-  background-color: #E8F5E9;
-  color: #4CAF50;
-  font-size: 28rpx;
-  font-weight: bold;
-  border-radius: 8rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 20rpx;
-}
+	.function-icon {
+		width: 44rpx;
+		height: 44rpx;
+		background-color: #E8F5E9;
+		color: #4CAF50;
+		font-size: 28rpx;
+		font-weight: bold;
+		border-radius: 8rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 20rpx;
+	}
 
-.function-item text {
-  font-size: 28rpx;
-  color: #333;
-}
+	.function-item text {
+		font-size: 28rpx;
+		color: #333;
+	}
 
-.arrow {
-  color: #999;
-  font-size: 24rpx;
-}
-</style>
+	.arrow {
+		color: #999;
+		font-size: 24rpx;
+	}
+
+	.login-text {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #fff;
+		background-color: rgba(255, 255, 255, 0.2);
+		padding: 10rpx 30rpx;
+		border-radius: 30rpx;
+	}
+</style>

+ 229 - 0
pages/userInfo/index.vue

@@ -0,0 +1,229 @@
+<template>
+	<view class="container">
+		<!-- 顶部导航 -->
+		<view class="nav-bar">
+			<view class="back" @click="goBack">
+				<text class="iconfont">←</text>
+			</view>
+			<!-- <text class="title">登录2</text> -->
+			<view class="more-options">
+				<text class="iconfont">→</text>
+			</view>
+		</view>
+
+		<!-- 头像授权 -->
+		<view class="avatar-section">
+			<button class="avatar-button" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
+				<image class="avatar" :src="userInfo.avatarUrl || '/static/images/icon.png'" mode="aspectFill"></image>
+			</button>
+			<text class="avatar-text" v-if="isShow">授权头像</text>
+		</view>
+
+
+		<!-- 昵称输入 -->
+		<view class="form">
+			<view class="form-item">
+				<text class="label">昵称</text>
+				<input name="nickName" type="nickname" placeholder="请填写昵称" class="input" v-model="userInfo.nickName" />
+			</view>
+		</view>
+
+		<!-- 微信一键登录按钮 -->
+		<button class="wx-login-btn" @tap="loginWithWeChat" :loading="loading" :disabled="loading">
+			<image class="wx-icon" src="/static/wechat-icon.png" mode="widthFix" />
+			微信一键授权登录
+		</button>
+
+		<!-- 提示文字 -->
+		<text class="tip-text">微信授权登录后才可进行更多操作哦</text>
+	</view>
+</template>
+
+<script>
+	import {
+		uploadInfo
+	} from "@/api/services/connect.js";
+	import storage from "@/utils/storage.js";
+	export default {
+		data() {
+			return {
+				isShow: true,
+				userInfo: {
+					avatarUrl: '',
+					nickName: '',
+					openId: ''
+				},
+				loading: false,
+			};
+		},
+		methods: {
+			//获取微信头像
+			onChooseAvatar(e) {
+				this.userInfo.avatarUrl = e.detail.avatarUrl;
+				console.log("eeee", e);
+			},
+			goBack() {
+				uni.navigateBack();
+			},
+
+			async requestProfile() {
+				try {
+					const res = await uni.getUserProfile({
+						desc: '用于完善会员资料',
+					});
+					this.userInfo = res.userInfo;
+				} catch (err) {
+					uni.showToast({
+						icon: 'none',
+						title: '授权失败',
+					});
+				}
+			},
+
+			async loginWithWeChat() {
+				// 防止进入后重复点击(双保险)
+				if (this.loading) return;
+				if (!this.userInfo.nickName) {
+					uni.showToast({
+						title: '请先授权头像昵称',
+						icon: 'none'
+					});
+					return;
+				}
+
+				this.loading = true;
+				try {
+					// TODO: 调用后端接口完成注册/登录
+					const res = await uploadInfo(this.userInfo);
+					console.log("res", res);
+					if (res.data.code == 200) {
+						storage.setUserInfo(res.data.data);
+					}
+					uni.showToast({
+						title: '登录成功',
+					});
+
+					// 成功跳转页面
+					wx.switchTab({
+						url: '/pages/user/index'
+					});
+				} catch (err) {
+					console.log("err", err);
+					uni.showToast({
+						icon: 'none',
+						title: '登录失败,请重试',
+					});
+				} finally {
+					this.loading = false;
+				}
+			},
+		},
+		onLoad(options) {
+			console.log("options", options);
+			this.userInfo.openId = options.openId || '';
+		}
+	};
+</script>
+
+<style>
+	.container {
+		background-color: #fff;
+		height: 100vh;
+		padding: 0 20rpx;
+		box-sizing: border-box;
+	}
+
+	.nav-bar {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 40rpx 0 20rpx;
+	}
+
+	.back,
+	.more-options {
+		width: 60rpx;
+		text-align: center;
+	}
+
+	.title {
+		font-size: 36rpx;
+		font-weight: bold;
+	}
+
+	.avatar-section {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		margin-top: 60rpx;
+	}
+
+	.avatar-button {
+		padding: 0;
+		margin: 0;
+		border: none;
+		background-color: transparent;
+		border-radius: 50%;
+		overflow: hidden;
+	}
+
+	.avatar {
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 50%;
+		background-color: #f5f5f5;
+	}
+
+	.avatar-text {
+		margin-top: 20rpx;
+		font-size: 28rpx;
+		color: #888;
+	}
+
+	.form {
+		margin: 60rpx 0 40rpx;
+	}
+
+	.form-item {
+		display: flex;
+		align-items: center;
+		border-bottom: 1px solid #eee;
+		padding-bottom: 20rpx;
+	}
+
+	.label {
+		width: 100rpx;
+		font-size: 28rpx;
+		color: #333;
+	}
+
+	.input {
+		flex: 1;
+		font-size: 28rpx;
+		padding-left: 20rpx;
+	}
+
+	.wx-login-btn {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background-color: #1aad19;
+		color: #fff;
+		border-radius: 100rpx;
+		height: 88rpx;
+		font-size: 30rpx;
+		margin-top: 40rpx;
+	}
+
+	.wx-icon {
+		width: 40rpx;
+		margin-right: 20rpx;
+	}
+
+	.tip-text {
+		margin-top: 20rpx;
+		font-size: 24rpx;
+		color: #999;
+		text-align: center;
+	}
+</style>

BIN
static/images/icon.png


+ 41 - 0
store/index.js

@@ -0,0 +1,41 @@
+import { createStore } from 'vuex';
+import storage from '@/utils/storage';
+
+const store = createStore({
+  state: () => ({
+    isShowToast: false, // 是否在展示Toast中
+    remark: [], // 填写订单备注
+    shareLink: "", // 分享链接
+    verificationKey: "", // 获取key表示验证通过
+    distributionId: "", // 分销员Id
+    hasLogin: storage.getHasLogin(),
+    userInfo: storage.getUserInfo(),
+    uuid: storage.getUuid(),
+    token: "",
+    userName: "",
+  }),
+
+  mutations: {
+    login(state, userInfo) {
+      state.userInfo = userInfo || {};
+      state.userName =
+        userInfo.Name || userInfo.Nickname || userInfo.Username || "匿名用户";
+      state.hasLogin = true;
+    },
+    logout(state) {
+      state.userName = "";
+      state.hasLogin = false;
+      state.userInfo = {};
+    },
+    // 设置填写订单中备注
+    setRemark(state, remark) {
+      state.remark = remark;
+    },
+  },
+
+  actions: {
+    // 可在此添加异步登录逻辑
+  },
+});
+
+export default store;

+ 58 - 3
unpackage/dist/dev/mp-weixin/app.js

@@ -1,8 +1,14 @@
 "use strict";
 Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
 const common_vendor = require("./common/vendor.js");
+const api_services_auth = require("./api/services/auth.js");
+const utils_filters = require("./utils/filters.js");
+const store_index = require("./store/index.js");
+const config_config = require("./config/config.js");
 if (!Math) {
   "./pages/dashboard/index.js";
+  "./pages/login/index.js";
+  "./pages/userInfo/index.js";
   "./pages/activity/index.js";
   "./pages/device/index.js";
   "./pages/knowledge/index.js";
@@ -15,17 +21,66 @@ if (!Math) {
   "./pages/about/index.js";
   "./pages/privacy/index.js";
 }
-const _sfc_main = {};
-function _sfc_render(_ctx, _cache) {
+const _sfc_main = {
+  onLaunch: function() {
+    console.log("App Launch");
+    this.checkLoginStatus();
+  },
+  onShow: function() {
+    console.log("App Show");
+  },
+  onHide: function() {
+    console.log("App Hide");
+  },
+  methods: {
+    checkLoginStatus() {
+      const pages = ["pages/dashboard/index", "pages/user/index", "pages/activity/index", "pages/device/index"];
+      common_vendor.index.addInterceptor("navigateTo", {
+        invoke(e) {
+          const url = e.url;
+          const needLogin = pages.some((page) => url.indexOf(page) > -1);
+          if (needLogin && !api_services_auth.isLoggedIn()) {
+            common_vendor.index.navigateTo({
+              url: "/pages/login/index"
+            });
+            return false;
+          }
+          return true;
+        }
+      });
+      common_vendor.index.addInterceptor("switchTab", {
+        invoke(e) {
+          const url = e.url;
+          const needLogin = pages.some((page) => url.indexOf(page) > -1);
+          if (needLogin && !api_services_auth.isLoggedIn()) {
+            common_vendor.index.navigateTo({
+              url: "/pages/login/index"
+            });
+            return false;
+          }
+          return true;
+        }
+      });
+    }
+  }
+};
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
   return {};
 }
 const App = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
 function createApp() {
   const app = common_vendor.createSSRApp(App);
+  app.config.globalProperties.$filters = {};
+  Object.keys(utils_filters.filters).forEach((key) => {
+    app.config.globalProperties.$filters[key] = utils_filters.filters[key];
+  });
+  app.config.globalProperties.$mainColor = config_config.config.mainColor;
+  app.config.globalProperties.$lightColor = config_config.config.lightColor;
+  app.config.globalProperties.$aiderLightColor = config_config.config.aiderLightColor;
+  app.use(store_index.store);
   return {
     app
   };
 }
 createApp().app.mount("#app");
 exports.createApp = createApp;
-//# sourceMappingURL=../.sourcemap/mp-weixin/app.js.map

+ 2 - 0
unpackage/dist/dev/mp-weixin/app.json

@@ -1,6 +1,8 @@
 {
   "pages": [
     "pages/dashboard/index",
+    "pages/login/index",
+    "pages/userInfo/index",
     "pages/activity/index",
     "pages/device/index",
     "pages/knowledge/index",

+ 21 - 0
unpackage/dist/dev/mp-weixin/app.wxss

@@ -1 +1,22 @@
+
+/* 全局基础样式 */
+page {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  font-size: 28rpx;
+  line-height: 1.5;
+  color: #333;
+  background-color: #f5f5f5;
+}
+
+/* 去除按钮默认边框 */
+button::after {
+  border: none;
+}
+
+/* 隐藏滚动条 */
+::-webkit-scrollbar {
+  width: 0;
+  height: 0;
+  color: transparent;
+}
 page{--status-bar-height:25px;--top-window-height:0px;--window-top:0px;--window-bottom:0px;--window-left:0px;--window-right:0px;--window-magin:0px}[data-c-h="true"]{display: none !important;}

+ 2 - 3
unpackage/dist/dev/mp-weixin/common/assets.js

@@ -1,6 +1,5 @@
 "use strict";
-const _imports_0$1 = "/static/images/default-avatar.png";
-const _imports_0 = "/static/images/logo.png";
+const _imports_0$1 = "/static/images/logo.png";
+const _imports_0 = "/static/wechat-icon.png";
 exports._imports_0 = _imports_0$1;
 exports._imports_0$1 = _imports_0;
-//# sourceMappingURL=../../.sourcemap/mp-weixin/common/assets.js.map

Файловите разлики са ограничени, защото са твърде много
+ 1148 - 1023
unpackage/dist/dev/mp-weixin/common/vendor.js


+ 1 - 2
unpackage/dist/dev/mp-weixin/pages/about/index.js

@@ -26,7 +26,7 @@ const _sfc_main = {
     };
     return (_ctx, _cache) => {
       return {
-        a: common_assets._imports_0$1,
+        a: common_assets._imports_0,
         b: common_vendor.t(version.value),
         c: common_vendor.o(($event) => copyText("www.nongxiaoyu.com")),
         d: common_vendor.o(makePhoneCall),
@@ -36,4 +36,3 @@ const _sfc_main = {
   }
 };
 wx.createPage(_sfc_main);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/about/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/activity/index.js

@@ -6,4 +6,3 @@ function _sfc_render(_ctx, _cache) {
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-2c61ebca"]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/activity/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/ai-chat/index.js

@@ -6,4 +6,3 @@ function _sfc_render(_ctx, _cache) {
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-9edadb70"]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/ai-chat/index.js.map

+ 2 - 3
unpackage/dist/dev/mp-weixin/pages/dashboard/index.js

@@ -109,9 +109,9 @@ const _sfc_main = {
     };
     const fetchStatsData = async () => {
       try {
-        common_vendor.index.__f__("log", "at pages/dashboard/index.vue:429", "统计数据加载成功");
+        console.log("统计数据加载成功");
       } catch (error) {
-        common_vendor.index.__f__("error", "at pages/dashboard/index.vue:431", "获取统计数据失败", error);
+        console.error("获取统计数据失败", error);
         common_vendor.index.showToast({
           title: "数据加载失败",
           icon: "none"
@@ -351,4 +351,3 @@ const _sfc_main = {
 };
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-a869e244"]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/dashboard/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/device/index.js

@@ -6,4 +6,3 @@ function _sfc_render(_ctx, _cache) {
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-1d9691da"]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/device/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/field/index.js

@@ -6,4 +6,3 @@ function _sfc_render(_ctx, _cache) {
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-6f005d18"]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/field/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/knowledge/index.js

@@ -6,4 +6,3 @@ function _sfc_render(_ctx, _cache) {
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-502b983e"]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/knowledge/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/machine/index.js

@@ -6,4 +6,3 @@ function _sfc_render(_ctx, _cache) {
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-3c4b8275"]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/machine/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/more/index.js

@@ -6,4 +6,3 @@ function _sfc_render(_ctx, _cache) {
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-9cb5c55a"]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/more/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/privacy/index.js

@@ -8,4 +8,3 @@ const _sfc_main = {
   }
 };
 wx.createPage(_sfc_main);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/privacy/index.js.map

+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/settings/index.js

@@ -104,4 +104,3 @@ const _sfc_main = {
   }
 };
 wx.createPage(_sfc_main);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/settings/index.js.map

+ 78 - 23
unpackage/dist/dev/mp-weixin/pages/user/index.js

@@ -1,6 +1,7 @@
 "use strict";
 const common_vendor = require("../../common/vendor.js");
-const common_assets = require("../../common/assets.js");
+const api_services_connect = require("../../api/services/connect.js");
+const utils_storage = require("../../utils/storage.js");
 const _sfc_main = {
   data() {
     return {
@@ -35,15 +36,52 @@ const _sfc_main = {
           iconText: "保",
           path: "/pages/service/insurance"
         }
-      ]
+      ],
+      userInfo: {
+        nickName: "游客",
+        id: "",
+        avatar: "/static/images/icon.png"
+      },
+      isLogin: false
     };
   },
+  onShow() {
+    this.checkLoginStatus();
+  },
   methods: {
+    refresh() {
+      api_services_connect.refresh().then((res) => {
+        console.log("请求成功。未执行属性token", res);
+      });
+    },
+    // 检查登录状态
+    checkLoginStatus() {
+      console.log("执行Show");
+      if (utils_storage.storage.getHasLogin()) {
+        this.isLogin = true;
+        const userInfo = utils_storage.storage.getUserInfo();
+        console.log("执行Show", userInfo);
+        if (userInfo) {
+          this.userInfo = userInfo;
+        }
+      } else {
+        this.isLogin = false;
+      }
+    },
+    navigateToLogin() {
+      common_vendor.index.navigateTo({
+        url: "/pages/login/index"
+      });
+    },
     navigateToPlots() {
-      common_vendor.index.navigateTo({ url: "/pages/plots/list" });
+      common_vendor.index.navigateTo({
+        url: "/pages/plots/list"
+      });
     },
     navigateToService(item) {
-      common_vendor.index.navigateTo({ url: item.path });
+      common_vendor.index.navigateTo({
+        url: item.path
+      });
     },
     handleContact() {
       common_vendor.index.makePhoneCall({
@@ -52,21 +90,29 @@ const _sfc_main = {
       });
     },
     navigateToAbout() {
-      common_vendor.index.navigateTo({ url: "/pages/about/index" });
+      common_vendor.index.navigateTo({
+        url: "/pages/about/index"
+      });
     },
     navigateToSettings() {
-      common_vendor.index.navigateTo({ url: "/pages/settings/index" });
+      common_vendor.index.navigateTo({
+        url: "/pages/settings/index"
+      });
     },
+    // 使用授权服务处理登出
     handleLogout() {
       common_vendor.index.showModal({
         title: "提示",
         content: "确认退出登录?",
         success: (res) => {
           if (res.confirm) {
-            common_vendor.index.removeStorageSync("token");
-            common_vendor.index.removeStorageSync("userInfo");
-            common_vendor.index.reLaunch({
-              url: "/pages/login/index"
+            api_services_connect.logout().then(() => {
+              this.isLogin = false;
+              this.userInfo = {
+                nickname: "游客",
+                id: "",
+                avatar: "/static/images/icon.png"
+              };
             });
           }
         }
@@ -75,13 +121,21 @@ const _sfc_main = {
   }
 };
 function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
-  return {
-    a: common_assets._imports_0,
-    b: common_vendor.o((...args) => $options.navigateToPlots && $options.navigateToPlots(...args)),
-    c: common_vendor.t($data.plotInfo.total),
-    d: common_vendor.t($data.plotInfo.active),
-    e: common_vendor.t($data.plotInfo.idle),
-    f: common_vendor.f($data.serviceList, (item, index, i0) => {
+  return common_vendor.e({
+    a: $data.userInfo.avatar || "/static/images/default-avatar.png",
+    b: $data.isLogin
+  }, $data.isLogin ? {
+    c: common_vendor.t($data.userInfo.nickName),
+    d: common_vendor.t($data.userInfo.sex == "0" ? "男" : "女")
+  } : {
+    e: common_vendor.o((...args) => $options.navigateToLogin && $options.navigateToLogin(...args))
+  }, {
+    f: common_vendor.o((...args) => $options.navigateToPlots && $options.navigateToPlots(...args)),
+    g: common_vendor.o((...args) => $options.refresh && $options.refresh(...args)),
+    h: common_vendor.t($data.plotInfo.total),
+    i: common_vendor.t($data.plotInfo.active),
+    j: common_vendor.t($data.plotInfo.idle),
+    k: common_vendor.f($data.serviceList, (item, index, i0) => {
       return {
         a: common_vendor.t(item.iconText),
         b: common_vendor.t(item.name),
@@ -89,12 +143,13 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
         d: common_vendor.o(($event) => $options.navigateToService(item), index)
       };
     }),
-    g: common_vendor.o((...args) => $options.handleContact && $options.handleContact(...args)),
-    h: common_vendor.o((...args) => $options.navigateToAbout && $options.navigateToAbout(...args)),
-    i: common_vendor.o((...args) => $options.navigateToSettings && $options.navigateToSettings(...args)),
-    j: common_vendor.o((...args) => $options.handleLogout && $options.handleLogout(...args))
-  };
+    l: common_vendor.o((...args) => $options.handleContact && $options.handleContact(...args)),
+    m: common_vendor.o((...args) => $options.navigateToAbout && $options.navigateToAbout(...args)),
+    n: common_vendor.o((...args) => $options.navigateToSettings && $options.navigateToSettings(...args)),
+    o: this.isLogin
+  }, this.isLogin ? {
+    p: common_vendor.o((...args) => $options.handleLogout && $options.handleLogout(...args))
+  } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
 wx.createPage(MiniProgramPage);
-//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/user/index.js.map

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/user/index.wxml

@@ -1 +1 @@
-<view class="container"><view class="user-card"><view class="user-header"><image class="avatar" src="{{a}}"></image><view class="user-detail"><text class="nickname">测试用户</text><text class="user-id">ID: 888888</text></view></view></view><view class="info-card"><view class="card-title"><text>我的地块</text><text class="more" bindtap="{{b}}">查看更多 ></text></view><view class="plot-info"><view class="plot-item"><text class="number">{{c}}</text><text class="label">总地块数</text></view><view class="plot-item"><text class="number">{{d}}</text><text class="label">种植中</text></view><view class="plot-item"><text class="number">{{e}}</text><text class="label">空闲</text></view></view></view><view class="info-card"><view class="card-title"><text>农业服务</text></view><view class="service-grid"><view wx:for="{{f}}" wx:for-item="item" wx:key="c" class="service-item" bindtap="{{item.d}}"><view class="service-icon"><text>{{item.a}}</text></view><text class="service-name">{{item.b}}</text></view></view></view><view class="info-card"><view class="function-list"><view class="function-item" bindtap="{{g}}"><view class="left"><text class="function-icon">客</text><text>联系客服</text></view><text class="arrow">></text></view><view class="function-item" bindtap="{{h}}"><view class="left"><text class="function-icon">关</text><text>关于我们</text></view><text class="arrow">></text></view><view class="function-item" bindtap="{{i}}"><view class="left"><text class="function-icon">设</text><text>系统设置</text></view><text class="arrow">></text></view><view class="function-item" bindtap="{{j}}"><view class="left"><text class="function-icon">退</text><text>退出登录</text></view><text class="arrow">></text></view></view></view></view>
+<view class="container"><view class="user-card"><view class="user-header"><image class="avatar" src="{{a}}"></image><view wx:if="{{b}}" class="user-detail"><text class="nickname">{{c}}</text><text class="user-id">性别: {{d}}</text></view><view wx:else class="user-detail"><text class="login-text" bindtap="{{e}}">登录/注册</text></view></view></view><view class="info-card"><view class="card-title"><text>我的地块</text><text class="more" bindtap="{{f}}">查看更多 ></text><text class="more" bindtap="{{g}}">测试token过期 ></text></view><view class="plot-info"><view class="plot-item"><text class="number">{{h}}</text><text class="label">总地块数</text></view><view class="plot-item"><text class="number">{{i}}</text><text class="label">种植中</text></view><view class="plot-item"><text class="number">{{j}}</text><text class="label">空闲</text></view></view></view><view class="info-card"><view class="card-title"><text>农业服务</text></view><view class="service-grid"><view wx:for="{{k}}" wx:for-item="item" wx:key="c" class="service-item" bindtap="{{item.d}}"><view class="service-icon"><text>{{item.a}}</text></view><text class="service-name">{{item.b}}</text></view></view></view><view class="info-card"><view class="function-list"><view class="function-item" bindtap="{{l}}"><view class="left"><text class="function-icon">客</text><text>联系客服</text></view><text class="arrow">></text></view><view class="function-item" bindtap="{{m}}"><view class="left"><text class="function-icon">关</text><text>关于我们</text></view><text class="arrow">></text></view><view class="function-item" bindtap="{{n}}"><view class="left"><text class="function-icon">设</text><text>系统设置</text></view><text class="arrow">></text></view><view wx:if="{{o}}" class="function-item" bindtap="{{p}}"><view class="left"><text class="function-icon">退</text><text>退出登录</text></view><text class="arrow">></text></view></view></view></view>

+ 98 - 90
unpackage/dist/dev/mp-weixin/pages/user/index.wxss

@@ -1,143 +1,151 @@
 
 .container {
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  padding: 20rpx;
+		min-height: 100vh;
+		background-color: #f5f5f5;
+		padding: 20rpx;
 }
 .user-card {
-  background-color: #4CAF50;
-  border-radius: 16rpx;
-  padding: 30rpx;
-  margin-bottom: 20rpx;
+		background-color: #4CAF50;
+		border-radius: 16rpx;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
 }
 .user-header {
-  display: flex;
-  align-items: center;
+		display: flex;
+		align-items: center;
 }
 .avatar {
-  width: 120rpx;
-  height: 120rpx;
-  border-radius: 60rpx;
-  border: 4rpx solid #fff;
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 60rpx;
+		border: 4rpx solid #fff;
 }
 .user-detail {
-  margin-left: 20rpx;
-  color: #fff;
+		margin-left: 20rpx;
+		color: #fff;
 }
 .nickname {
-  font-size: 32rpx;
-  font-weight: bold;
-  margin-bottom: 10rpx;
-  display: block;
+		font-size: 32rpx;
+		font-weight: bold;
+		margin-bottom: 10rpx;
+		display: block;
 }
 .user-id {
-  font-size: 24rpx;
-  opacity: 0.8;
+		font-size: 24rpx;
+		opacity: 0.8;
 }
 .info-card {
-  background-color: #fff;
-  border-radius: 16rpx;
-  padding: 30rpx;
-  margin-bottom: 20rpx;
+		background-color: #fff;
+		border-radius: 16rpx;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
 }
 .card-title {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 30rpx;
-  font-size: 32rpx;
-  font-weight: bold;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 30rpx;
+		font-size: 32rpx;
+		font-weight: bold;
 }
 .more {
-  color: #666;
-  font-size: 24rpx;
-  font-weight: normal;
+		color: #666;
+		font-size: 24rpx;
+		font-weight: normal;
 }
 .plot-info {
-  display: flex;
-  justify-content: space-around;
+		display: flex;
+		justify-content: space-around;
 }
 .plot-item {
-  text-align: center;
+		text-align: center;
 }
 .number {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #4CAF50;
-  display: block;
+		font-size: 36rpx;
+		font-weight: bold;
+		color: #4CAF50;
+		display: block;
 }
 .label {
-  font-size: 24rpx;
-  color: #666;
-  margin-top: 10rpx;
-  display: block;
+		font-size: 24rpx;
+		color: #666;
+		margin-top: 10rpx;
+		display: block;
 }
 .service-grid {
-  display: grid;
-  grid-template-columns: repeat(4, 1fr);
-  gap: 30rpx;
-  padding: 10rpx 0;
+		display: grid;
+		grid-template-columns: repeat(4, 1fr);
+		gap: 30rpx;
+		padding: 10rpx 0;
 }
 .service-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
 }
 .service-icon {
-  width: 88rpx;
-  height: 88rpx;
-  background-color: #E8F5E9;
-  border-radius: 16rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 16rpx;
+		width: 88rpx;
+		height: 88rpx;
+		background-color: #E8F5E9;
+		border-radius: 16rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-bottom: 16rpx;
 }
 .service-icon text {
-  font-size: 32rpx;
-  color: #4CAF50;
-  font-weight: bold;
+		font-size: 32rpx;
+		color: #4CAF50;
+		font-weight: bold;
 }
 .service-name {
-  font-size: 28rpx;
-  color: #666;
-  text-align: center;
+		font-size: 28rpx;
+		color: #666;
+		text-align: center;
 }
 .function-list {
-  width: 100%;
+		width: 100%;
 }
 .function-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 30rpx 0;
-  border-bottom: 1rpx solid #f5f5f5;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 30rpx 0;
+		border-bottom: 1rpx solid #f5f5f5;
 }
 .function-item:last-child {
-  border-bottom: none;
+		border-bottom: none;
 }
 .function-item .left {
-  display: flex;
-  align-items: center;
+		display: flex;
+		align-items: center;
 }
 .function-icon {
-  width: 44rpx;
-  height: 44rpx;
-  background-color: #E8F5E9;
-  color: #4CAF50;
-  font-size: 28rpx;
-  font-weight: bold;
-  border-radius: 8rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 20rpx;
+		width: 44rpx;
+		height: 44rpx;
+		background-color: #E8F5E9;
+		color: #4CAF50;
+		font-size: 28rpx;
+		font-weight: bold;
+		border-radius: 8rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 20rpx;
 }
 .function-item text {
-  font-size: 28rpx;
-  color: #333;
+		font-size: 28rpx;
+		color: #333;
 }
 .arrow {
-  color: #999;
-  font-size: 24rpx;
+		color: #999;
+		font-size: 24rpx;
+}
+.login-text {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #fff;
+		background-color: rgba(255, 255, 255, 0.2);
+		padding: 10rpx 30rpx;
+		border-radius: 30rpx;
 }

+ 3 - 13
unpackage/dist/dev/mp-weixin/project.config.json

@@ -11,7 +11,6 @@
     "minified": false,
     "newFeature": true,
     "bigPackageSizeSupport": true,
-    "ignoreUploadUnusedFiles": true,
     "babelSetting": {
       "ignore": [],
       "disablePlugins": [],
@@ -19,19 +18,10 @@
     }
   },
   "compileType": "miniprogram",
-  "libVersion": "3.8.3",
+  "libVersion": "3.8.4",
+  "appid": "wxc738cddfb96a9176",
   "projectname": "nongxiaoyu",
-  "condition": {
-    "miniprogram": {
-      "list": [
-        {
-          "name": "pages/user/index",
-          "pathName": "pages/user/index",
-          "query": ""
-        }
-      ]
-    }
-  },
+  "condition": {},
   "editorSetting": {
     "tabIndent": "insertSpaces",
     "tabSize": 2

+ 259 - 0
utils/Foundation.js

@@ -0,0 +1,259 @@
+/**
+ * 一些常用的基础方法
+ * whetherNavigate 登录后跳转判断
+ * unixToDate    将unix时间戳转换为指定格式
+ * dateToUnix    将时间转unix时间戳
+ * deepClone     对一个对象进行深拷贝
+ * formatPrice   货币格式化
+ * secrecyMobile 手机号隐私保护
+ * randomString  随机生成指定长度的字符串
+ */
+
+/**
+ * 验证银行卡号
+ */
+export function checkBankno(bankno) {
+  var lastNum = bankno.substr(bankno.length - 1, 1); //取出最后一位(与luhm进行比较)
+  var first15Num = bankno.substr(0, bankno.length - 1); //前15或18位
+  var newArr = [];
+
+  for (var i = first15Num.length - 1; i > -1; i--) {
+    //前15或18位倒序存进数组
+    newArr.push(first15Num.substr(i, 1));
+  }
+
+  var arrJiShu = []; //奇数位*2的积 <9
+  var arrJiShu2 = []; //奇数位*2的积 >9
+  var arrOuShu = []; //偶数位数组
+  for (var j = 0; j < newArr.length; j++) {
+    if ((j + 1) % 2 == 1) {
+      //奇数位
+      if (parseInt(newArr[j]) * 2 < 9) arrJiShu.push(parseInt(newArr[j]) * 2);
+      else arrJiShu2.push(parseInt(newArr[j]) * 2);
+    } //偶数位
+    else arrOuShu.push(newArr[j]);
+  }
+
+  var jishu_child1 = []; //奇数位*2 >9 的分割之后的数组个位数
+  var jishu_child2 = []; //奇数位*2 >9 的分割之后的数组十位数
+  for (var h = 0; h < arrJiShu2.length; h++) {
+    jishu_child1.push(parseInt(arrJiShu2[h]) % 10);
+    jishu_child2.push(parseInt(arrJiShu2[h]) / 10);
+  }
+
+  var sumJiShu = 0; //奇数位*2 < 9 的数组之和
+  var sumOuShu = 0; //偶数位数组之和
+  var sumJiShuChild1 = 0; //奇数位*2 >9 的分割之后的数组个位数之和
+  var sumJiShuChild2 = 0; //奇数位*2 >9 的分割之后的数组十位数之和
+  var sumTotal = 0;
+  for (var m = 0; m < arrJiShu.length; m++) {
+    sumJiShu = sumJiShu + parseInt(arrJiShu[m]);
+  }
+  for (var n = 0; n < arrOuShu.length; n++) {
+    sumOuShu = sumOuShu + parseInt(arrOuShu[n]);
+  }
+  for (var p = 0; p < jishu_child1.length; p++) {
+    sumJiShuChild1 = sumJiShuChild1 + parseInt(jishu_child1[p]);
+    sumJiShuChild2 = sumJiShuChild2 + parseInt(jishu_child2[p]);
+  }
+  //计算总和
+  sumTotal =
+    parseInt(sumJiShu) +
+    parseInt(sumOuShu) +
+    parseInt(sumJiShuChild1) +
+    parseInt(sumJiShuChild2);
+  //计算Luhm值
+  var k = parseInt(sumTotal) % 10 == 0 ? 10 : parseInt(sumTotal) % 10;
+  var luhm = 10 - k;
+  if (lastNum == luhm) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+/**
+ * 登录后跳转判断
+ * 计算出当前router路径
+ * 1.如果跳转的链接为登录页面或跳转的链接为空页面。则会重新跳转到首页
+ * 2.都不满足返回跳转页面
+ * @param type  'default' || 'wx'  //返回地址会做判断默认为default
+ */
+
+export function whetherNavigate(type = "default") {
+  let navigation = getCurrentPages()[getCurrentPages().length - (getCurrentPages().length ) ];
+  if (getCurrentPages().length > 1) {
+    console.log(navigation, getCurrentPages());
+    if (navigation.route == "pages/passport/login") {
+      navigationToBack(type);
+    } else {
+      if (!navigation.route || navigation.route == "undefined") {
+        navigationToBack(type);
+      } else {
+        uni.navigateBack({
+          delta: getCurrentPages().length-1,
+        });
+      }
+    }
+  } else {
+    uni.switchTab({
+      url: "/pages/tabbar/home/index",
+    });
+  }
+}
+
+/**
+ * 将unix时间戳转换为指定格式
+ * @param unix   时间戳【秒】
+ * @param format 转换格式
+ * @returns {*|string}
+ */
+export function unixToDate(unix, format) {
+  if (!unix) return unix;
+  let _format = format || "yyyy-MM-dd hh:mm:ss";
+  const d = new Date(unix);
+  const o = {
+    "M+": d.getMonth() + 1,
+    "d+": d.getDate(),
+    "h+": d.getHours(),
+    "m+": d.getMinutes(),
+    "s+": d.getSeconds(),
+    "q+": Math.floor((d.getMonth() + 3) / 3),
+    S: d.getMilliseconds(),
+  };
+  if (/(y+)/.test(_format))
+    _format = _format.replace(
+      RegExp.$1,
+      (d.getFullYear() + "").substr(4 - RegExp.$1.length)
+    );
+  for (const k in o)
+    if (new RegExp("(" + k + ")").test(_format))
+      _format = _format.replace(
+        RegExp.$1,
+        RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
+      );
+  return _format;
+}
+
+/**
+ * 将时间转unix时间戳
+ * @param date
+ * @returns {number} 【秒】
+ */
+export function dateToUnix(date) {
+  let newStr = date.replace(/:/g, "-");
+  newStr = newStr.replace(/ /g, "-");
+  const arr = newStr.split("-");
+  const datum = new Date(
+    Date.UTC(
+      arr[0],
+      arr[1] - 1,
+      arr[2],
+      arr[3] - 8 || -8,
+      arr[4] || 0,
+      arr[5] || 0
+    )
+  );
+  return parseInt(datum.getTime() / 1000);
+}
+
+/**
+ * 货币格式化
+ * @param price
+ * @returns {string}
+ */
+export function formatPrice(price) {
+  if (typeof price !== "number") return price;
+  return String(Number(price).toFixed(2)).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+}
+
+/**
+ * 手机号隐私保护
+ * 隐藏中间四位数字
+ * @param mobile
+ * @returns {*}
+ */
+export function secrecyMobile(mobile) {
+  mobile = String(mobile);
+  if (!/\d{11}/.test(mobile)) {
+    return mobile;
+  }
+  return mobile.replace(/(\d{3})(\d{4})(\d{4})/, "$1****$3");
+}
+
+/**
+ * 随机生成指定长度的字符串
+ * @param length
+ * @returns {string}
+ */
+export function randomString(length = 32) {
+  const chars =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+  const maxPos = chars.length;
+  let _string = "";
+  for (let i = 0; i < length; i++) {
+    _string += chars.charAt(Math.floor(Math.random() * maxPos));
+  }
+  return _string;
+}
+
+/**
+ * 计算传秒数的倒计时【天、时、分、秒】
+ * @param seconds
+ * @returns {{day : *, hours : *, minutes : *, seconds : *}}
+ */
+
+export function countTimeDown(seconds) {
+  const leftTime = (time) => {
+    if (time < 10) time = "0" + time;
+    return time + "";
+  };
+  return {
+    day: leftTime(parseInt(seconds / 60 / 60 / 24, 10)),
+    hours: leftTime(parseInt((seconds / 60 / 60) % 24, 10)),
+    minutes: leftTime(parseInt((seconds / 60) % 60, 10)),
+    seconds: leftTime(parseInt(seconds % 60, 10)),
+  };
+}
+
+function navigationToBack(type) {
+  if (type == "wx") {
+    // console.log(getCurrentPages().length - 3)
+    uni.navigateBack({
+      delta: getCurrentPages().length,
+    });
+  } else {
+    uni.switchTab({
+      url: "/pages/tabbar/home/index",
+    });
+  }
+}
+
+/**
+ * 计算当前时间到第二天0点的倒计时[秒]
+ * @returns {number}
+ */
+export function theNextDayTime() {
+  const nowDate = new Date();
+  const time =
+    new Date(
+      nowDate.getFullYear(),
+      nowDate.getMonth(),
+      nowDate.getDate() + 1,
+      0,
+      0,
+      0
+    ).getTime() - nowDate.getTime();
+  return parseInt(time / 1000);
+}
+export default {
+  unixToDate,
+  dateToUnix,
+  formatPrice,
+  secrecyMobile,
+  randomString,
+  countTimeDown,
+  theNextDayTime,
+  whetherNavigate,
+  checkBankno,
+};

+ 537 - 0
utils/filters.js

@@ -0,0 +1,537 @@
+import { logout, logoffConfirm } from "@/api/services/login.js";
+import { getUserInfo } from "@/api/services/members.js";
+import storage from "@/utils/storage.js";
+// import Vue from "vue";
+import { getCurrentInstance } from 'vue'
+// 获取当前组件实例
+const { proxy } = getCurrentInstance()
+import Foundation from "./Foundation.js";
+/**
+ * 金钱单位置换  2999 --> 2,999.00
+ * @param val
+ * @param unit
+ * @param location
+ * @returns {*}
+ */
+export function unitPrice (val, unit, location) {
+  if (!val) val = 0;
+  let price = Foundation.formatPrice(val);
+  if (location === "before") {
+    return price.substr(0, price.length - 3);
+  }
+  if (location === "after") {
+    return price.substr(-2);
+  }
+  return (unit || "") + price;
+}
+
+/**
+ * 格式化价格  1999 --> [1999,00]
+ * @param {*} val
+ * @returns
+ */
+export function goodsFormatPrice (val) {
+  if (typeof val == "undefined") {
+    return val;
+  }
+  let valNum = new Number(val);
+  return valNum.toFixed(2).split(".");
+}
+
+
+/**
+ * 将内容复制到粘贴板
+ */
+import { h5Copy } from "@/utils/js_sdk/h5-copy/h5-copy.js";
+export function setClipboard (val) {
+  // #ifdef H5
+  if (val === null || val === undefined) {
+    val = "";
+  } else val = val + "";
+  const result = h5Copy(val);
+  if (result === false) {
+    uni.showToast({
+      title: "不支持",
+    });
+  } else {
+    uni.showToast({
+      title: "复制成功",
+      icon: "none",
+    });
+  }
+  // #endif
+
+  // #ifndef H5
+  uni.setClipboardData({
+    data: val,
+    success: function () {
+      uni.showToast({
+        title: "复制成功!",
+        duration: 2000,
+        icon: "none",
+      });
+    },
+  });
+  // #endif
+}
+
+/**
+ * 拨打电话
+ */
+
+export function callPhone (phoneNumber) {
+  uni.makePhoneCall({
+    phoneNumber: phoneNumber,
+  });
+}
+
+/**
+ * 脱敏姓名
+ */
+
+export function noPassByName (str) {
+  if (null != str && str != undefined) {
+    if (str.length <= 3) {
+      return "*" + str.substring(1, str.length);
+    } else if (str.length > 3 && str.length <= 6) {
+      return "**" + str.substring(2, str.length);
+    } else if (str.length > 6) {
+      return str.substring(0, 2) + "****" + str.substring(6, str.length);
+    }
+  } else {
+    return "";
+  }
+}
+
+/**
+ * 处理unix时间戳,转换为可阅读时间格式
+ * @param unix
+ * @param format
+ * @returns {*|string}
+ */
+export function unixToDate (unix, format) {
+  let _format = format || "yyyy-MM-dd hh:mm:ss";
+  const d = new Date(unix * 1000);
+  const o = {
+    "M+": d.getMonth() + 1,
+    "d+": d.getDate(),
+    "h+": d.getHours(),
+    "m+": d.getMinutes(),
+    "s+": d.getSeconds(),
+    "q+": Math.floor((d.getMonth() + 3) / 3),
+    S: d.getMilliseconds(),
+  };
+  if (/(y+)/.test(_format))
+    _format = _format.replace(
+      RegExp.$1,
+      (d.getFullYear() + "").substr(4 - RegExp.$1.length)
+    );
+  for (const k in o)
+    if (new RegExp("(" + k + ")").test(_format))
+      _format = _format.replace(
+        RegExp.$1,
+        RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
+      );
+  return _format;
+}
+
+/**
+ * 人性化显示时间
+ *
+ * @param {Object} datetime
+ */
+export function beautifyTime (datetime = "") {
+  if (datetime == null || datetime == undefined || !datetime) {
+    return "";
+  }
+  datetime = timestampToTime(datetime).replace(/-/g, "/");
+
+  let time = new Date();
+  let outTime = new Date(datetime);
+  if (/^[1-9]\d*$/.test(datetime)) {
+    outTime = new Date(parseInt(datetime) * 1000);
+  }
+
+  if (time.getTime() < outTime.getTime()) {
+    return parseTime(outTime, "{y}/{m}/{d}");
+  }
+
+  if (time.getFullYear() != outTime.getFullYear()) {
+    return parseTime(outTime, "{y}/{m}/{d}");
+  }
+
+  if (time.getMonth() != outTime.getMonth()) {
+    return parseTime(outTime, "{m}/{d}");
+  }
+
+  if (time.getDate() != outTime.getDate()) {
+    let day = outTime.getDate() - time.getDate();
+    if (day == -1) {
+      return parseTime(outTime, "昨天 {h}:{i}");
+    }
+
+    if (day == -2) {
+      return parseTime(outTime, "前天 {h}:{i}");
+    }
+
+    return parseTime(outTime, "{m}-{d}");
+  }
+
+  if (time.getHours() != outTime.getHours()) {
+    return parseTime(outTime, "{h}:{i}");
+  }
+
+  let minutes = outTime.getMinutes() - time.getMinutes();
+  if (minutes == 0) {
+    return "刚刚";
+  }
+
+  minutes = Math.abs(minutes);
+  return `${minutes}分钟前`;
+}
+// 时间转换
+function timestampToTime (timestamp) {
+  var date = new Date(timestamp);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
+  var Y = date.getFullYear() + '-';
+  var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
+  var D = date.getDate() + ' ';
+  var h = date.getHours() + ':';
+  var m = date.getMinutes() + ':';
+  var s = date.getSeconds();
+  return Y + M + D + h + m + s;
+}
+
+/**
+ * 13888888888 -> 138****8888
+ * @param mobile
+ * @returns {*}
+ */
+export function secrecyMobile (mobile) {
+  mobile = String(mobile);
+  if (!/\d{11}/.test(mobile)) {
+    return mobile;
+  }
+  return mobile.replace(/(\d{3})(\d{4})(\d{4})/, "$1****$3");
+}
+
+/**
+ * 人性化时间显示
+ *
+ * @param {Object} datetime
+ */
+export function formatTime (datetime) {
+  if (datetime == null) return "";
+
+  datetime = datetime.replace(/-/g, "/");
+
+  let time = new Date();
+  let outTime = new Date(datetime);
+  if (/^[1-9]\d*$/.test(datetime)) {
+    outTime = new Date(parseInt(datetime) * 1000);
+  }
+
+  if (
+    time.getTime() < outTime.getTime() ||
+    time.getFullYear() != outTime.getFullYear()
+  ) {
+    return parseTime(outTime, "{y}-{m}-{d} {h}:{i}");
+  }
+
+  if (time.getMonth() != outTime.getMonth()) {
+    return parseTime(outTime, "{m}-{d} {h}:{i}");
+  }
+
+  if (time.getDate() != outTime.getDate()) {
+    let day = outTime.getDate() - time.getDate();
+    if (day == -1) {
+      return parseTime(outTime, "昨天 {h}:{i}");
+    }
+
+    if (day == -2) {
+      return parseTime(outTime, "前天 {h}:{i}");
+    }
+
+    return parseTime(outTime, "{m}-{d} {h}:{i}");
+  }
+
+  if (time.getHours() != outTime.getHours()) {
+    return parseTime(outTime, "{h}:{i}");
+  }
+
+  let minutes = outTime.getMinutes() - time.getMinutes();
+  if (minutes == 0) {
+    return "刚刚";
+  }
+
+  minutes = Math.abs(minutes);
+  return `${minutes}分钟前`;
+}
+
+/**
+ * 时间格式化方法
+ *
+ * @param {(Object|string|number)} time
+ * @param {String} cFormat
+ * @returns {String | null}
+ */
+export function parseTime (time, cFormat) {
+  if (arguments.length === 0) {
+    return null;
+  }
+
+  let date;
+  const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}";
+
+  if (typeof time === "object") {
+    date = time;
+  } else {
+    if (typeof time === "string" && /^[0-9]+$/.test(time)) {
+      time = parseInt(time);
+    }
+    if (typeof time === "number" && time.toString().length === 10) {
+      time = time * 1000;
+      console.log("时间判断为number");
+    }
+
+    date = new Date(time.replace(/-/g, "/"));
+  }
+
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay(),
+  };
+
+  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
+    const value = formatObj[key];
+    // Note: getDay() returns 0 on Sunday
+    if (key === "a") {
+      return ["日", "一", "二", "三", "四", "五", "六"][value];
+    }
+
+    return value.toString().padStart(2, "0");
+  });
+
+  return time_str;
+}
+
+/**
+ * 清除逗号
+ *
+ */
+export function clearStrComma (str) {
+  str = str.replace(/,/g, ""); //取消字符串中出现的所有逗号
+  return str;
+}
+
+/**
+ * 判断用户是否登录
+ * @param val  如果为auth则判断是否登录
+ * 如果传入 auth 则为判断是否登录
+ */
+export function isLogin (val) {
+  let userInfo = storage.getUserInfo();
+  if (val == "auth") {
+    return userInfo && userInfo.id ? true : false;
+  } else {
+    return storage.getUserInfo();
+  }
+}
+
+/**
+ * 退出登录
+ *
+ */
+export function quiteLoginOut () {
+  uni.showModal({
+    title: "提示",
+    content: "是否退出登录?",
+    confirmColor: '#ff3c2a',
+    async success (res) {
+      if (res.confirm) {
+        storage.setAccessToken("");
+        // storage.setRefreshToken("");
+        storage.setUserInfo({});
+        storage.setHasLogin(false)
+        navigateToLogin("redirectTo");
+        await logout();
+      }
+    },
+  });
+}
+
+/**
+ * 用户注销
+ *
+ */
+export function logoff () {
+  uni.showModal({
+    title: "提示",
+    content: "确认注销用户么?注销用户将无法再次登录并失去当前数据。",
+    confirmColor: getCurrentInstance.proxy.$mainColor,
+    async success (res) {
+      if (res.confirm) {
+        await logoffConfirm();
+        storage.setAccessToken("");
+        storage.setRefreshToken("");
+        storage.setUserInfo({});
+        navigateToLogin("redirectTo");
+      }
+    },
+  });
+}
+
+
+/**
+ * 跳转im
+ */
+export function talkIm (storeId, goodsId, id) {
+  if (isLogin('auth')) {
+    let url = `/pages/mine/im/index?userId=${storeId}`
+    if(goodsId && id) url = `/pages/mine/im/index?userId=${storeId}&goodsid=${goodsId}&skuid=${id}`
+    uni.navigateTo({
+      url
+    });
+  }
+  else {
+    tipsToLogin()
+  }
+}
+
+export function tipsToLogin (type) {
+  if (!isLogin("auth")) {
+    uni.showModal({
+      title: "提示",
+      content: "当前用户未登录是否登录?",
+      confirmText: "确定",
+      cancelText: "取消",
+      confirmColor: getCurrentInstance.proxy.$mainColor,
+      success: (res) => {
+        if (res.confirm) {
+          navigateToLogin();
+        } else if (res.cancel) {
+          if(type !== 'normal'){
+            uni.navigateBack();
+          }
+          
+        }
+      },
+    });
+    return false;
+  }
+  return true;
+}
+
+/**
+ * 获取用户信息并重新添加到缓存里面
+ */
+export async function userInfo () {
+  let res = await getUserInfo();
+  if (res.data.success) {
+    storage.setUserInfo(res.data.result);
+    return res.data.result;
+  }
+}
+
+/**
+ * 验证是否登录如果没登录则去登录
+ * @param {*} val
+ * @returns
+ */
+
+export function forceLogin () {
+  let userInfo = storage.getUserInfo();
+  if (!userInfo || !userInfo.id) {
+    // #ifdef MP-WEIXIN
+
+    uni.navigateTo({
+      url: "/pages/passport/wechatMPLogin",
+    });
+
+    // #endif
+
+    // #ifndef MP-WEIXIN
+
+    uni.navigateTo({
+      url: "/pages/passport/login",
+    });
+
+    //  #endif
+  }
+}
+
+/**
+ * 获取当前加载的页面对象
+ * @param val
+ */
+export function getPages (val) {
+  const pages = getCurrentPages(); //获取加载的页面
+  const currentPage = pages[pages.length - 1]; //获取当前页面的对象
+  const url = currentPage.route; //当前页面url
+
+  return val ? currentPage : url;
+}
+
+/**
+ * 跳转到登录页面
+ */
+export function navigateToLogin (type = "navigateTo") {
+  /**
+   * 此处进行条件编译判断
+   * 微信小程序跳转到微信小程序登录页面
+   * H5/App跳转到普通登录页面
+   */
+  // #ifdef MP-WEIXIN
+  uni[type]({
+    url: "/pages/passport/wechatMPLogin",
+  });
+  // #endif
+  // #ifndef MP-WEIXIN
+  uni[type]({
+    url: "/pages/login/index",
+  });
+  //  #endif
+}
+
+/**
+ * 服务状态列表
+ */
+export function serviceStatusList (val) {
+  let statusList = {
+    APPLY: "申请售后",
+    PASS: "通过售后",
+    REFUSE: "拒绝售后",
+    BUYER_RETURN: "买家退货,待卖家收货",
+    SELLER_RE_DELIVERY: "商家换货/补发",
+    SELLER_CONFIRM: "卖家确认收货",
+    SELLER_TERMINATION: "卖家终止售后",
+    BUYER_CONFIRM: "买家确认收货",
+    BUYER_CANCEL: "买家取消售后",
+    WAIT_REFUND: "等待平台退款",
+    COMPLETE: "完成售后",
+  };
+  return statusList[val];
+}
+
+/**
+ * 订单状态列表
+ */
+export function orderStatusList (val) {
+  let orderStatusList = {
+    UNDELIVERED: "待发货",
+    UNPAID: "未付款",
+    PAID: "已付款",
+    PARTS_DELIVERED: "部分发货",
+    DELIVERED: "已发货",
+    CANCELLED: "已取消",
+    COMPLETED: "已完成",
+    COMPLETE: "已完成",
+    TAKE: "待核验",
+    STAY_PICKED_UP: "待自提",
+  };
+  return orderStatusList[val];
+}

+ 31 - 0
utils/js_sdk/amap-wx.130.js

@@ -0,0 +1,31 @@
+function AMapWX(a){this.key=a.key;this.requestConfig={key:a.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};this.MeRequestConfig={key:a.key,serviceName:"https://restapi.amap.com/rest/me"}}
+AMapWX.prototype.getWxLocation=function(a,b){wx.getLocation({type:"gcj02",success:function(c){c=c.longitude+","+c.latitude;wx.setStorage({key:"userLocation",data:c});b(c)},fail:function(c){wx.getStorage({key:"userLocation",success:function(d){d.data&&b(d.data)}});a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getMEKeywordsSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.city&&(d.city=b.city);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);b.sig&&(d.sig=
+b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/local",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEIdSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.id&&(d.id=b.id);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/id",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&
+0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEPolygonSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.polygon&&(d.polygon=b.polygon);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);
+b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/polygon",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEaroundSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.center&&(d.center=b.center);b.radius&&(d.radius=b.radius);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&
+(d.pageSize=b.pageSize);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/around",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getGeo=function(a){var b=this.requestConfig,c=a.options;b={key:this.key,extensions:"all",s:b.s,platform:b.platform,appname:this.key,sdkversion:b.sdkversion,logversion:b.logversion};c.address&&(b.address=c.address);c.city&&(b.city=c.city);c.batch&&(b.batch=c.batch);c.sig&&(b.sig=c.sig);wx.request({url:"https://restapi.amap.com/v3/geocode/geo",data:b,method:"GET",header:{"content-type":"application/json"},success:function(d){(d=d.data)&&d.status&&"1"===d.status?a.success(d):a.fail({errCode:"0",
+errMsg:d})},fail:function(d){a.fail({errCode:"0",errMsg:d.errMsg||""})}})};
+AMapWX.prototype.getRegeo=function(a){function b(d){var e=c.requestConfig;wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:c.key,location:d,extensions:"all",s:e.s,platform:e.platform,appname:c.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){g=g.data.regeocode;var h=g.addressComponent,f=[],k=g.roads[0].name+"\u9644\u8fd1",m=d.split(",")[0],n=d.split(",")[1];if(g.pois&&
+g.pois[0]){k=g.pois[0].name+"\u9644\u8fd1";var l=g.pois[0].location;l&&(m=parseFloat(l.split(",")[0]),n=parseFloat(l.split(",")[1]))}h.provice&&f.push(h.provice);h.city&&f.push(h.city);h.district&&f.push(h.district);h.streetNumber&&h.streetNumber.street&&h.streetNumber.number?(f.push(h.streetNumber.street),f.push(h.streetNumber.number)):f.push(g.roads[0].name);f=f.join("");a.success([{iconPath:a.iconPath,width:a.iconWidth,height:a.iconHeight,name:f,desc:k,longitude:m,latitude:n,id:0,regeocodeData:g}])}else a.fail({errCode:g.data.infocode,
+errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this;a.location?b(a.location):c.getWxLocation(a,function(d){b(d)})};
+AMapWX.prototype.getWeather=function(a){function b(g){var h="base";a.type&&"forecast"==a.type&&(h="all");wx.request({url:"https://restapi.amap.com/v3/weather/weatherInfo",data:{key:d.key,city:g,extensions:h,s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(f){if(f.data.status&&"1"==f.data.status)if(f.data.lives){if((f=f.data.lives)&&0<f.length){f=f[0];var k={city:{text:"\u57ce\u5e02",data:f.city},
+weather:{text:"\u5929\u6c14",data:f.weather},temperature:{text:"\u6e29\u5ea6",data:f.temperature},winddirection:{text:"\u98ce\u5411",data:f.winddirection+"\u98ce"},windpower:{text:"\u98ce\u529b",data:f.windpower+"\u7ea7"},humidity:{text:"\u6e7f\u5ea6",data:f.humidity+"%"}};k.liveData=f;a.success(k)}}else f.data.forecasts&&f.data.forecasts[0]&&a.success({forecast:f.data.forecasts[0]});else a.fail({errCode:f.data.infocode,errMsg:f.data.info})},fail:function(f){a.fail({errCode:"0",errMsg:f.errMsg||""})}})}
+function c(g){wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:d.key,location:g,extensions:"all",s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(h){if(h.data.status&&"1"==h.data.status){h=h.data.regeocode;if(h.addressComponent)var f=h.addressComponent.adcode;else h.aois&&0<h.aois.length&&(f=h.aois[0].adcode);b(f)}else a.fail({errCode:h.data.infocode,errMsg:h.data.info})},
+fail:function(h){a.fail({errCode:"0",errMsg:h.errMsg||""})}})}var d=this,e=d.requestConfig;a.city?b(a.city):d.getWxLocation(a,function(g){c(g)})};
+AMapWX.prototype.getPoiAround=function(a){function b(e){e={key:c.key,location:e,s:d.s,platform:d.platform,appname:c.key,sdkversion:d.sdkversion,logversion:d.logversion};a.querytypes&&(e.types=a.querytypes);a.querykeywords&&(e.keywords=a.querykeywords);wx.request({url:"https://restapi.amap.com/v3/place/around",data:e,method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){if((g=g.data)&&g.pois){for(var h=[],f=0;f<g.pois.length;f++){var k=0==
+f?a.iconPathSelected:a.iconPath;h.push({latitude:parseFloat(g.pois[f].location.split(",")[1]),longitude:parseFloat(g.pois[f].location.split(",")[0]),iconPath:k,width:22,height:32,id:f,name:g.pois[f].name,address:g.pois[f].address})}a.success({markers:h,poisData:g.pois})}}else a.fail({errCode:g.data.infocode,errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this,d=c.requestConfig;a.location?b(a.location):c.getWxLocation(a,function(e){b(e)})};
+AMapWX.prototype.getStaticmap=function(a){function b(e){c.push("location="+e);a.zoom&&c.push("zoom="+a.zoom);a.size&&c.push("size="+a.size);a.scale&&c.push("scale="+a.scale);a.markers&&c.push("markers="+a.markers);a.labels&&c.push("labels="+a.labels);a.paths&&c.push("paths="+a.paths);a.traffic&&c.push("traffic="+a.traffic);e="https://restapi.amap.com/v3/staticmap?"+c.join("&");a.success({url:e})}var c=[];c.push("key="+this.key);var d=this.requestConfig;c.push("s="+d.s);c.push("platform="+d.platform);
+c.push("appname="+d.appname);c.push("sdkversion="+d.sdkversion);c.push("logversion="+d.logversion);a.location?b(a.location):this.getWxLocation(a,function(e){b(e)})};
+AMapWX.prototype.getInputtips=function(a){var b=Object.assign({},this.requestConfig);a.location&&(b.location=a.location);a.keywords&&(b.keywords=a.keywords);a.type&&(b.type=a.type);a.city&&(b.city=a.city);a.citylimit&&(b.citylimit=a.citylimit);wx.request({url:"https://restapi.amap.com/v3/assistant/inputtips",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.tips&&a.success({tips:c.data.tips})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||
+""})}})};
+AMapWX.prototype.getDrivingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.waypoints&&(b.waypoints=a.waypoints);a.avoidpolygons&&(b.avoidpolygons=a.avoidpolygons);a.avoidroad&&(b.avoidroad=a.avoidroad);wx.request({url:"https://restapi.amap.com/v3/direction/driving",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths,
+taxi_cost:c.data.route.taxi_cost||""})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getWalkingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/walking",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getTransitRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.city&&(b.city=a.city);a.cityd&&(b.cityd=a.cityd);wx.request({url:"https://restapi.amap.com/v3/direction/transit/integrated",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&(c=c.data.route,a.success({distance:c.distance||"",taxi_cost:c.taxi_cost||
+"",transits:c.transits}))},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getRidingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/riding",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};module.exports.AMapWX=AMapWX;

+ 42 - 0
utils/js_sdk/h5-copy/h5-copy.js

@@ -0,0 +1,42 @@
+export  function h5Copy(content) {
+  
+  if (!document.queryCommandSupported('copy')) {
+    // 不支持
+    return false
+  }
+  
+  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") // 执行浏览器复制命令
+  textarea.remove()
+  return result
+  
+}
+
+
+
+/**
+ * 获取系统剪贴板内容
+ */
+ export function getClipboardData() {
+  return new Promise((success, fail) => {
+    // #ifndef H5
+    uni.getClipboardData({
+      success: ({ data }) => success(data),
+      fail
+    })
+    // #endif
+
+    // #ifdef H5
+    try {
+      navigator.clipboard.readText().then(success).catch(fail)
+    } catch (error) {
+      fail(error)
+    }
+    // #endif
+  })
+}

+ 104 - 0
utils/js_sdk/lili-pay/wx-pay.js

@@ -0,0 +1,104 @@
+/**
+ * 微信小程序支付
+ * 此处针对于微信小程序开发的支付插件
+ * 第一次支付成功后会跳出订阅的消息 如果用户拒绝或同意都会跳转到支付成功页面
+ * 如果点击订阅 会将状态写进缓存 之后不再提醒。
+ * 
+ * @param {sn,price}
+ */
+
+import { getWeChatMpMessage } from "@/api/message.js";
+import { initiatePay } from "@/api/trade";
+class LiLiWXPay {
+  constructor(...payList) {
+    this.data = payList[0];
+    console.log(payList);
+    // 调用支付
+    this.pay = () => {
+      uni.showLoading({
+        title: "加载中",
+      });
+
+      let submitData = {
+        sn: this.data.sn,
+        orderType: this.data.orderType || "TRADE",
+        clientType: "WECHAT_MP",
+      };
+      const paymentMethod = "WECHAT";
+      const paymentClient = "MP";
+      // 调用支付
+      initiatePay(paymentMethod, paymentClient, submitData).then((res) => {
+        let response = res.data.result;
+        uni.hideLoading();
+        uni.requestPayment({
+          provider: "wxpay",
+          appid: response.appid,
+          timeStamp: response.timeStamp,
+          nonceStr: response.nonceStr,
+          package: response.package,
+          signType: response.signType,
+          paySign: response.paySign,
+          success: (e) => {
+            uni.showToast({
+              icon: "none",
+              title: "支付成功!",
+            });
+            sendMessage(payList[0].price);
+          },
+          fail: (e) => {
+            this.exception = e;
+            // 支付异常或支付失败之后跳转到订单页面
+            uni.showModal({
+              content: "支付失败,如果您已支付,请勿反复支付",
+              showCancel: false,
+              success: () => {
+                uni.redirectTo({
+                  url: "/pages/order/myOrder?status=0",
+                });
+              },
+            });
+          },
+        });
+      });
+    };
+  }
+}
+
+function sendMessage(price) {
+
+
+  //订阅消息
+  getWeChatMpMessage().then((res) => {
+    var message = res.data.result;
+    var templateid = message.map((item) => item.code);
+    uni.requestSubscribeMessage({
+      tmplIds: templateid,
+      success: (res) => {
+      
+      },
+      fail: (res) => {
+        console.log('fail', res)
+        uni.showToast({
+          icon: "none",
+          title: "订阅消息失败",
+        })
+      },
+      complete: (res) => {
+        console.log('complete', res)
+
+        /**
+         * 已经支付成功
+         */
+        uni.redirectTo({
+          url:
+            "/pages/cart/payment/success?paymentMethod=WECHAT" +
+            "&payPrice=" +
+            price,
+        });
+      },
+    });
+  });
+
+}
+
+export default LiLiWXPay;

+ 78 - 0
utils/js_sdk/t-jwt/jwt.js

@@ -0,0 +1,78 @@
+const b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+const b64re = /^(?:[A-Za-z\d+/]{4})*?(?:[A-Za-z\d+/]{2}(?:==)?|[A-Za-z\d+/]{3}=?)?$/;
+
+export function weBtoa(string) {
+  string = String(string);
+  let bitmap, a, b, c, result = "", i = 0, rest = string.length % 3;
+  for (; i < string.length;) {
+    if ((a = string.charCodeAt(i++)) > 255 ||
+        (b = string.charCodeAt(i++)) > 255 ||
+        (c = string.charCodeAt(i++)) > 255) {
+      throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
+    }
+    bitmap = (a << 16) | (b << 8) | c;
+    result += b64.charAt((bitmap >> 18) & 63)
+            + b64.charAt((bitmap >> 12) & 63)
+            + b64.charAt((bitmap >> 6) & 63)
+            + b64.charAt(bitmap & 63);
+  }
+  return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result;
+}
+
+export function weAtob(string) {
+  string = String(string).replace(/[\t\n\f\r ]+/g, "");
+  if (!b64re.test(string)) {
+    throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
+  }
+  string += "==".slice(2 - (string.length & 3));
+  let bitmap, result = "", r1, r2, i = 0;
+  for (; i < string.length;) {
+    bitmap = (b64.indexOf(string.charAt(i++)) << 18) |
+             (b64.indexOf(string.charAt(i++)) << 12) |
+             ((r1 = b64.indexOf(string.charAt(i++))) << 6) |
+             (r2 = b64.indexOf(string.charAt(i++)));
+    result += r1 === 64
+      ? String.fromCharCode((bitmap >> 16) & 255)
+      : r2 === 64
+        ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255)
+        : String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255, bitmap & 255);
+  }
+  return result;
+}
+
+function b64DecodeUnicode(str) {
+  return decodeURIComponent(
+    weAtob(str).replace(/(.)/g, (p) => {
+      let code = p.charCodeAt(0).toString(16).toUpperCase();
+      if (code.length < 2) code = "0" + code;
+      return "%" + code;
+    })
+  );
+}
+
+function base64_url_decode(str) {
+  let output = str.replace(/-/g, "+").replace(/_/g, "/");
+  switch (output.length % 4) {
+    case 0: break;
+    case 2: output += "=="; break;
+    case 3: output += "="; break;
+    default: throw new Error("Illegal base64url string!");
+  }
+  try {
+    return b64DecodeUnicode(output);
+  } catch (err) {
+    return weAtob(output);
+  }
+}
+
+export default function jwt(token, options = {}) {
+  if (typeof token !== "string") {
+    throw new Error("Invalid token specified");
+  }
+  const pos = options.header === true ? 0 : 1;
+  try {
+    return JSON.parse(base64_url_decode(token.split(".")[pos]));
+  } catch (e) {
+    throw new Error("Invalid token specified: " + e.message);
+  }
+}

+ 38 - 0
utils/js_sdk/u-draw-poster/draw-poster.d.ts

@@ -0,0 +1,38 @@
+import { Canvas, DrawPosterCanvasCtx, CreateImagePathOptions, DrawPosterBuildOpts, DrawPosterUseOpts, drawPosterExtends, DrawPosterUseCtxOpts } from "./utils/interface";
+declare type DrawPosterInstanceType = InstanceType<typeof DrawPoster> & drawPosterExtends;
+declare class DrawPoster {
+    canvas: Canvas;
+    ctx: DrawPosterCanvasCtx;
+    canvasId: string;
+    loading: boolean;
+    debugging: boolean;
+    loadingText: string;
+    createText: string;
+    [key: string]: any;
+    private executeOnions;
+    private stopStatus;
+    private drawType;
+    /** 构建器, 构建返回当前实例, 并挂载多个方法 */
+    constructor(canvas: Canvas, ctx: DrawPosterCanvasCtx, canvasId: string, loading: boolean, debugging: boolean, loadingText: string, createText: string, tips: boolean);
+    /** 提示器, 传入消息与数据 */
+    private debuggingLog;
+    /** 传入挂载配置对象, 添加扩展方法 */
+    static use: (opts: DrawPosterUseOpts) => void;
+    /** 传入挂载配置对象, 添加绘画扩展方法 */
+    static useCtx: (opts: DrawPosterUseCtxOpts) => void;
+    /** 构建绘制海报矩形方法, 传入canvas选择器或配置对象, 返回绘制对象 */
+    static build: (options: string | DrawPosterBuildOpts, tips?: boolean) => Promise<DrawPosterInstanceType>;
+    /** 构建多个绘制海报矩形方法, 传入选择器或配置对象的数组, 返回多个绘制对象 */
+    static buildAll: (optionsAll: (string | DrawPosterBuildOpts)[]) => Promise<{
+        [key: string]: DrawPosterInstanceType;
+    }>;
+    /** 绘制器, 接收执行器函数, 添加到绘制容器中 */
+    draw: (execute: (ctx: DrawPosterCanvasCtx) => Promise<any> | void) => void;
+    /** 等待创建绘画, 成功后清空绘制器容器 */
+    awaitCreate: () => Promise<boolean[]>;
+    /** 创建canvas本地地址 @returns {string} 本地地址 */
+    createImagePath: (baseOptions?: CreateImagePathOptions) => Promise<string>;
+    /** 停止当前绘画, 调用则停止当前绘画堆栈的绘画 */
+    stop: () => void;
+}
+export default DrawPoster;

+ 194 - 0
utils/js_sdk/u-draw-poster/draw-poster.js

@@ -0,0 +1,194 @@
+import uni from "./utils/global";
+import { handleBuildOpts, extendMount } from "./utils/utils";
+import { getCanvas2dContext } from "./utils/wx-utils";
+// 扩展挂载储存
+let drawPosterExtend = {};
+let drawCtxPosterExtend = {};
+class DrawPoster {
+    /** 构建器, 构建返回当前实例, 并挂载多个方法 */
+    constructor(canvas, ctx, canvasId, loading, debugging, loadingText, createText, tips) {
+        var _a;
+        this.canvas = canvas;
+        this.ctx = ctx;
+ 
+        this.canvasId = canvasId;
+        this.loading = loading;
+        this.debugging = debugging;
+        this.loadingText = loadingText;
+        this.createText = createText;
+        this.executeOnions = [];
+        this.stopStatus = false;
+        /** 提示器, 传入消息与数据 */
+        this.debuggingLog = (message, data, color = "#3489fd") => {
+            if (this.debugging) {
+                if (data) {
+                    console.log(`%c${this.canvasId} -> ${message}`, `color: ${color}`, data);
+                }
+                else {
+                    console.log(`%c${this.canvasId} -> ${message}`, `color: ${color}`);
+                }
+            }
+        };
+        /** 绘制器, 接收执行器函数, 添加到绘制容器中 */
+        this.draw = (execute) => {
+            const length = this.executeOnions.length;
+            this.executeOnions.push(async () => {
+                var _a, _b;
+                try {
+                    this.ctx.save();
+                    await execute(this.ctx);
+                    this.ctx.restore();
+                    return true;
+                }
+                catch (error) {
+                    const isOutError = ((_b = (_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.search) === null || _b === void 0 ? void 0 : _b.call(_a, `'nodeId' of undefined`)) >= 0;
+                    !isOutError && console.error(`${this.canvasId} -> 绘画栈(${length}),绘制错误:`, error);
+                    return false;
+                }
+            });
+        };
+        /** 等待创建绘画, 成功后清空绘制器容器 */
+        this.awaitCreate = async () => {
+            this.debuggingLog('绘制海报中...');
+            this.loading && uni.showLoading({ title: this.loadingText });
+            const tips = [];
+            for (let i = 0; i < this.executeOnions.length; i++) {
+                const execute = this.executeOnions[i];
+                tips.push(await execute());
+            }
+            this.executeOnions = [];
+            this.debuggingLog('绘制状况', tips);
+            // 当前绘制为 type2 绘制
+            if (this.drawType === 'type2d') {
+                this.loading && uni.hideLoading();
+            
+                return tips;
+            }
+            // 当前绘制为 context 绘制
+            return await new Promise((resolve) => {
+                this.ctx.draw(true, () => {
+                    resolve(tips);
+                    this.loading && uni.hideLoading();
+                });
+            });
+        };
+        /** 创建canvas本地地址 @returns {string} 本地地址 */
+        this.createImagePath = async (baseOptions = {}) => {
+            const { canvas, canvasId, executeOnions, awaitCreate } = this;
+            executeOnions.length && await awaitCreate();
+            // 如果当前为停止状态
+            if (this.stopStatus) {
+                this.stopStatus = false;
+                return '---stop createImagePath---';
+            }
+            this.loading && uni.showLoading({ title: this.createText });
+            const options = Object.assign({}, baseOptions);
+            if (this.drawType === 'context')
+                options.canvasId = canvasId;
+            if (this.drawType === 'type2d')
+                options.canvas = canvas;
+
+                console.log(options)
+            return new Promise((resolve, reject) => {
+                options.success = (res) => {
+                    resolve(res.tempFilePath);
+                    this.loading && uni.hideLoading();
+                    this.debuggingLog('绘制成功 🎉', res, '#19be6b');
+                };
+                options.fail = (err) => {
+                    reject(err);
+                    this.loading && uni.hideLoading();
+                    this.debuggingLog('绘制失败 🌟', err, '#fa3534');
+                };
+                uni.canvasToTempFilePath(options);
+            });
+        };
+        /** 停止当前绘画, 调用则停止当前绘画堆栈的绘画 */
+        this.stop = () => {
+            this.executeOnions = [];
+            this.stopStatus = true;
+        };
+        if (!canvas || !ctx || !canvasId) {
+            throw new Error("DrawPoster Error: Use DrawPoster.build(string | ops) to build drawPoster instance objects");
+        }
+        // 判断当前绘制类型
+        ctx.drawType = this.drawType = (ctx.draw) ? 'context' : 'type2d';
+        // 挂载全局实例, 绘画扩展
+        extendMount(this.ctx, drawCtxPosterExtend, (extend, target) => {
+            var _a;
+            (_a = target === null || target === void 0 ? void 0 : target.init) === null || _a === void 0 ? void 0 : _a.call(target, this.canvas, this.ctx);
+            return (...args) => extend(this.canvas, this.ctx, ...args);
+        });
+        extendMount(this, drawPosterExtend, (extend, target) => {
+            var _a;
+            (_a = target === null || target === void 0 ? void 0 : target.init) === null || _a === void 0 ? void 0 : _a.call(target, this);
+            return (...args) => extend(this, ...args);
+        });
+        // 当离开页面时, 自动调用停止绘画
+        const _this = this;
+        const pages = getCurrentPages();
+        const page = pages[pages.length - 1];
+        // 查询标识, 不存在, 在替换页面卸载回调, 避免产生死循环
+        if (!((_a = page === null || page === void 0 ? void 0 : page.onUnload) === null || _a === void 0 ? void 0 : _a.identification)) {
+            page.oldOnUnload = page.onUnload;
+            page.onUnload = function () {
+                _this === null || _this === void 0 ? void 0 : _this.stop();
+                page.oldOnUnload();
+            };
+            page.onUnload.identification = true;
+        }
+        tips && this.debuggingLog('构建完成', { canvas, ctx, selector: canvasId }, '#19be6b');
+    }
+}
+/** 传入挂载配置对象, 添加扩展方法 */
+DrawPoster.use = (opts) => {
+    if (opts.name)
+        drawPosterExtend[opts.name] = opts;
+};
+/** 传入挂载配置对象, 添加绘画扩展方法 */
+DrawPoster.useCtx = (opts) => {
+    if (opts.name)
+        drawCtxPosterExtend[opts.name] = opts;
+};
+/** 构建绘制海报矩形方法, 传入canvas选择器或配置对象, 返回绘制对象 */
+DrawPoster.build = async (options, tips = true) => {
+    var _a, _b, _c, _d, _e;
+    const config = handleBuildOpts(options);
+    // 初始化监测当前页面绘制对象
+    const pages = getCurrentPages();
+    const page = pages[pages.length - 1];
+    const gcanvas = DrawPoster.prototype['gcanvas'];
+    if (page[config.selector + '__dp']) {
+        return page[config.selector + '__dp'];
+    }
+    if (config.gcanvas) {
+        if (!gcanvas)
+            console.error('--- 当前未引入gcanvas扩展, 将自动切换为普通 canvas ---');
+        else
+            gcanvas.enable((_b = (_a = config.componentThis) === null || _a === void 0 ? void 0 : _a.$refs) === null || _b === void 0 ? void 0 : _b[config.selector], {
+                bridge: gcanvas.WeexBridge
+            });
+    }
+    // 获取canvas实例
+    const canvas = config.gcanvas && gcanvas ?
+        gcanvas.enable((_d = (_c = config.componentThis) === null || _c === void 0 ? void 0 : _c.$refs) === null || _d === void 0 ? void 0 : _d[config.selector], {
+            bridge: gcanvas.WeexBridge
+        }) :
+        await getCanvas2dContext(config.selector, config.componentThis);
+    const ctx = (((_e = canvas.getContext) === null || _e === void 0 ? void 0 : _e.call(canvas, "2d")) || uni.createCanvasContext(config.selector, config.componentThis));
+    const dp = new DrawPoster(canvas, ctx, config.selector, config.loading, config.debugging, config.loadingText, config.createText, tips);
+    // 储存当前绘制对象
+    page[config.selector + '__dp'] = dp;
+    return page[config.selector + '__dp'];
+};
+/** 构建多个绘制海报矩形方法, 传入选择器或配置对象的数组, 返回多个绘制对象 */
+DrawPoster.buildAll = async (optionsAll) => {
+    const dpsArr = await Promise.all(optionsAll.map(async (options) => {
+        return await DrawPoster.build(options, false);
+    }));
+    const dpsObj = {};
+    dpsArr.forEach(dp => dpsObj[dp.canvasId] = dp);
+    console.log("%cdraw-poster 构建完成:", "#E3712A", dpsObj);
+    return dpsObj;
+};
+export default DrawPoster;

+ 17 - 0
utils/js_sdk/u-draw-poster/extends/create-from-list/index.d.ts

@@ -0,0 +1,17 @@
+import { DrawPosterUseOpts } from '../../utils/interface';
+export interface CreateLayerOpts {
+    background?: string;
+    self?: boolean;
+    line?: boolean;
+    lineHeight?: number;
+}
+export interface DrawRowOpt {
+    text?: string;
+    font?: string;
+    color?: string;
+    center?: boolean;
+    width?: number;
+}
+declare const _default: DrawPosterUseOpts;
+/** 绘制表单扩展方法 */
+export default _default;

+ 140 - 0
utils/js_sdk/u-draw-poster/extends/create-from-list/index.js

@@ -0,0 +1,140 @@
+/** 绘制表单扩展方法 */
+export default {
+    name: 'createLayer',
+    init: (dp) => {
+        dp.from = {
+            height: 0,
+            padding: 8,
+            margin: 0
+        };
+        dp.setFromOptions = (opts) => {
+            if (typeof opts.height !== 'undefined') {
+                dp.from.height = opts.height;
+            }
+            if (typeof opts.margin !== 'undefined') {
+                dp.from.margin = opts.margin;
+            }
+            if (typeof opts.padding !== 'undefined') {
+                dp.from.padding = opts.padding;
+            }
+        };
+    },
+    handle: (dp, afferOpts, rowList) => {
+        // 当前配置(头部偏移量, 列内边距, 表单外边距)
+        const height = dp.from.height;
+        const margin = dp.from.margin;
+        const padding = dp.from.padding;
+        // 当前层宽度
+        const containerWidth = dp.canvas.width - (margin * 2);
+        // 基本层配置
+        const opts = Object.assign({ background: "#fff", columnY: height || margin, self: true, line: true, lineHeight: 0, border: true }, afferOpts);
+        // 基本列配置
+        const baseRowOpts = {
+            text: "",
+            font: "24px sans-serif",
+            color: "#333",
+            center: false,
+            width: 0,
+        };
+        // 累计最高的列为标准定义为层高度
+        let maxRowHeight = 0;
+        // 累计固定栅格列偏移量
+        let columnOffsetX = margin;
+        // 创建行绘制任务
+        const drawLayerInfos = rowList.map((afferRowOpts = {}, index) => {
+            const rowOpts = Object.assign(Object.assign({}, baseRowOpts), afferRowOpts);
+            let columnX = 0; // 每列的X轴
+            let columnW = 0; // 每列的宽度
+            let fontOffsetX = 0; // 字体偏移X轴
+            let fontMaxWidth = 100; // 字体最大宽度
+            opts.lineHeight = opts.lineHeight || Number(rowOpts.font.replace(/[^0-9.]/g, ""));
+            if (opts.self) {
+                // 自适应栅格格子计算
+                columnX = containerWidth - (containerWidth / (index + 1)) + margin;
+                columnW = containerWidth / rowList.length;
+                if (columnX > 0 && columnX < containerWidth - columnW) {
+                    columnX = (columnW * index) + margin;
+                }
+                fontOffsetX = rowOpts.center ? columnX + (columnW / 2) : columnX + padding;
+                fontMaxWidth = columnW - (padding * 3);
+            }
+            if (!opts.self) {
+                // 固定栅格格子计算
+                columnW = rowOpts.width;
+                columnX = columnOffsetX;
+                fontMaxWidth = columnW - (padding * 3);
+                fontOffsetX = rowOpts.center ? columnOffsetX + (rowOpts.width / 2) : columnOffsetX + padding;
+                columnOffsetX += rowOpts.width;
+            }
+            dp.ctx.font = rowOpts.font;
+            const drawFontInfos = dp.ctx.fillWarpText({
+                text: rowOpts.text,
+                maxWidth: fontMaxWidth,
+                lineHeight: opts.lineHeight,
+                x: fontOffsetX,
+                y: opts.columnY,
+                layer: 10,
+                notFillText: true
+            });
+            // 当前行的高度
+            const rowHeight = (opts.lineHeight * drawFontInfos.length) + (padding * 3);
+            // 若该列高度大于累计高度, 将累计高度替换
+            if (rowHeight > maxRowHeight) {
+                maxRowHeight = rowHeight;
+            }
+            return {
+                font: rowOpts.font,
+                center: rowOpts.center,
+                color: rowOpts.color,
+                border: opts.border,
+                background: opts.background,
+                lineHeight: opts.lineHeight,
+                line: opts.line,
+                drawFontInfos,
+                columnY: opts.columnY,
+                columnX,
+                columnW,
+                columnH: maxRowHeight,
+                margin,
+                padding
+            };
+        });
+        // 将行绘制任务添加至绘制容器中
+        dp.draw((ctx) => drawLayerInfos.forEach((rowOpts, index) => {
+            ctx.font = rowOpts.font;
+            ctx.fillStyle = rowOpts.background;
+            ctx.strokeStyle = "#333";
+            ctx.textBaseline = "middle";
+            ctx.textAlign = 'left';
+            if (rowOpts.center) {
+                ctx.textAlign = "center";
+            }
+            ctx.fillRect(rowOpts.columnX, rowOpts.columnY, rowOpts.columnW, rowOpts.columnH);
+            if (rowOpts.border) {
+                dp.ctx.strokeRect(margin, rowOpts.columnY, dp.canvas.width - margin, maxRowHeight);
+            }
+            if (rowOpts.line && rowOpts.columnX !== margin) {
+                ctx.moveTo(rowOpts.columnX, rowOpts.columnY);
+                ctx.lineTo(rowOpts.columnX, rowOpts.columnY + rowOpts.columnH);
+                ctx.stroke();
+                ctx.beginPath();
+            }
+            ctx.fillStyle = rowOpts.color;
+            rowOpts.drawFontInfos.forEach(fontInfo => {
+                // 计算每行字体绘制y轴长度
+                // y(当前列置顶轴) + (rowOpts.columnH(当前列最高长度) / 2) - (((总列数-1) * 行高) / 2)
+                const textTotal = rowOpts.drawFontInfos.length - 1;
+                const textMiddleY = (textTotal * rowOpts.lineHeight) / 2;
+                let fontOffsetY = fontInfo.y + (rowOpts.columnH / 2);
+                fontOffsetY -= textMiddleY;
+                ctx.fillText(fontInfo.text, fontInfo.x, fontOffsetY);
+            });
+        }));
+        if (opts.columnY === 0 || opts.columnY === margin) {
+            maxRowHeight += margin;
+        }
+        // 叠加高度
+        dp.from.height += maxRowHeight;
+        return maxRowHeight;
+    },
+};

+ 4 - 0
utils/js_sdk/u-draw-poster/extends/create-gcanvas/index.d.ts

@@ -0,0 +1,4 @@
+import { DrawPosterUseOpts } from '../../utils/interface';
+export * from './gcanvas';
+declare const _default: DrawPosterUseOpts;
+export default _default;

+ 9 - 0
utils/js_sdk/u-draw-poster/extends/create-gcanvas/index.js

@@ -0,0 +1,9 @@
+import { WeexBridge, enable, Image } from './gcanvas';
+export * from './gcanvas';
+import DrawPoster from "../../draw-poster";
+DrawPoster.prototype['gcanvas'] = {
+    WeexBridge,
+    enable,
+    Image
+};
+export default {};

+ 12 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/draw-image-fit.d.ts

@@ -0,0 +1,12 @@
+import { DrawPosterUseCtxOpts } from '../../utils/interface';
+import { ObjectFit, ObjectPosition, Size } from "../../utils/object-sizing";
+export interface ImageFitOption {
+    radius?: number;
+    objectFit?: ObjectFit;
+    intrinsicSize?: Size;
+    specifiedSize?: Size;
+    intrinsicPosition?: ObjectPosition;
+    specifiedPosition?: [number, number];
+}
+declare const _default: DrawPosterUseCtxOpts;
+export default _default;

+ 25 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/draw-image-fit.js

@@ -0,0 +1,25 @@
+import { calculateConcreteRect } from "../../utils/object-sizing";
+import uni from "../../utils/global";
+export default {
+    name: 'drawImageFit',
+    handle: async (canvas, ctx, url, options) => {
+        var _a, _b, _c;
+        const [error, imageInfo] = await uni.getImageInfo({ src: url });
+        // 配置默认值
+        const style = Object.assign({ radius: 0, objectFit: 'cover', intrinsicSize: { width: (_a = imageInfo === null || imageInfo === void 0 ? void 0 : imageInfo.width) !== null && _a !== void 0 ? _a : 100, height: (_b = imageInfo === null || imageInfo === void 0 ? void 0 : imageInfo.height) !== null && _b !== void 0 ? _b : 100 }, specifiedSize: { width: 100, height: 100 }, intrinsicPosition: ['center', 'center'], specifiedPosition: [0, 0] }, options);
+        // 计算图片尺寸
+        const drawImageInfo = calculateConcreteRect(style, style.intrinsicSize, style.specifiedSize);
+        // 如有圆角, 则进行裁剪
+        if (style.radius > 0) {
+            ctx.save();
+            (_c = ctx.setFillStyle) === null || _c === void 0 ? void 0 : _c.call(ctx, 'transparent');
+            ctx.fillStyle = 'transparent';
+            ctx.fillRoundRect(style.specifiedPosition[0], style.specifiedPosition[1], style.specifiedSize.width, style.specifiedSize.height, style.radius);
+            ctx.clip();
+        }
+        const result = await ctx.drawImage(url, ...Object.values(drawImageInfo));
+        if (style.radius > 0)
+            ctx.restore();
+        return result;
+    }
+};

+ 4 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/draw-image.d.ts

@@ -0,0 +1,4 @@
+import { DrawPosterUseCtxOpts } from '../../utils/interface';
+declare const _default: DrawPosterUseCtxOpts;
+/** 等待绘制图片原型方法 */
+export default _default;

+ 42 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/draw-image.js

@@ -0,0 +1,42 @@
+import { downloadImgUrl } from '../../utils/wx-utils';
+/** 等待绘制图片原型方法 */
+export default {
+    name: 'drawImage',
+    init: (canvas, ctx) => {
+        ctx.drawImageProto = ctx.drawImage;
+    },
+    handle: async (canvas, ctx, url, sx, sy, sh, sw, dx, dy, dh, dw) => {
+        // 下载路径
+        const path = await downloadImgUrl(url);
+        // 标记当前绘画存在图片绘制
+        let result = false;
+        // 基本绘制方法, 如果是 fit 方式, 则传入所有参数, 不然则只传入四个参数
+        const baseDrawImage = (imageResource) => {
+            const isFit = typeof dx === 'number' && typeof dw === 'number';
+            if (isFit) {
+                ctx.drawImageProto(imageResource, sx, sy, sh, sw, dx, dy, dh, dw);
+            }
+            else {
+                ctx.drawImageProto(imageResource, sx, sy, sh, sw);
+            }
+        };
+        // 如果是 context 绘制方式, 则直接绘制
+        if (ctx.drawType === 'context') {
+            baseDrawImage(path);
+            result = true;
+        }
+        // 如果是 type2d 绘制方式, 则等待图片绘制完毕
+        if (ctx.drawType === 'type2d') {
+            result = await new Promise(resolve => {
+                const image = canvas.createImage();
+                image.src = path;
+                image.onload = () => {
+                    baseDrawImage(image);
+                    resolve(true);
+                };
+                image.onerror = () => resolve(false);
+            });
+        }
+        return result;
+    }
+};

+ 4 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/draw-round-image.d.ts

@@ -0,0 +1,4 @@
+import { DrawPosterUseCtxOpts } from '../../utils/interface';
+declare const _default: DrawPosterUseCtxOpts;
+/** 绘制圆角图片原型方法 */
+export default _default;

+ 15 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/draw-round-image.js

@@ -0,0 +1,15 @@
+/** 绘制圆角图片原型方法 */
+export default {
+    name: 'drawRoundImage',
+    handle: async (canvas, ctx, url, x, y, w, h, r = 15) => {
+        var _a;
+        ctx.save();
+        (_a = ctx.setFillStyle) === null || _a === void 0 ? void 0 : _a.call(ctx, 'transparent');
+        ctx.fillStyle = 'transparent';
+        ctx.fillRoundRect(x, y, w, h, r);
+        ctx.clip();
+        const result = await ctx.drawImage(url, x, y, w, h);
+        ctx.restore();
+        return result;
+    }
+};

+ 4 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/fill-round-rect.d.ts

@@ -0,0 +1,4 @@
+import { DrawPosterUseCtxOpts } from '../../utils/interface';
+declare const _default: DrawPosterUseCtxOpts;
+/** 绘制填充圆角矩形方法 */
+export default _default;

+ 7 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/fill-round-rect.js

@@ -0,0 +1,7 @@
+/** 绘制填充圆角矩形方法 */
+export default {
+    name: 'fillRoundRect',
+    handle: (canvas, ctx, x, y, w, h, r) => {
+        ctx.roundRect(x, y, w, h, r, true);
+    }
+};

+ 4 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/fill-warp-text.d.ts

@@ -0,0 +1,4 @@
+import { DrawPosterUseCtxOpts } from '../../utils/interface';
+declare const _default: DrawPosterUseCtxOpts;
+/** 绘制换行字体原型方法 */
+export default _default;

+ 76 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/fill-warp-text.js

@@ -0,0 +1,76 @@
+/** 绘制换行字体原型方法 */
+export default {
+    name: 'fillWarpText',
+    handle: (canvas, ctx, config) => {
+        const newConfig = config = Object.assign({ maxWidth: 100, layer: 2, lineHeight: Number(ctx.font.replace(/[^0-9.]/g, '')), x: 0, y: Number(ctx.font.replace(/[^0-9.]/g, '')) / 1.2, splitText: '', notFillText: false }, config);
+        const { text, splitText, maxWidth, layer, lineHeight, notFillText, x, y } = newConfig;
+        // 当字符串为空时, 抛出错误
+        if (!text) {
+            throw Error('warpFillText Error: text is empty string');
+        }
+        // 分割所有单个字符串
+        const chr = text.split(splitText);
+        // 存入的每行字体的容器
+        let row = [];
+        // 判断字符串
+        let timp = '';
+        if (splitText) {
+            row = chr;
+        }
+        else {
+            // 遍历所有字符串, 填充行容器
+            for (let i = 0; i < chr.length; i++) {
+                // 当超出行列时, 停止执行遍历, 节省计算时间
+                if (row.length > layer) {
+                    break;
+                }
+                if (ctx.measureText(timp).width < maxWidth) {
+                    // 如果超出长度, 添加进row数组
+                    timp += chr[i];
+                }
+                else {
+                    // 如超出一行长度, 则换行, 并清除容器
+                    i--;
+                    row.push(timp);
+                    timp = '';
+                }
+            }
+            // 如有剩下字体, 则在最后时添加一行
+            if (timp) {
+                row.push(timp);
+            }
+            // 如果数组长度大于指定行数
+            if (row.length > layer) {
+                row = row.slice(0, layer);
+                // 结束的索引
+                const end = layer - 1;
+                for (let i = 0; i < row[end].length; i++) {
+                    const currentWidth = ctx.measureText(`${row[end]}...`).width;
+                    if (currentWidth > maxWidth) {
+                        // 加上... 当前宽度大于最大宽度时, 去除一位字符串
+                        const strEnd = row[end].length - 1;
+                        row[end] = row[end].slice(0, strEnd);
+                    }
+                    else {
+                        row[end] += '...';
+                        break;
+                    }
+                }
+            }
+        }
+        // 储存并返回绘制信息
+        const drawInfos = row.map((item, index) => {
+            const info = {
+                text: item,
+                y: y + index * lineHeight,
+                x: x,
+            };
+            // 默认执行绘制信息
+            if (!notFillText) {
+                ctx.fillText(info.text, info.x, info.y);
+            }
+            return info;
+        });
+        return drawInfos;
+    }
+};

+ 7 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/index.d.ts

@@ -0,0 +1,7 @@
+export { default as drawImage } from "./draw-image";
+export { default as roundRect } from "./round-rect";
+export { default as fillRoundRect } from "./fill-round-rect";
+export { default as strokeRoundRect } from "./stroke-round-rect";
+export { default as fillWarpText } from "./fill-warp-text";
+export { default as drawRoundImage } from "./draw-round-image";
+export { default as drawImageFit } from "./draw-image-fit";

+ 15 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/index.js

@@ -0,0 +1,15 @@
+/*
+ * @Author: Mr.Mao
+ * @LastEditors: Mr.Mao
+ * @Date: 2020-11-11 20:43:33
+ * @LastEditTime: 2021-01-02 00:16:59
+ * @Description:
+ * @任何一个傻子都能写出让电脑能懂的代码,而只有好的程序员可以写出让人能看懂的代码
+ */
+export { default as drawImage } from "./draw-image";
+export { default as roundRect } from "./round-rect";
+export { default as fillRoundRect } from "./fill-round-rect";
+export { default as strokeRoundRect } from "./stroke-round-rect";
+export { default as fillWarpText } from "./fill-warp-text";
+export { default as drawRoundImage } from "./draw-round-image";
+export { default as drawImageFit } from "./draw-image-fit";

+ 4 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/round-rect.d.ts

@@ -0,0 +1,4 @@
+import { DrawPosterUseCtxOpts } from '../../utils/interface';
+declare const _default: DrawPosterUseCtxOpts;
+/** 绘制圆角矩形原型方法 */
+export default _default;

+ 41 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/round-rect.js

@@ -0,0 +1,41 @@
+/** 绘制圆角矩形原型方法 */
+export default {
+    name: 'roundRect',
+    handle: (canvas, ctx, x, y, w, h, r = 15, fill = false, stroke = false) => {
+        if (r === 0) {
+            if (stroke)
+                ctx.strokeRect(x, y, w, h);
+            if (fill)
+                ctx.fillRect(x, y, w, h);
+            return;
+        }
+        if (w < 2 * r) {
+            r = w / 2;
+        }
+        if (h < 2 * r) {
+            r = h / 2;
+        }
+        // 开始绘制
+        ctx.beginPath();
+        ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
+        // 移动复制
+        ctx.moveTo(x + r, y);
+        ctx.lineTo(x + w - r, y);
+        ctx.lineTo(x + w, y + r);
+        // (x,y,z,j,f) x,y圆心z半径,j起始弧度f,终止弧度
+        ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
+        ctx.lineTo(x + w, y + h - r);
+        ctx.lineTo(x + w - r, y + h);
+        ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
+        ctx.lineTo(x + r, y + h);
+        ctx.lineTo(x, y + h - r);
+        ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
+        ctx.lineTo(x, y + r);
+        ctx.lineTo(x + r, y);
+        if (stroke)
+            ctx.stroke();
+        if (fill)
+            ctx.fill();
+        ctx.closePath();
+    }
+};

+ 4 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/stroke-round-rect.d.ts

@@ -0,0 +1,4 @@
+import { DrawPosterUseCtxOpts } from '../../utils/interface';
+declare const _default: DrawPosterUseCtxOpts;
+/** 绘制填充圆角矩形方法 */
+export default _default;

+ 7 - 0
utils/js_sdk/u-draw-poster/extends/draw-function/stroke-round-rect.js

@@ -0,0 +1,7 @@
+/** 绘制填充圆角矩形方法 */
+export default {
+    name: 'strokeRoundRect',
+    handle: (canvas, ctx, x, y, w, h, r) => {
+        ctx.roundRect(x, y, w, h, r, false, true);
+    }
+};

+ 101 - 0
utils/js_sdk/u-draw-poster/extends/draw-painter/index.d.ts

@@ -0,0 +1,101 @@
+import { DrawPosterUseOpts } from '../../utils/interface';
+import { ImageFitOption } from '../draw-function/draw-image-fit';
+/** 矩形基本信息 */
+interface PainterItemSize {
+    /** 容器的宽度,固定值 */
+    width: number;
+    /** 容器的高度,固定值 */
+    height: number;
+}
+/** 元素位置信息 */
+interface PainterItemSite {
+    /** 元素锚点距左边的距离; 默认: 0 */
+    left?: number;
+    /** 元素锚点距上边的距离; 默认: 0 */
+    top?: number;
+}
+/** 绘制图片信息 */
+interface PainterImageInfo extends PainterItemSize, PainterItemSite {
+    /** 绘制图片元素 */
+    type: 'image';
+    /** 图片地址 */
+    src: string;
+    /** 图片自适应, 可参考 css 属性 object-fit */
+    objectFit?: ImageFitOption['objectFit'];
+    /** 图片在元素容器中显示的位置,可参考 css 属性 object-position */
+    position?: ImageFitOption['intrinsicPosition'];
+    /** 圆角尺寸; 默认: 0 */
+    radius?: number;
+}
+/** 绘制矩形信息 */
+interface PainterRectInfo extends PainterItemSize, PainterItemSite {
+    /** 绘制矩形元素 */
+    type: "rect";
+    /** 矩形背景颜色; 默认: "#000" */
+    background?: string;
+    /** 圆角尺寸; 默认: 0 */
+    radius?: number;
+}
+/** 绘制单行文字信息 */
+interface PainterTextInfo extends PainterItemSite {
+    /** 绘制文本元素 */
+    type: "text";
+    /** 文本颜色; 默认: "#000" */
+    color?: string;
+    /** 字体; 默认: "serial" */
+    fontFamily?: string;
+    /** 字号(单位rpx); 默认: 30 rpx */
+    fontSize?: number;
+    /** 字重; 默认: "normal" 可选项: "bold" */
+    fontWeight?: string;
+    /** 字型 默认: "normal" 可选项: "italic" */
+    fontStyle?: string;
+    /** 元素的宽度(单位rpx), 水平排布时影响后一个元素的位置,为 null 时根据文字实际占用的宽度计算 */
+    width?: number;
+    /** 文本内容 */
+    content: string;
+}
+/** 绘制多行文字信息 */
+interface PainterLineFeedTextInfo extends PainterItemSite {
+    /** 绘制换行文本元素 */
+    type: "line-feed-text";
+    /** 文本颜色; 默认: "#000" */
+    color?: string;
+    /** 字体; 默认: "serial" */
+    fontFamily?: string;
+    /** 字号(单位rpx); 默认: 30 rpx */
+    fontSize?: number;
+    /** 字重; 默认: "normal" 可选项: "bold" */
+    fontWeight?: string;
+    /** 字型 默认: "normal" 可选项: "italic" */
+    fontStyle?: string;
+    /** 文本块的宽度,不能为空 */
+    width: number;
+    /** 行高; 默认取当前文字行高 */
+    lineHeight?: number;
+    /** 文本最大行数,超出即显示省略号; 默认3行 */
+    lineClamp?: number;
+    /** 文本内容 */
+    content: string;
+}
+/** 绘制二维码信息 */
+interface PainterQrCodeInfo extends PainterItemSite {
+    /** 绘制换行文本元素 */
+    type: "qr-code";
+    /** 二维码尺寸 */
+    size: number;
+    /** 二维码内容 */
+    content: string;
+    /** 边距,二维码实际尺寸会根据所设边距值进行缩放调整(默认:5) */
+    margin?: number;
+    /** 背景色(默认:'#ffffff')*/
+    backgroundColor?: string;
+    /** 前景色(默认:'#000000') */
+    foregroundColor?: string;
+}
+export interface PainterContainerOption extends PainterItemSize {
+    /** 绘制项的数组 */
+    contents: Array<PainterImageInfo | PainterRectInfo | PainterTextInfo | PainterLineFeedTextInfo | PainterQrCodeInfo>;
+}
+declare const _default: DrawPosterUseOpts;
+export default _default;

+ 73 - 0
utils/js_sdk/u-draw-poster/extends/draw-painter/index.js

@@ -0,0 +1,73 @@
+export default {
+    name: 'painter',
+    handle: (dp, option) => {
+        dp.canvas.width = option.width;
+        dp.canvas.height = option.height;
+        dp.draw(async (ctx) => {
+            for (let i = 0; i < option.contents.length; i++) {
+                ctx.save();
+                const drawInfo = option.contents[i];
+                const { left = 0, top = 0 } = drawInfo;
+                if (drawInfo.type === 'rect') {
+                    ctx.fillStyle = drawInfo.background || '#000000';
+                    ctx.fillRoundRect(left, top, drawInfo.width, drawInfo.height, drawInfo.radius || 0);
+                }
+                if (drawInfo.type === 'image') {
+                    await ctx.drawImageFit(drawInfo.src, {
+                        objectFit: drawInfo.objectFit || 'cover',
+                        intrinsicPosition: drawInfo.position || ['center', 'center'],
+                        specifiedPosition: [left, top],
+                        specifiedSize: {
+                            width: drawInfo.width,
+                            height: drawInfo.height
+                        },
+                        radius: drawInfo.radius
+                    });
+                }
+                if (drawInfo.type === 'text') {
+                    ctx.fillStyle = drawInfo.color || '#000000';
+                    ctx.font = `\
+          ${drawInfo.fontStyle || 'normal'} \
+          ${drawInfo.fontWeight || 'normal'} \
+          ${drawInfo.fontSize || 30} \
+          ${drawInfo.fontFamily || 'serial'}\
+          `;
+                    ctx.fillText(drawInfo.content, left, top, drawInfo.width);
+                }
+                if (drawInfo.type === 'line-feed-text') {
+                    ctx.fillStyle = drawInfo.color || '#000000';
+                    ctx.font = `\
+          ${drawInfo.fontStyle || 'normal'} \
+          ${drawInfo.fontWeight || 'normal'} \
+          ${drawInfo.fontSize || 30} \
+          ${drawInfo.fontFamily || 'serial'}\
+          `;
+                    ctx.fillWarpText({
+                        x: drawInfo.left,
+                        y: drawInfo.top,
+                        layer: drawInfo.lineClamp,
+                        lineHeight: drawInfo.lineHeight,
+                        maxWidth: drawInfo.width,
+                        text: drawInfo.content
+                    });
+                }
+                if (drawInfo.type === 'qr-code') {
+                    if (typeof ctx.drawQrCode !== 'function') {
+                        console.error('--- 当前未引入qr-code扩展, 将自动省略该二维码绘制 ---');
+                        return false;
+                    }
+                    ctx.drawQrCode({
+                        x: left,
+                        y: top,
+                        size: drawInfo.size,
+                        text: drawInfo.content,
+                        margin: drawInfo.margin || 5,
+                        backgroundColor: drawInfo.backgroundColor || '#ffffff',
+                        foregroundColor: drawInfo.foregroundColor || '#000000',
+                    });
+                }
+                ctx.restore();
+            }
+        });
+    }
+};

+ 6 - 0
utils/js_sdk/u-draw-poster/extends/draw-qr-code/index.d.ts

@@ -0,0 +1,6 @@
+declare const _default: {
+    name: string;
+    handle: any;
+    errorCorrectLevel: any;
+};
+export default _default;

+ 6 - 0
utils/js_sdk/u-draw-poster/extends/draw-qr-code/index.js

@@ -0,0 +1,6 @@
+import uQRCode from "./uQRCode";
+export default {
+    name: "drawQrCode",
+    handle: uQRCode.make.bind(uQRCode),
+    errorCorrectLevel: uQRCode.errorCorrectLevel
+};

+ 10 - 0
utils/js_sdk/u-draw-poster/extends/draw-qr-code/uQRCode.d.ts

@@ -0,0 +1,10 @@
+/*
+ * @Author: Mr.Mao
+ * @LastEditors: Mr.Mao
+ * @Date: 2021-01-02 13:30:58
+ * @LastEditTime: 2021-01-02 13:31:27
+ * @Description: 
+ * @任何一个傻子都能写出让电脑能懂的代码,而只有好的程序员可以写出让人能看懂的代码
+ */
+declare const uQRCode: any
+export default uQRCode

+ 1355 - 0
utils/js_sdk/u-draw-poster/extends/draw-qr-code/uQRCode.js

@@ -0,0 +1,1355 @@
+/*
+ * @Author: Mr.Mao
+ * @LastEditors: Mr.Mao
+ * @Date: 2021-01-02 13:30:58
+ * @LastEditTime: 2021-01-02 13:30:58
+ * @Description: 
+ * @任何一个傻子都能写出让电脑能懂的代码,而只有好的程序员可以写出让人能看懂的代码
+ */
+//---------------------------------------------------------------------
+// github https://github.com/Sansnn/uQRCode
+//---------------------------------------------------------------------
+
+let uQRCode = {};
+
+(function () {
+  //---------------------------------------------------------------------
+  // QRCode for JavaScript
+  //
+  // Copyright (c) 2009 Kazuhiko Arase
+  //
+  // URL: http://www.d-project.com/
+  //
+  // Licensed under the MIT license:
+  //   http://www.opensource.org/licenses/mit-license.php
+  //
+  // The word "QR Code" is registered trademark of 
+  // DENSO WAVE INCORPORATED
+  //   http://www.denso-wave.com/qrcode/faqpatent-e.html
+  //
+  //---------------------------------------------------------------------
+
+  //---------------------------------------------------------------------
+  // QR8bitByte
+  //---------------------------------------------------------------------
+
+  function QR8bitByte(data) {
+    this.mode = QRMode.MODE_8BIT_BYTE;
+    this.data = data;
+  }
+
+  QR8bitByte.prototype = {
+
+    getLength: function (buffer) {
+      return this.data.length;
+    },
+
+    write: function (buffer) {
+      for (var i = 0; i < this.data.length; i++) {
+        // not JIS ...
+        buffer.put(this.data.charCodeAt(i), 8);
+      }
+    }
+  };
+
+  //---------------------------------------------------------------------
+  // QRCode
+  //---------------------------------------------------------------------
+
+  function QRCode(typeNumber, errorCorrectLevel) {
+    this.typeNumber = typeNumber;
+    this.errorCorrectLevel = errorCorrectLevel;
+    this.modules = null;
+    this.moduleCount = 0;
+    this.dataCache = null;
+    this.dataList = new Array();
+  }
+
+  QRCode.prototype = {
+
+    addData: function (data) {
+      var newData = new QR8bitByte(data);
+      this.dataList.push(newData);
+      this.dataCache = null;
+    },
+
+    isDark: function (row, col) {
+      if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) {
+        throw new Error(row + "," + col);
+      }
+      return this.modules[row][col];
+    },
+
+    getModuleCount: function () {
+      return this.moduleCount;
+    },
+
+    make: function () {
+      // Calculate automatically typeNumber if provided is < 1
+      if (this.typeNumber < 1) {
+        var typeNumber = 1;
+        for (typeNumber = 1; typeNumber < 40; typeNumber++) {
+          var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, this.errorCorrectLevel);
+
+          var buffer = new QRBitBuffer();
+          var totalDataCount = 0;
+          for (var i = 0; i < rsBlocks.length; i++) {
+            totalDataCount += rsBlocks[i].dataCount;
+          }
+
+          for (var i = 0; i < this.dataList.length; i++) {
+            var data = this.dataList[i];
+            buffer.put(data.mode, 4);
+            buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
+            data.write(buffer);
+          }
+          if (buffer.getLengthInBits() <= totalDataCount * 8)
+            break;
+        }
+        this.typeNumber = typeNumber;
+      }
+      this.makeImpl(false, this.getBestMaskPattern());
+    },
+
+    makeImpl: function (test, maskPattern) {
+
+      this.moduleCount = this.typeNumber * 4 + 17;
+      this.modules = new Array(this.moduleCount);
+
+      for (var row = 0; row < this.moduleCount; row++) {
+
+        this.modules[row] = new Array(this.moduleCount);
+
+        for (var col = 0; col < this.moduleCount; col++) {
+          this.modules[row][col] = null; //(col + row) % 3;
+        }
+      }
+
+      this.setupPositionProbePattern(0, 0);
+      this.setupPositionProbePattern(this.moduleCount - 7, 0);
+      this.setupPositionProbePattern(0, this.moduleCount - 7);
+      this.setupPositionAdjustPattern();
+      this.setupTimingPattern();
+      this.setupTypeInfo(test, maskPattern);
+
+      if (this.typeNumber >= 7) {
+        this.setupTypeNumber(test);
+      }
+
+      if (this.dataCache == null) {
+        this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList);
+      }
+
+      this.mapData(this.dataCache, maskPattern);
+    },
+
+    setupPositionProbePattern: function (row, col) {
+
+      for (var r = -1; r <= 7; r++) {
+
+        if (row + r <= -1 || this.moduleCount <= row + r) continue;
+
+        for (var c = -1; c <= 7; c++) {
+
+          if (col + c <= -1 || this.moduleCount <= col + c) continue;
+
+          if ((0 <= r && r <= 6 && (c == 0 || c == 6)) ||
+            (0 <= c && c <= 6 && (r == 0 || r == 6)) ||
+            (2 <= r && r <= 4 && 2 <= c && c <= 4)) {
+            this.modules[row + r][col + c] = true;
+          } else {
+            this.modules[row + r][col + c] = false;
+          }
+        }
+      }
+    },
+
+    getBestMaskPattern: function () {
+
+      var minLostPoint = 0;
+      var pattern = 0;
+
+      for (var i = 0; i < 8; i++) {
+
+        this.makeImpl(true, i);
+
+        var lostPoint = QRUtil.getLostPoint(this);
+
+        if (i == 0 || minLostPoint > lostPoint) {
+          minLostPoint = lostPoint;
+          pattern = i;
+        }
+      }
+
+      return pattern;
+    },
+
+    createMovieClip: function (target_mc, instance_name, depth) {
+
+      var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth);
+      var cs = 1;
+
+      this.make();
+
+      for (var row = 0; row < this.modules.length; row++) {
+
+        var y = row * cs;
+
+        for (var col = 0; col < this.modules[row].length; col++) {
+
+          var x = col * cs;
+          var dark = this.modules[row][col];
+
+          if (dark) {
+            qr_mc.beginFill(0, 100);
+            qr_mc.moveTo(x, y);
+            qr_mc.lineTo(x + cs, y);
+            qr_mc.lineTo(x + cs, y + cs);
+            qr_mc.lineTo(x, y + cs);
+            qr_mc.endFill();
+          }
+        }
+      }
+
+      return qr_mc;
+    },
+
+    setupTimingPattern: function () {
+
+      for (var r = 8; r < this.moduleCount - 8; r++) {
+        if (this.modules[r][6] != null) {
+          continue;
+        }
+        this.modules[r][6] = (r % 2 == 0);
+      }
+
+      for (var c = 8; c < this.moduleCount - 8; c++) {
+        if (this.modules[6][c] != null) {
+          continue;
+        }
+        this.modules[6][c] = (c % 2 == 0);
+      }
+    },
+
+    setupPositionAdjustPattern: function () {
+
+      var pos = QRUtil.getPatternPosition(this.typeNumber);
+
+      for (var i = 0; i < pos.length; i++) {
+
+        for (var j = 0; j < pos.length; j++) {
+
+          var row = pos[i];
+          var col = pos[j];
+
+          if (this.modules[row][col] != null) {
+            continue;
+          }
+
+          for (var r = -2; r <= 2; r++) {
+
+            for (var c = -2; c <= 2; c++) {
+
+              if (r == -2 || r == 2 || c == -2 || c == 2 ||
+                (r == 0 && c == 0)) {
+                this.modules[row + r][col + c] = true;
+              } else {
+                this.modules[row + r][col + c] = false;
+              }
+            }
+          }
+        }
+      }
+    },
+
+    setupTypeNumber: function (test) {
+
+      var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
+
+      for (var i = 0; i < 18; i++) {
+        var mod = (!test && ((bits >> i) & 1) == 1);
+        this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
+      }
+
+      for (var i = 0; i < 18; i++) {
+        var mod = (!test && ((bits >> i) & 1) == 1);
+        this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+      }
+    },
+
+    setupTypeInfo: function (test, maskPattern) {
+
+      var data = (this.errorCorrectLevel << 3) | maskPattern;
+      var bits = QRUtil.getBCHTypeInfo(data);
+
+      // vertical		
+      for (var i = 0; i < 15; i++) {
+
+        var mod = (!test && ((bits >> i) & 1) == 1);
+
+        if (i < 6) {
+          this.modules[i][8] = mod;
+        } else if (i < 8) {
+          this.modules[i + 1][8] = mod;
+        } else {
+          this.modules[this.moduleCount - 15 + i][8] = mod;
+        }
+      }
+
+      // horizontal
+      for (var i = 0; i < 15; i++) {
+
+        var mod = (!test && ((bits >> i) & 1) == 1);
+
+        if (i < 8) {
+          this.modules[8][this.moduleCount - i - 1] = mod;
+        } else if (i < 9) {
+          this.modules[8][15 - i - 1 + 1] = mod;
+        } else {
+          this.modules[8][15 - i - 1] = mod;
+        }
+      }
+
+      // fixed module
+      this.modules[this.moduleCount - 8][8] = (!test);
+
+    },
+
+    mapData: function (data, maskPattern) {
+
+      var inc = -1;
+      var row = this.moduleCount - 1;
+      var bitIndex = 7;
+      var byteIndex = 0;
+
+      for (var col = this.moduleCount - 1; col > 0; col -= 2) {
+
+        if (col == 6) col--;
+
+        while (true) {
+
+          for (var c = 0; c < 2; c++) {
+
+            if (this.modules[row][col - c] == null) {
+
+              var dark = false;
+
+              if (byteIndex < data.length) {
+                dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
+              }
+
+              var mask = QRUtil.getMask(maskPattern, row, col - c);
+
+              if (mask) {
+                dark = !dark;
+              }
+
+              this.modules[row][col - c] = dark;
+              bitIndex--;
+
+              if (bitIndex == -1) {
+                byteIndex++;
+                bitIndex = 7;
+              }
+            }
+          }
+
+          row += inc;
+
+          if (row < 0 || this.moduleCount <= row) {
+            row -= inc;
+            inc = -inc;
+            break;
+          }
+        }
+      }
+
+    }
+
+  };
+
+  QRCode.PAD0 = 0xEC;
+  QRCode.PAD1 = 0x11;
+
+  QRCode.createData = function (typeNumber, errorCorrectLevel, dataList) {
+
+    var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel);
+
+    var buffer = new QRBitBuffer();
+
+    for (var i = 0; i < dataList.length; i++) {
+      var data = dataList[i];
+      buffer.put(data.mode, 4);
+      buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
+      data.write(buffer);
+    }
+
+    // calc num max data.
+    var totalDataCount = 0;
+    for (var i = 0; i < rsBlocks.length; i++) {
+      totalDataCount += rsBlocks[i].dataCount;
+    }
+
+    if (buffer.getLengthInBits() > totalDataCount * 8) {
+      throw new Error("code length overflow. (" +
+        buffer.getLengthInBits() +
+        ">" +
+        totalDataCount * 8 +
+        ")");
+    }
+
+    // end code
+    if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
+      buffer.put(0, 4);
+    }
+
+    // padding
+    while (buffer.getLengthInBits() % 8 != 0) {
+      buffer.putBit(false);
+    }
+
+    // padding
+    while (true) {
+
+      if (buffer.getLengthInBits() >= totalDataCount * 8) {
+        break;
+      }
+      buffer.put(QRCode.PAD0, 8);
+
+      if (buffer.getLengthInBits() >= totalDataCount * 8) {
+        break;
+      }
+      buffer.put(QRCode.PAD1, 8);
+    }
+
+    return QRCode.createBytes(buffer, rsBlocks);
+  }
+
+  QRCode.createBytes = function (buffer, rsBlocks) {
+
+    var offset = 0;
+
+    var maxDcCount = 0;
+    var maxEcCount = 0;
+
+    var dcdata = new Array(rsBlocks.length);
+    var ecdata = new Array(rsBlocks.length);
+
+    for (var r = 0; r < rsBlocks.length; r++) {
+
+      var dcCount = rsBlocks[r].dataCount;
+      var ecCount = rsBlocks[r].totalCount - dcCount;
+
+      maxDcCount = Math.max(maxDcCount, dcCount);
+      maxEcCount = Math.max(maxEcCount, ecCount);
+
+      dcdata[r] = new Array(dcCount);
+
+      for (var i = 0; i < dcdata[r].length; i++) {
+        dcdata[r][i] = 0xff & buffer.buffer[i + offset];
+      }
+      offset += dcCount;
+
+      var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+      var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
+
+      var modPoly = rawPoly.mod(rsPoly);
+      ecdata[r] = new Array(rsPoly.getLength() - 1);
+      for (var i = 0; i < ecdata[r].length; i++) {
+        var modIndex = i + modPoly.getLength() - ecdata[r].length;
+        ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0;
+      }
+
+    }
+
+    var totalCodeCount = 0;
+    for (var i = 0; i < rsBlocks.length; i++) {
+      totalCodeCount += rsBlocks[i].totalCount;
+    }
+
+    var data = new Array(totalCodeCount);
+    var index = 0;
+
+    for (var i = 0; i < maxDcCount; i++) {
+      for (var r = 0; r < rsBlocks.length; r++) {
+        if (i < dcdata[r].length) {
+          data[index++] = dcdata[r][i];
+        }
+      }
+    }
+
+    for (var i = 0; i < maxEcCount; i++) {
+      for (var r = 0; r < rsBlocks.length; r++) {
+        if (i < ecdata[r].length) {
+          data[index++] = ecdata[r][i];
+        }
+      }
+    }
+
+    return data;
+
+  }
+
+  //---------------------------------------------------------------------
+  // QRMode
+  //---------------------------------------------------------------------
+
+  var QRMode = {
+    MODE_NUMBER: 1 << 0,
+    MODE_ALPHA_NUM: 1 << 1,
+    MODE_8BIT_BYTE: 1 << 2,
+    MODE_KANJI: 1 << 3
+  };
+
+  //---------------------------------------------------------------------
+  // QRErrorCorrectLevel
+  //---------------------------------------------------------------------
+
+  var QRErrorCorrectLevel = {
+    L: 1,
+    M: 0,
+    Q: 3,
+    H: 2
+  };
+
+  //---------------------------------------------------------------------
+  // QRMaskPattern
+  //---------------------------------------------------------------------
+
+  var QRMaskPattern = {
+    PATTERN000: 0,
+    PATTERN001: 1,
+    PATTERN010: 2,
+    PATTERN011: 3,
+    PATTERN100: 4,
+    PATTERN101: 5,
+    PATTERN110: 6,
+    PATTERN111: 7
+  };
+
+  //---------------------------------------------------------------------
+  // QRUtil
+  //---------------------------------------------------------------------
+
+  var QRUtil = {
+
+    PATTERN_POSITION_TABLE: [
+      [],
+      [6, 18],
+      [6, 22],
+      [6, 26],
+      [6, 30],
+      [6, 34],
+      [6, 22, 38],
+      [6, 24, 42],
+      [6, 26, 46],
+      [6, 28, 50],
+      [6, 30, 54],
+      [6, 32, 58],
+      [6, 34, 62],
+      [6, 26, 46, 66],
+      [6, 26, 48, 70],
+      [6, 26, 50, 74],
+      [6, 30, 54, 78],
+      [6, 30, 56, 82],
+      [6, 30, 58, 86],
+      [6, 34, 62, 90],
+      [6, 28, 50, 72, 94],
+      [6, 26, 50, 74, 98],
+      [6, 30, 54, 78, 102],
+      [6, 28, 54, 80, 106],
+      [6, 32, 58, 84, 110],
+      [6, 30, 58, 86, 114],
+      [6, 34, 62, 90, 118],
+      [6, 26, 50, 74, 98, 122],
+      [6, 30, 54, 78, 102, 126],
+      [6, 26, 52, 78, 104, 130],
+      [6, 30, 56, 82, 108, 134],
+      [6, 34, 60, 86, 112, 138],
+      [6, 30, 58, 86, 114, 142],
+      [6, 34, 62, 90, 118, 146],
+      [6, 30, 54, 78, 102, 126, 150],
+      [6, 24, 50, 76, 102, 128, 154],
+      [6, 28, 54, 80, 106, 132, 158],
+      [6, 32, 58, 84, 110, 136, 162],
+      [6, 26, 54, 82, 110, 138, 166],
+      [6, 30, 58, 86, 114, 142, 170]
+    ],
+
+    G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0),
+    G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0),
+    G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1),
+
+    getBCHTypeInfo: function (data) {
+      var d = data << 10;
+      while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
+        d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15)));
+      }
+      return ((data << 10) | d) ^ QRUtil.G15_MASK;
+    },
+
+    getBCHTypeNumber: function (data) {
+      var d = data << 12;
+      while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
+        d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)));
+      }
+      return (data << 12) | d;
+    },
+
+    getBCHDigit: function (data) {
+
+      var digit = 0;
+
+      while (data != 0) {
+        digit++;
+        data >>>= 1;
+      }
+
+      return digit;
+    },
+
+    getPatternPosition: function (typeNumber) {
+      return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
+    },
+
+    getMask: function (maskPattern, i, j) {
+
+      switch (maskPattern) {
+
+        case QRMaskPattern.PATTERN000:
+          return (i + j) % 2 == 0;
+        case QRMaskPattern.PATTERN001:
+          return i % 2 == 0;
+        case QRMaskPattern.PATTERN010:
+          return j % 3 == 0;
+        case QRMaskPattern.PATTERN011:
+          return (i + j) % 3 == 0;
+        case QRMaskPattern.PATTERN100:
+          return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
+        case QRMaskPattern.PATTERN101:
+          return (i * j) % 2 + (i * j) % 3 == 0;
+        case QRMaskPattern.PATTERN110:
+          return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
+        case QRMaskPattern.PATTERN111:
+          return ((i * j) % 3 + (i + j) % 2) % 2 == 0;
+
+        default:
+          throw new Error("bad maskPattern:" + maskPattern);
+      }
+    },
+
+    getErrorCorrectPolynomial: function (errorCorrectLength) {
+
+      var a = new QRPolynomial([1], 0);
+
+      for (var i = 0; i < errorCorrectLength; i++) {
+        a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0));
+      }
+
+      return a;
+    },
+
+    getLengthInBits: function (mode, type) {
+
+      if (1 <= type && type < 10) {
+
+        // 1 - 9
+
+        switch (mode) {
+          case QRMode.MODE_NUMBER:
+            return 10;
+          case QRMode.MODE_ALPHA_NUM:
+            return 9;
+          case QRMode.MODE_8BIT_BYTE:
+            return 8;
+          case QRMode.MODE_KANJI:
+            return 8;
+          default:
+            throw new Error("mode:" + mode);
+        }
+
+      } else if (type < 27) {
+
+        // 10 - 26
+
+        switch (mode) {
+          case QRMode.MODE_NUMBER:
+            return 12;
+          case QRMode.MODE_ALPHA_NUM:
+            return 11;
+          case QRMode.MODE_8BIT_BYTE:
+            return 16;
+          case QRMode.MODE_KANJI:
+            return 10;
+          default:
+            throw new Error("mode:" + mode);
+        }
+
+      } else if (type < 41) {
+
+        // 27 - 40
+
+        switch (mode) {
+          case QRMode.MODE_NUMBER:
+            return 14;
+          case QRMode.MODE_ALPHA_NUM:
+            return 13;
+          case QRMode.MODE_8BIT_BYTE:
+            return 16;
+          case QRMode.MODE_KANJI:
+            return 12;
+          default:
+            throw new Error("mode:" + mode);
+        }
+
+      } else {
+        throw new Error("type:" + type);
+      }
+    },
+
+    getLostPoint: function (qrCode) {
+
+      var moduleCount = qrCode.getModuleCount();
+
+      var lostPoint = 0;
+
+      // LEVEL1
+
+      for (var row = 0; row < moduleCount; row++) {
+
+        for (var col = 0; col < moduleCount; col++) {
+
+          var sameCount = 0;
+          var dark = qrCode.isDark(row, col);
+
+          for (var r = -1; r <= 1; r++) {
+
+            if (row + r < 0 || moduleCount <= row + r) {
+              continue;
+            }
+
+            for (var c = -1; c <= 1; c++) {
+
+              if (col + c < 0 || moduleCount <= col + c) {
+                continue;
+              }
+
+              if (r == 0 && c == 0) {
+                continue;
+              }
+
+              if (dark == qrCode.isDark(row + r, col + c)) {
+                sameCount++;
+              }
+            }
+          }
+
+          if (sameCount > 5) {
+            lostPoint += (3 + sameCount - 5);
+          }
+        }
+      }
+
+      // LEVEL2
+
+      for (var row = 0; row < moduleCount - 1; row++) {
+        for (var col = 0; col < moduleCount - 1; col++) {
+          var count = 0;
+          if (qrCode.isDark(row, col)) count++;
+          if (qrCode.isDark(row + 1, col)) count++;
+          if (qrCode.isDark(row, col + 1)) count++;
+          if (qrCode.isDark(row + 1, col + 1)) count++;
+          if (count == 0 || count == 4) {
+            lostPoint += 3;
+          }
+        }
+      }
+
+      // LEVEL3
+
+      for (var row = 0; row < moduleCount; row++) {
+        for (var col = 0; col < moduleCount - 6; col++) {
+          if (qrCode.isDark(row, col) &&
+            !qrCode.isDark(row, col + 1) &&
+            qrCode.isDark(row, col + 2) &&
+            qrCode.isDark(row, col + 3) &&
+            qrCode.isDark(row, col + 4) &&
+            !qrCode.isDark(row, col + 5) &&
+            qrCode.isDark(row, col + 6)) {
+            lostPoint += 40;
+          }
+        }
+      }
+
+      for (var col = 0; col < moduleCount; col++) {
+        for (var row = 0; row < moduleCount - 6; row++) {
+          if (qrCode.isDark(row, col) &&
+            !qrCode.isDark(row + 1, col) &&
+            qrCode.isDark(row + 2, col) &&
+            qrCode.isDark(row + 3, col) &&
+            qrCode.isDark(row + 4, col) &&
+            !qrCode.isDark(row + 5, col) &&
+            qrCode.isDark(row + 6, col)) {
+            lostPoint += 40;
+          }
+        }
+      }
+
+      // LEVEL4
+
+      var darkCount = 0;
+
+      for (var col = 0; col < moduleCount; col++) {
+        for (var row = 0; row < moduleCount; row++) {
+          if (qrCode.isDark(row, col)) {
+            darkCount++;
+          }
+        }
+      }
+
+      var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+      lostPoint += ratio * 10;
+
+      return lostPoint;
+    }
+
+  };
+
+
+  //---------------------------------------------------------------------
+  // QRMath
+  //---------------------------------------------------------------------
+
+  var QRMath = {
+
+    glog: function (n) {
+
+      if (n < 1) {
+        throw new Error("glog(" + n + ")");
+      }
+
+      return QRMath.LOG_TABLE[n];
+    },
+
+    gexp: function (n) {
+
+      while (n < 0) {
+        n += 255;
+      }
+
+      while (n >= 256) {
+        n -= 255;
+      }
+
+      return QRMath.EXP_TABLE[n];
+    },
+
+    EXP_TABLE: new Array(256),
+
+    LOG_TABLE: new Array(256)
+
+  };
+
+  for (var i = 0; i < 8; i++) {
+    QRMath.EXP_TABLE[i] = 1 << i;
+  }
+  for (var i = 8; i < 256; i++) {
+    QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^
+      QRMath.EXP_TABLE[i - 5] ^
+      QRMath.EXP_TABLE[i - 6] ^
+      QRMath.EXP_TABLE[i - 8];
+  }
+  for (var i = 0; i < 255; i++) {
+    QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i;
+  }
+
+  //---------------------------------------------------------------------
+  // QRPolynomial
+  //---------------------------------------------------------------------
+
+  function QRPolynomial(num, shift) {
+
+    if (num.length == undefined) {
+      throw new Error(num.length + "/" + shift);
+    }
+
+    var offset = 0;
+
+    while (offset < num.length && num[offset] == 0) {
+      offset++;
+    }
+
+    this.num = new Array(num.length - offset + shift);
+    for (var i = 0; i < num.length - offset; i++) {
+      this.num[i] = num[i + offset];
+    }
+  }
+
+  QRPolynomial.prototype = {
+
+    get: function (index) {
+      return this.num[index];
+    },
+
+    getLength: function () {
+      return this.num.length;
+    },
+
+    multiply: function (e) {
+
+      var num = new Array(this.getLength() + e.getLength() - 1);
+
+      for (var i = 0; i < this.getLength(); i++) {
+        for (var j = 0; j < e.getLength(); j++) {
+          num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)));
+        }
+      }
+
+      return new QRPolynomial(num, 0);
+    },
+
+    mod: function (e) {
+
+      if (this.getLength() - e.getLength() < 0) {
+        return this;
+      }
+
+      var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0));
+
+      var num = new Array(this.getLength());
+
+      for (var i = 0; i < this.getLength(); i++) {
+        num[i] = this.get(i);
+      }
+
+      for (var i = 0; i < e.getLength(); i++) {
+        num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio);
+      }
+
+      // recursive call
+      return new QRPolynomial(num, 0).mod(e);
+    }
+  };
+
+  //---------------------------------------------------------------------
+  // QRRSBlock
+  //---------------------------------------------------------------------
+
+  function QRRSBlock(totalCount, dataCount) {
+    this.totalCount = totalCount;
+    this.dataCount = dataCount;
+  }
+
+  QRRSBlock.RS_BLOCK_TABLE = [
+
+    // L
+    // M
+    // Q
+    // H
+
+    // 1
+    [1, 26, 19],
+    [1, 26, 16],
+    [1, 26, 13],
+    [1, 26, 9],
+
+    // 2
+    [1, 44, 34],
+    [1, 44, 28],
+    [1, 44, 22],
+    [1, 44, 16],
+
+    // 3
+    [1, 70, 55],
+    [1, 70, 44],
+    [2, 35, 17],
+    [2, 35, 13],
+
+    // 4		
+    [1, 100, 80],
+    [2, 50, 32],
+    [2, 50, 24],
+    [4, 25, 9],
+
+    // 5
+    [1, 134, 108],
+    [2, 67, 43],
+    [2, 33, 15, 2, 34, 16],
+    [2, 33, 11, 2, 34, 12],
+
+    // 6
+    [2, 86, 68],
+    [4, 43, 27],
+    [4, 43, 19],
+    [4, 43, 15],
+
+    // 7		
+    [2, 98, 78],
+    [4, 49, 31],
+    [2, 32, 14, 4, 33, 15],
+    [4, 39, 13, 1, 40, 14],
+
+    // 8
+    [2, 121, 97],
+    [2, 60, 38, 2, 61, 39],
+    [4, 40, 18, 2, 41, 19],
+    [4, 40, 14, 2, 41, 15],
+
+    // 9
+    [2, 146, 116],
+    [3, 58, 36, 2, 59, 37],
+    [4, 36, 16, 4, 37, 17],
+    [4, 36, 12, 4, 37, 13],
+
+    // 10		
+    [2, 86, 68, 2, 87, 69],
+    [4, 69, 43, 1, 70, 44],
+    [6, 43, 19, 2, 44, 20],
+    [6, 43, 15, 2, 44, 16],
+
+    // 11
+    [4, 101, 81],
+    [1, 80, 50, 4, 81, 51],
+    [4, 50, 22, 4, 51, 23],
+    [3, 36, 12, 8, 37, 13],
+
+    // 12
+    [2, 116, 92, 2, 117, 93],
+    [6, 58, 36, 2, 59, 37],
+    [4, 46, 20, 6, 47, 21],
+    [7, 42, 14, 4, 43, 15],
+
+    // 13
+    [4, 133, 107],
+    [8, 59, 37, 1, 60, 38],
+    [8, 44, 20, 4, 45, 21],
+    [12, 33, 11, 4, 34, 12],
+
+    // 14
+    [3, 145, 115, 1, 146, 116],
+    [4, 64, 40, 5, 65, 41],
+    [11, 36, 16, 5, 37, 17],
+    [11, 36, 12, 5, 37, 13],
+
+    // 15
+    [5, 109, 87, 1, 110, 88],
+    [5, 65, 41, 5, 66, 42],
+    [5, 54, 24, 7, 55, 25],
+    [11, 36, 12],
+
+    // 16
+    [5, 122, 98, 1, 123, 99],
+    [7, 73, 45, 3, 74, 46],
+    [15, 43, 19, 2, 44, 20],
+    [3, 45, 15, 13, 46, 16],
+
+    // 17
+    [1, 135, 107, 5, 136, 108],
+    [10, 74, 46, 1, 75, 47],
+    [1, 50, 22, 15, 51, 23],
+    [2, 42, 14, 17, 43, 15],
+
+    // 18
+    [5, 150, 120, 1, 151, 121],
+    [9, 69, 43, 4, 70, 44],
+    [17, 50, 22, 1, 51, 23],
+    [2, 42, 14, 19, 43, 15],
+
+    // 19
+    [3, 141, 113, 4, 142, 114],
+    [3, 70, 44, 11, 71, 45],
+    [17, 47, 21, 4, 48, 22],
+    [9, 39, 13, 16, 40, 14],
+
+    // 20
+    [3, 135, 107, 5, 136, 108],
+    [3, 67, 41, 13, 68, 42],
+    [15, 54, 24, 5, 55, 25],
+    [15, 43, 15, 10, 44, 16],
+
+    // 21
+    [4, 144, 116, 4, 145, 117],
+    [17, 68, 42],
+    [17, 50, 22, 6, 51, 23],
+    [19, 46, 16, 6, 47, 17],
+
+    // 22
+    [2, 139, 111, 7, 140, 112],
+    [17, 74, 46],
+    [7, 54, 24, 16, 55, 25],
+    [34, 37, 13],
+
+    // 23
+    [4, 151, 121, 5, 152, 122],
+    [4, 75, 47, 14, 76, 48],
+    [11, 54, 24, 14, 55, 25],
+    [16, 45, 15, 14, 46, 16],
+
+    // 24
+    [6, 147, 117, 4, 148, 118],
+    [6, 73, 45, 14, 74, 46],
+    [11, 54, 24, 16, 55, 25],
+    [30, 46, 16, 2, 47, 17],
+
+    // 25
+    [8, 132, 106, 4, 133, 107],
+    [8, 75, 47, 13, 76, 48],
+    [7, 54, 24, 22, 55, 25],
+    [22, 45, 15, 13, 46, 16],
+
+    // 26
+    [10, 142, 114, 2, 143, 115],
+    [19, 74, 46, 4, 75, 47],
+    [28, 50, 22, 6, 51, 23],
+    [33, 46, 16, 4, 47, 17],
+
+    // 27
+    [8, 152, 122, 4, 153, 123],
+    [22, 73, 45, 3, 74, 46],
+    [8, 53, 23, 26, 54, 24],
+    [12, 45, 15, 28, 46, 16],
+
+    // 28
+    [3, 147, 117, 10, 148, 118],
+    [3, 73, 45, 23, 74, 46],
+    [4, 54, 24, 31, 55, 25],
+    [11, 45, 15, 31, 46, 16],
+
+    // 29
+    [7, 146, 116, 7, 147, 117],
+    [21, 73, 45, 7, 74, 46],
+    [1, 53, 23, 37, 54, 24],
+    [19, 45, 15, 26, 46, 16],
+
+    // 30
+    [5, 145, 115, 10, 146, 116],
+    [19, 75, 47, 10, 76, 48],
+    [15, 54, 24, 25, 55, 25],
+    [23, 45, 15, 25, 46, 16],
+
+    // 31
+    [13, 145, 115, 3, 146, 116],
+    [2, 74, 46, 29, 75, 47],
+    [42, 54, 24, 1, 55, 25],
+    [23, 45, 15, 28, 46, 16],
+
+    // 32
+    [17, 145, 115],
+    [10, 74, 46, 23, 75, 47],
+    [10, 54, 24, 35, 55, 25],
+    [19, 45, 15, 35, 46, 16],
+
+    // 33
+    [17, 145, 115, 1, 146, 116],
+    [14, 74, 46, 21, 75, 47],
+    [29, 54, 24, 19, 55, 25],
+    [11, 45, 15, 46, 46, 16],
+
+    // 34
+    [13, 145, 115, 6, 146, 116],
+    [14, 74, 46, 23, 75, 47],
+    [44, 54, 24, 7, 55, 25],
+    [59, 46, 16, 1, 47, 17],
+
+    // 35
+    [12, 151, 121, 7, 152, 122],
+    [12, 75, 47, 26, 76, 48],
+    [39, 54, 24, 14, 55, 25],
+    [22, 45, 15, 41, 46, 16],
+
+    // 36
+    [6, 151, 121, 14, 152, 122],
+    [6, 75, 47, 34, 76, 48],
+    [46, 54, 24, 10, 55, 25],
+    [2, 45, 15, 64, 46, 16],
+
+    // 37
+    [17, 152, 122, 4, 153, 123],
+    [29, 74, 46, 14, 75, 47],
+    [49, 54, 24, 10, 55, 25],
+    [24, 45, 15, 46, 46, 16],
+
+    // 38
+    [4, 152, 122, 18, 153, 123],
+    [13, 74, 46, 32, 75, 47],
+    [48, 54, 24, 14, 55, 25],
+    [42, 45, 15, 32, 46, 16],
+
+    // 39
+    [20, 147, 117, 4, 148, 118],
+    [40, 75, 47, 7, 76, 48],
+    [43, 54, 24, 22, 55, 25],
+    [10, 45, 15, 67, 46, 16],
+
+    // 40
+    [19, 148, 118, 6, 149, 119],
+    [18, 75, 47, 31, 76, 48],
+    [34, 54, 24, 34, 55, 25],
+    [20, 45, 15, 61, 46, 16]
+  ];
+
+  QRRSBlock.getRSBlocks = function (typeNumber, errorCorrectLevel) {
+
+    var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel);
+
+    if (rsBlock == undefined) {
+      throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel);
+    }
+
+    var length = rsBlock.length / 3;
+
+    var list = new Array();
+
+    for (var i = 0; i < length; i++) {
+
+      var count = rsBlock[i * 3 + 0];
+      var totalCount = rsBlock[i * 3 + 1];
+      var dataCount = rsBlock[i * 3 + 2];
+
+      for (var j = 0; j < count; j++) {
+        list.push(new QRRSBlock(totalCount, dataCount));
+      }
+    }
+
+    return list;
+  }
+
+  QRRSBlock.getRsBlockTable = function (typeNumber, errorCorrectLevel) {
+
+    switch (errorCorrectLevel) {
+      case QRErrorCorrectLevel.L:
+        return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
+      case QRErrorCorrectLevel.M:
+        return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
+      case QRErrorCorrectLevel.Q:
+        return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
+      case QRErrorCorrectLevel.H:
+        return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
+      default:
+        return undefined;
+    }
+  }
+
+  //---------------------------------------------------------------------
+  // QRBitBuffer
+  //---------------------------------------------------------------------
+
+  function QRBitBuffer() {
+    this.buffer = new Array();
+    this.length = 0;
+  }
+
+  QRBitBuffer.prototype = {
+
+    get: function (index) {
+      var bufIndex = Math.floor(index / 8);
+      return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1) == 1;
+    },
+
+    put: function (num, length) {
+      for (var i = 0; i < length; i++) {
+        this.putBit(((num >>> (length - i - 1)) & 1) == 1);
+      }
+    },
+
+    getLengthInBits: function () {
+      return this.length;
+    },
+
+    putBit: function (bit) {
+
+      var bufIndex = Math.floor(this.length / 8);
+      if (this.buffer.length <= bufIndex) {
+        this.buffer.push(0);
+      }
+
+      if (bit) {
+        this.buffer[bufIndex] |= (0x80 >>> (this.length % 8));
+      }
+
+      this.length++;
+    }
+  };
+
+  //---------------------------------------------------------------------
+  // Support Chinese
+  //---------------------------------------------------------------------
+  function utf16To8(text) {
+    var result = '';
+    var c;
+    for (var i = 0; i < text.length; i++) {
+      c = text.charCodeAt(i);
+      if (c >= 0x0001 && c <= 0x007F) {
+        result += text.charAt(i);
+      } else if (c > 0x07FF) {
+        result += String.fromCharCode(0xE0 | c >> 12 & 0x0F);
+        result += String.fromCharCode(0x80 | c >> 6 & 0x3F);
+        result += String.fromCharCode(0x80 | c >> 0 & 0x3F);
+      } else {
+        result += String.fromCharCode(0xC0 | c >> 6 & 0x1F);
+        result += String.fromCharCode(0x80 | c >> 0 & 0x3F);
+      }
+    }
+    return result;
+  }
+
+  uQRCode = {
+
+    errorCorrectLevel: QRErrorCorrectLevel,
+
+    defaults: {
+      size: 354,
+      margin: 0,
+      backgroundColor: '#ffffff',
+      foregroundColor: '#000000',
+      errorCorrectLevel: QRErrorCorrectLevel.H,
+      typeNumber: -1
+    },
+
+    make: function (canvas, ctx, options) {
+      var defaultOptions = {
+        x: 0,
+        y: 0,
+        text: options.text,
+        size: this.defaults.size,
+        margin: this.defaults.margin,
+        backgroundColor: this.defaults.backgroundColor,
+        foregroundColor: this.defaults.foregroundColor,
+        errorCorrectLevel: this.defaults.errorCorrectLevel,
+        typeNumber: this.defaults.typeNumber
+      };
+      if (options) {
+        for (var i in options) {
+          defaultOptions[i] = options[i];
+        }
+      }
+      options = defaultOptions;
+
+      var qrcode = new QRCode(options.typeNumber, options.errorCorrectLevel);
+      qrcode.addData(utf16To8(options.text));
+      qrcode.make();
+
+      ctx.fill
+      ctx.fillStyle = options.backgroundColor;
+      ctx.fillRect(options.x, options.y, options.size, options.size);
+
+      var tileW = (options.size - options.margin * 2) / qrcode.getModuleCount();
+      var tileH = tileW;
+
+      for (var row = 0; row < qrcode.getModuleCount(); row++) {
+        for (var col = 0; col < qrcode.getModuleCount(); col++) {
+          var style = qrcode.isDark(row, col) ? options.foregroundColor : options.backgroundColor;
+          ctx.fillStyle = style
+          var x = Math.round(col * tileW) + options.margin;
+          var y = Math.round(row * tileH) + options.margin;
+          var w = Math.ceil((col + 1) * tileW) - Math.floor(col * tileW);
+          var h = Math.ceil((row + 1) * tileW) - Math.floor(row * tileW);
+          ctx.fillRect(options.x + x, options.y + y, w, h);
+        }
+      }
+    }
+  }
+
+})()
+
+export default uQRCode

+ 11 - 0
utils/js_sdk/u-draw-poster/index.d.ts

@@ -0,0 +1,11 @@
+import { DrawPosterBuildOpts } from "./utils/interface";
+import DrawPoster from "./draw-poster";
+import drawQrCode from "./extends/draw-qr-code/index";
+import createFromList from './extends/create-from-list/index';
+import drawPainter from './extends/draw-painter/index';
+declare const useDrawPoster: (options: string | DrawPosterBuildOpts) => Promise<DrawPoster & import("./utils/interface").drawPosterExtends>;
+declare const useDrawPosters: (optionsAll: (string | DrawPosterBuildOpts)[]) => Promise<{
+    [key: string]: DrawPoster & import("./utils/interface").drawPosterExtends;
+}>;
+export { DrawPoster, useDrawPoster, useDrawPosters, drawQrCode, drawPainter, createFromList };
+export default DrawPoster;

+ 22 - 0
utils/js_sdk/u-draw-poster/index.js

@@ -0,0 +1,22 @@
+import * as dfucs from "./extends/draw-function/index";
+import DrawPoster from "./draw-poster";
+import drawQrCode from "./extends/draw-qr-code/index";
+import createFromList from './extends/create-from-list/index';
+import drawPainter from './extends/draw-painter/index';
+DrawPoster.useCtx(dfucs.drawImage);
+DrawPoster.useCtx(dfucs.fillWarpText);
+DrawPoster.useCtx(dfucs.roundRect);
+DrawPoster.useCtx(dfucs.fillRoundRect);
+DrawPoster.useCtx(dfucs.strokeRoundRect);
+DrawPoster.useCtx(dfucs.drawRoundImage);
+DrawPoster.useCtx(dfucs.drawImageFit);
+const useDrawPoster = async (options) => {
+    const dp = await DrawPoster.build(options);
+    return dp;
+};
+const useDrawPosters = async (optionsAll) => {
+    const dps = await DrawPoster.buildAll(optionsAll);
+    return dps;
+};
+export { DrawPoster, useDrawPoster, useDrawPosters, drawQrCode, drawPainter, createFromList };
+export default DrawPoster;

+ 13 - 0
utils/js_sdk/u-draw-poster/package.json

@@ -0,0 +1,13 @@
+{
+    "id": "u-draw-poster",
+    "name": "u-draw-poster uniVue2|3适用 海报绘制工具",
+    "version": "1.1.5",
+    "description": "全端支持,内置多种海报绘制方法、表单绘制、二维码生成,图片裁剪。原生开发体验,上手快,不污染组件数据",
+    "keywords": [
+        "海报",
+        "绘制",
+        "分享",
+        "小程序",
+        "canvas"
+    ]
+}

+ 7 - 0
utils/js_sdk/u-draw-poster/utils/global.d.ts

@@ -0,0 +1,7 @@
+/// <reference types="@dcloudio/types" />
+/** 当前环境类型 */
+export declare type UniPlatforms = 'app-plus' | 'app-plus-nvue' | 'h5' | 'mp-weixin' | 'mp-alipay' | 'mp-baidu' | 'mp-toutiao' | 'mp-qq' | 'mp-360' | 'mp' | 'quickapp-webview' | 'quickapp-webview-union' | 'quickapp-webview-huawei' | undefined;
+export declare const PLATFORM: UniPlatforms;
+/** 全局对象 */
+declare const _uni: UniApp.Uni;
+export default _uni;

+ 11 - 0
utils/js_sdk/u-draw-poster/utils/global.js

@@ -0,0 +1,11 @@
+var _a;
+export const PLATFORM = typeof process !== 'undefined' ? (_a = process === null || process === void 0 ? void 0 : process.env) === null || _a === void 0 ? void 0 : _a.VUE_APP_PLATFORM : undefined;
+/** 全局对象 */
+const _uni = (function () {
+    if (typeof uni != "undefined")
+        return uni;
+    if (typeof wx != "undefined")
+        return wx;
+    return uni;
+})();
+export default _uni;

+ 175 - 0
utils/js_sdk/u-draw-poster/utils/interface.d.ts

@@ -0,0 +1,175 @@
+/// <reference types="@dcloudio/types" />
+import DrawPoster from "../draw-poster";
+import { ImageFitOption } from '../extends/draw-function/draw-image-fit';
+import { CreateLayerOpts, DrawRowOpt } from "../extends/create-from-list";
+import { PainterContainerOption } from "../extends/draw-painter";
+/** 绘制容器 */
+export declare type Execute = Array<() => Promise<boolean>>;
+export interface drawPosterExtends {
+    from: {
+        height: number;
+        padding: number;
+        margin: number;
+    };
+    createLayer: (afferOpts: CreateLayerOpts, rowList: DrawRowOpt[]) => number;
+    setFromOptions: (opts: Partial<{
+        height: number;
+        padding: number;
+        margin: number;
+    }>) => void;
+    gcanvas: {
+        WeexBridge: any;
+        Image: any;
+        enable: (el: any, options: {
+            bridge?: any;
+            debug?: boolean;
+            disableAutoSwap?: any;
+            disableComboCommands?: any;
+        }) => Canvas;
+    };
+    painter: (option: PainterContainerOption) => void;
+}
+/** 构建器配置 */
+export interface DrawPosterBuildOpts {
+    /** 查询选择器; 注意不需要加# */
+    selector: string;
+    /** 选取组件范围 */
+    componentThis?: any;
+    /** 绘制类型为2d绘制, 默认开启, 在微信小程序的时候动态加载 */
+    type2d?: boolean;
+    /** 是否在绘制时进行加载提示 */
+    loading?: boolean;
+    /** 当存在绘制图片时, 等待绘画完毕的时间(秒)仅App中生效
+     *
+     *  具体查看文档说明:https://github.com/TuiMao233/uni-draw-poster
+     */
+    drawImageTime?: number;
+    /** 是否开启调试模式 */
+    debugging?: boolean;
+    /** 加载提示文字 */
+    loadingText?: string;
+    /** 创建图片提示文字 */
+    createText?: string;
+    /** 是否启动gcanvas(nvue) */
+    gcanvas?: boolean;
+}
+/** 绘制换行配置 */
+export interface FillWarpTextOpts {
+    text: string;
+    maxWidth?: number;
+    lineHeight?: number;
+    layer?: number;
+    x?: number;
+    y?: number;
+    splitText?: string;
+    notFillText?: boolean;
+}
+/** 绘制二维码配置 */
+export interface DrawQrCodeOpts {
+    text: string;
+    x?: number;
+    y?: number;
+    size?: number;
+    margin?: number;
+    backgroundColor?: string;
+    foregroundColor?: string;
+}
+/** 绘制换行, 单行信息 */
+export interface FillWarpTextItemInfo {
+    text: string;
+    y: number;
+    x: number;
+}
+/** 绘制画笔 */
+export interface DrawPosterCanvasCtx extends UniApp.CanvasContext {
+    [key: string]: any;
+    createImageData: () => ImageData;
+    textAlign: CanvasTextDrawingStyles["textAlign"];
+    textBaseline: CanvasTextDrawingStyles["textBaseline"];
+    transform: CanvasTransform["transform"];
+    /** 绘制图片原型 */
+    drawImageProto: UniApp.CanvasContext['drawImage'];
+    /** 当前绘制类型 */
+    drawType: 'context' | 'type2d';
+    /** 等待绘制图片
+     *
+     * 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
+     */
+    drawImage(url: string, dx?: number | undefined, dy?: number | undefined, dWidth?: number | undefined, dHeigt?: number | undefined, sx?: number | undefined, sy?: number | undefined, sWidth?: number | undefined, sHeight?: number | undefined): Promise<boolean>;
+    /** 绘制圆角图片
+     *
+     * 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
+     */
+    drawRoundImage(url: string, x: number, y: number, w: number, h: number, r?: number): Promise<boolean>;
+    /** 绘制 Object-Fit 模式图片
+     *
+     * 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
+     */
+    drawImageFit(url: string, opts?: ImageFitOption): Promise<boolean>;
+    /** 绘制换行字体
+     *
+     * 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
+     */
+    fillWarpText(options: FillWarpTextOpts): Array<FillWarpTextItemInfo>;
+    /** 绘制圆角矩形(原型)
+     *
+     */
+    roundRect(x: number, y: number, w: number, h: number, r: number, fill?: boolean, stroke?: boolean): void;
+    /** 绘制圆角矩形(填充)
+     *
+     * 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
+     */
+    fillRoundRect(x: number, y: number, w: number, h: number, r: number): void;
+    /** 绘制圆角矩形(边框)
+     *
+     * 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
+     */
+    strokeRoundRect(x: number, y: number, w: number, h: number, r: number): void;
+    /** 绘制二维码
+     *
+     * 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
+     */
+    drawQrCode(options: DrawQrCodeOpts): void;
+}
+/** Canvas2d实例 */
+export interface Canvas {
+    width: number;
+    height: number;
+    getContext(contextType: "2d" | "webgl"): DrawPosterCanvasCtx | WebGLRenderingContext;
+    createImage(): {
+        src: string;
+        width: number;
+        height: number;
+        onload: () => void;
+        onerror: () => void;
+    };
+    requestAnimationFrame(callback: Function): number;
+    cancelAnimationFrame(requestID: number): void;
+    createImageData(): ImageData;
+    createPath2D(path: Path2D): Path2D;
+    toDataURL(type: string, encoderOptions: number): string;
+}
+/** 创建图片路径配置项 */
+export interface CreateImagePathOptions {
+    x?: number;
+    y?: number;
+    width?: number;
+    height?: number;
+    destWidth?: number;
+    destHeight?: number;
+}
+/** 绘制实例扩展配置 */
+export interface DrawPosterUseOpts {
+    name: string;
+    init?: (dp: InstanceType<typeof DrawPoster>) => void;
+    handle: (dp: InstanceType<typeof DrawPoster>, ...args: any[]) => any;
+    createImage?: (dp: InstanceType<typeof DrawPoster>) => void;
+    [key: string]: any;
+}
+/** 绘制画笔实例扩展配置 */
+export interface DrawPosterUseCtxOpts {
+    name: string;
+    init?: (canvas: Canvas, ctx: DrawPosterCanvasCtx) => void;
+    handle: (canvas: Canvas, ctx: DrawPosterCanvasCtx, ...args: any[]) => any;
+    [key: string]: any;
+}

+ 1 - 0
utils/js_sdk/u-draw-poster/utils/interface.js

@@ -0,0 +1 @@
+export {};

+ 38 - 0
utils/js_sdk/u-draw-poster/utils/object-sizing.d.ts

@@ -0,0 +1,38 @@
+export declare type ObjectFit = "contain" | "cover";
+export declare type ObjectPosition = ["left" | "center" | "right", "top" | "center" | "bottom"];
+export interface Size {
+    width: number;
+    height: number;
+}
+/**
+ * 用于计算图片的宽高比例
+ * @see https://drafts.csswg.org/css-images-3/#sizing-terms
+ *
+ * ## 名词解释
+ * ### intrinsic dimensions
+ * 图片本身的尺寸
+ *
+ * ### specified size
+ * 用户指定的元素尺寸
+ *
+ * ### concrete object size
+ * 应用了 `objectFit` 之后图片的显示尺寸
+ *
+ * ### default object size
+ */
+export declare function calculateConcreteRect(style: {
+    /** @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit */
+    objectFit?: ObjectFit;
+    /** @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position */
+    intrinsicPosition?: ObjectPosition;
+    specifiedPosition?: [number, number];
+}, intrinsicSize: Size, specifiedSize: Size): {
+    sx: number;
+    sy: number;
+    sw: number;
+    sh: number;
+    dx: number;
+    dy: number;
+    dw: number;
+    dh: number;
+};

+ 78 - 0
utils/js_sdk/u-draw-poster/utils/object-sizing.js

@@ -0,0 +1,78 @@
+/**
+ * 用于计算图片的宽高比例
+ * @see https://drafts.csswg.org/css-images-3/#sizing-terms
+ *
+ * ## 名词解释
+ * ### intrinsic dimensions
+ * 图片本身的尺寸
+ *
+ * ### specified size
+ * 用户指定的元素尺寸
+ *
+ * ### concrete object size
+ * 应用了 `objectFit` 之后图片的显示尺寸
+ *
+ * ### default object size
+ */
+export function calculateConcreteRect(style, intrinsicSize, specifiedSize) {
+    var _a, _b;
+    const isContain = style.objectFit === 'contain';
+    const specifiedPosition = style.specifiedPosition || [0, 0];
+    // ratio 越大表示矩形越"扁"
+    let intrinsicRatio = intrinsicSize.width / intrinsicSize.height;
+    let specifiedRatio = specifiedSize.width / specifiedSize.height;
+    /** 图片原始尺寸与最终尺寸之比 */
+    let concreteScale = 1;
+    if (intrinsicRatio > specifiedRatio && style.objectFit == "contain" ||
+        intrinsicRatio <= specifiedRatio && style.objectFit == "cover")
+        // 图片较"胖"时完整显示图片,图片较"瘦"时完全覆盖容器
+        // 这两种情况下有 concreteRect.width = specifiedSize.width
+        // 因为 concreteRect.width = intrinsicSize.width * concreteScale
+        // 所以:
+        concreteScale = specifiedSize.width / intrinsicSize.width;
+    else if (intrinsicRatio > specifiedRatio && style.objectFit == "cover" ||
+        intrinsicRatio <= specifiedRatio && style.objectFit == "contain")
+        // 图片较"瘦"时完整显示图片,图片较"胖"时完全覆盖容器
+        // 这两种情况下有 concreteRect.height = specifiedSize.height
+        // 因为 concreteRect.height = intrinsicSize.height * concreteScale
+        // 所以:
+        concreteScale = specifiedSize.height / intrinsicSize.height;
+    else
+        throw new Error("Unkonwn concreteScale");
+    let concreteRectWidth = intrinsicSize.width * concreteScale;
+    let concreteRectHeight = intrinsicSize.height * concreteScale;
+    // 这里可以把 left top 的计算想象成投影
+    let xRelativeOrigin = { left: 0, center: .5, right: 1 }[((_a = style.intrinsicPosition) === null || _a === void 0 ? void 0 : _a[0]) || "center"];
+    let yRelativeOrigin = { top: 0, center: .5, bottom: 1 }[((_b = style.intrinsicPosition) === null || _b === void 0 ? void 0 : _b[1]) || "center"];
+    let concreteRectLeft = (specifiedSize.width - concreteRectWidth) * xRelativeOrigin;
+    let concreteRectTop = (specifiedSize.height - concreteRectHeight) * yRelativeOrigin;
+    if (isContain) {
+        concreteRectLeft += specifiedPosition[0];
+        concreteRectTop += specifiedPosition[1];
+    }
+    // 这里有两个坐标系,一个是 specified (dist) 的坐标系,一个是 intrinsic (src) 的坐标系
+    // 这里将两个坐标系的点位置进行变换
+    // 例: 带入 x=0, y=0, 得到的结果就是 specifiedRect 的左上角在 intrinsic 坐标系下的坐标位置
+    // 在 specified 坐标系下, intrinsic 的零点在 (concreteRectLeft, concreteRectTop), 缩放为 concreteScale
+    // 所以有 x_dist = x_src * concreteScale + concreteRectLeft
+    //        y_dist = y_src * concreteScale + concreteRectTop
+    let dist2src = (distX, distY) => [
+        /* srcX = */ (distX - concreteRectLeft) / concreteScale,
+        /* srcY = */ (distY - concreteRectTop) / concreteScale
+    ];
+    let [srcLeft, srcTop] = dist2src(0, 0);
+    // srcRight =  图片 specified 框右边在 src 坐标系下的 x 坐标
+    // srcBottom = 图片 specified 框下边在 src 坐标系下的 y 坐标
+    let [srcRight, srcBottom] = dist2src(specifiedSize.width, specifiedSize.height);
+    // 这里要对 src 和 disc 两个框进行约束
+    return {
+        sx: Math.max(srcLeft, 0),
+        sy: Math.max(srcTop, 0),
+        sw: Math.min(srcRight - srcLeft, intrinsicSize.width),
+        sh: Math.min(srcBottom - srcTop, intrinsicSize.height),
+        dx: isContain ? Math.max(concreteRectLeft, 0) : specifiedPosition[0],
+        dy: isContain ? Math.max(concreteRectTop, 0) : specifiedPosition[1],
+        dw: Math.min(concreteRectWidth, specifiedSize.width),
+        dh: Math.min(concreteRectHeight, specifiedSize.height)
+    };
+}

+ 20 - 0
utils/js_sdk/u-draw-poster/utils/utils.d.ts

@@ -0,0 +1,20 @@
+import { DrawPosterBuildOpts } from "./interface";
+/** 是否是base64本地地址 */
+export declare const isBaseUrl: (str: string) => boolean;
+/** 是否是小程序本地地址 */
+export declare const isTmpUrl: (str: string) => boolean;
+/** 是否是网络地址 */
+export declare const isNetworkUrl: (str: string) => boolean;
+/** 对象target挂载到对象current */
+export declare const extendMount: (current: Record<any, any>, target: Record<any, any>, handle?: (extend: Function, target?: Record<any, any> | undefined) => any) => void;
+/** 处理构建配置 */
+export declare const handleBuildOpts: (options: string | DrawPosterBuildOpts) => {
+    selector: string;
+    componentThis: any;
+    type2d: boolean;
+    loading: boolean;
+    debugging: boolean;
+    loadingText: string;
+    createText: string;
+    gcanvas: boolean;
+};

+ 49 - 0
utils/js_sdk/u-draw-poster/utils/utils.js

@@ -0,0 +1,49 @@
+import { PLATFORM } from "./global";
+/** 是否是base64本地地址 */
+export const isBaseUrl = (str) => {
+    return /^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*?)\s*$/i.test(str);
+};
+/** 是否是小程序本地地址 */
+export const isTmpUrl = (str) => {
+    return /http:\/\/temp\/wx/.test(str);
+};
+/** 是否是网络地址 */
+export const isNetworkUrl = (str) => {
+    return /^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$/.test(str);
+};
+/** 对象target挂载到对象current */
+export const extendMount = (current, target, handle = (extend, target) => undefined) => {
+    for (const key in target) {
+        current[key] = handle(target[key].handle, target[key]) || target[key].handle;
+    }
+};
+/** 处理构建配置 */
+export const handleBuildOpts = (options) => {
+    let defaultOpts = {
+        selector: '',
+        componentThis: undefined,
+        type2d: true,
+        loading: false,
+        debugging: false,
+        loadingText: '绘制海报中...',
+        createText: '生成图片中...',
+        gcanvas: false
+    };
+    if (typeof options === "string") {
+        defaultOpts.selector = options;
+    }
+    else {
+        defaultOpts = Object.assign(Object.assign({}, defaultOpts), options);
+    }
+    const oldSelector = defaultOpts.selector;
+    if (PLATFORM === 'mp-weixin' && defaultOpts.type2d) {
+        defaultOpts.selector = '#' + defaultOpts.selector;
+    }
+    if (!PLATFORM) {
+        console.error('注意! draw-poster未开启uni条件编译! 当环境是微信小程序将不会动态切换为type2d模式');
+        console.error(`请在vue.config.js中的transpileDependencies中添加'uni-draw-poster'`);
+        console.error(`或者可以在选择器字符串前缀中添加#来切换为type2d绘制`);
+        defaultOpts.selector = oldSelector;
+    }
+    return defaultOpts;
+};

+ 3 - 0
utils/js_sdk/u-draw-poster/utils/wx-utils.d.ts

@@ -0,0 +1,3 @@
+import { Canvas } from "./interface";
+export declare const downloadImgUrl: (url: string) => Promise<string>;
+export declare const getCanvas2dContext: (selector: string, componentThis?: any) => Promise<Canvas | {}>;

+ 37 - 0
utils/js_sdk/u-draw-poster/utils/wx-utils.js

@@ -0,0 +1,37 @@
+/*
+ * @Author: Mr.Mao
+ * @LastEditors: Mr.Mao
+ * @Date: 2020-10-12 08:49:27
+ * @LastEditTime: 2020-12-09 13:54:10
+ * @Description:
+ * @任何一个傻子都能写出让电脑能懂的代码,而只有好的程序员可以写出让人能看懂的代码
+ */
+import uni from "./global";
+import { isBaseUrl, isNetworkUrl, isTmpUrl } from './utils';
+// 下载指定地址图片, 如果不符合下载图片, 则直接返回
+export const downloadImgUrl = (url) => {
+    const isLocalFile = isBaseUrl(url) || isTmpUrl(url) || !isNetworkUrl(url);
+    return new Promise((resolve, reject) => {
+        if (isLocalFile) {
+            return resolve(url);
+        }
+        uni.downloadFile({
+            url,
+            success: (res) => resolve(res.tempFilePath),
+            fail: reject
+        });
+    });
+};
+// 获取当前指定 node 节点
+export const getCanvas2dContext = (selector, componentThis) => {
+    return new Promise(resolve => {
+        const query = (componentThis ?
+            uni.createSelectorQuery().in(componentThis) :
+            uni.createSelectorQuery());
+        query.select(selector)
+            .fields({ node: true }, res => {
+            const node = res === null || res === void 0 ? void 0 : res.node;
+            resolve(node || {});
+        }).exec();
+    });
+};

+ 273 - 0
utils/js_sdk/wa-permission/permission.js

@@ -0,0 +1,273 @@
+/**
+ * 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启
+ * https://ext.dcloud.net.cn/plugin?id=594
+ */
+
+var isIos
+// #ifdef APP-PLUS
+isIos = (plus.os.name == "iOS")
+// #endif
+
+// 判断推送权限是否开启
+function judgeIosPermissionPush() {
+	var result = false;
+	var UIApplication = plus.ios.import("UIApplication");
+	var app = UIApplication.sharedApplication();
+	var enabledTypes = 0;
+	if (app.currentUserNotificationSettings) {
+		var settings = app.currentUserNotificationSettings();
+		enabledTypes = settings.plusGetAttribute("types");
+		console.log("enabledTypes1:" + enabledTypes);
+		if (enabledTypes == 0) {
+			console.log("推送权限没有开启");
+		} else {
+			result = true;
+			console.log("已经开启推送功能!")
+		}
+		plus.ios.deleteObject(settings);
+	} else {
+		enabledTypes = app.enabledRemoteNotificationTypes();
+		if (enabledTypes == 0) {
+			console.log("推送权限没有开启!");
+		} else {
+			result = true;
+			console.log("已经开启推送功能!")
+		}
+		console.log("enabledTypes2:" + enabledTypes);
+	}
+	plus.ios.deleteObject(app);
+	plus.ios.deleteObject(UIApplication);
+	return result;
+}
+
+// 判断定位权限是否开启
+function judgeIosPermissionLocation() {
+	var result = false;
+	var cllocationManger = plus.ios.import("CLLocationManager");
+	var status = cllocationManger.authorizationStatus();
+	result = (status != 2)
+	console.log("定位权限开启:" + result);
+	// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation
+	/* var enable = cllocationManger.locationServicesEnabled();
+	var status = cllocationManger.authorizationStatus();
+	console.log("enable:" + enable);
+	console.log("status:" + status);
+	if (enable && status != 2) {
+		result = true;
+		console.log("手机定位服务已开启且已授予定位权限");
+	} else {
+		console.log("手机系统的定位没有打开或未给予定位权限");
+	} */
+	plus.ios.deleteObject(cllocationManger);
+	return result;
+}
+
+// 判断麦克风权限是否开启
+function judgeIosPermissionRecord() {
+	var result = false;
+	var avaudiosession = plus.ios.import("AVAudioSession");
+	var avaudio = avaudiosession.sharedInstance();
+	var permissionStatus = avaudio.recordPermission();
+	console.log("permissionStatus:" + permissionStatus);
+	if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
+		console.log("麦克风权限没有开启");
+	} else {
+		result = true;
+		console.log("麦克风权限已经开启");
+	}
+	plus.ios.deleteObject(avaudiosession);
+	return result;
+}
+
+// 判断相机权限是否开启
+function judgeIosPermissionCamera() {
+	var result = false;
+	var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
+	var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
+	console.log("authStatus:" + authStatus);
+	if (authStatus == 3) {
+		result = true;
+		console.log("相机权限已经开启");
+	} else {
+		console.log("相机权限没有开启");
+	}
+	plus.ios.deleteObject(AVCaptureDevice);
+	return result;
+}
+
+// 判断相册权限是否开启
+function judgeIosPermissionPhotoLibrary() {
+	var result = false;
+	var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
+	var authStatus = PHPhotoLibrary.authorizationStatus();
+	console.log("authStatus:" + authStatus);
+	if (authStatus == 3) {
+		result = true;
+		console.log("相册权限已经开启");
+	} else {
+		console.log("相册权限没有开启");
+	}
+	plus.ios.deleteObject(PHPhotoLibrary);
+	return result;
+}
+
+// 判断通讯录权限是否开启
+function judgeIosPermissionContact() {
+	var result = false;
+	var CNContactStore = plus.ios.import("CNContactStore");
+	var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
+	if (cnAuthStatus == 3) {
+		result = true;
+		console.log("通讯录权限已经开启");
+	} else {
+		console.log("通讯录权限没有开启");
+	}
+	plus.ios.deleteObject(CNContactStore);
+	return result;
+}
+
+// 判断日历权限是否开启
+function judgeIosPermissionCalendar() {
+	var result = false;
+	var EKEventStore = plus.ios.import("EKEventStore");
+	var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
+	if (ekAuthStatus == 3) {
+		result = true;
+		console.log("日历权限已经开启");
+	} else {
+		console.log("日历权限没有开启");
+	}
+	plus.ios.deleteObject(EKEventStore);
+	return result;
+}
+
+// 判断备忘录权限是否开启
+function judgeIosPermissionMemo() {
+	var result = false;
+	var EKEventStore = plus.ios.import("EKEventStore");
+	var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
+	if (ekAuthStatus == 3) {
+		result = true;
+		console.log("备忘录权限已经开启");
+	} else {
+		console.log("备忘录权限没有开启");
+	}
+	plus.ios.deleteObject(EKEventStore);
+	return result;
+}
+
+// Android权限查询
+function requestAndroidPermission(permissionID) {
+	return new Promise((resolve, reject) => {
+		plus.android.requestPermissions(
+			[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
+			function(resultObj) {
+				var result = 0;
+				for (var i = 0; i < resultObj.granted.length; i++) {
+					var grantedPermission = resultObj.granted[i];
+					console.log('已获取的权限:' + grantedPermission);
+					result = 1
+				}
+				for (var i = 0; i < resultObj.deniedPresent.length; i++) {
+					var deniedPresentPermission = resultObj.deniedPresent[i];
+					console.log('拒绝本次申请的权限:' + deniedPresentPermission);
+					result = 0
+				}
+				for (var i = 0; i < resultObj.deniedAlways.length; i++) {
+					var deniedAlwaysPermission = resultObj.deniedAlways[i];
+					console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
+					result = -1
+				}
+				resolve(result);
+				// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
+				// if (result != 1) {
+				// gotoAppPermissionSetting()
+				// }
+			},
+			function(error) {
+				console.log('申请权限错误:' + error.code + " = " + error.message);
+				resolve({
+					code: error.code,
+					message: error.message
+				});
+			}
+		);
+	});
+}
+
+// 使用一个方法,根据参数判断权限
+function judgeIosPermission(permissionID) {
+	if (permissionID == "location") {
+		return judgeIosPermissionLocation()
+	} else if (permissionID == "camera") {
+		return judgeIosPermissionCamera()
+	} else if (permissionID == "photoLibrary") {
+		return judgeIosPermissionPhotoLibrary()
+	} else if (permissionID == "record") {
+		return judgeIosPermissionRecord()
+	} else if (permissionID == "push") {
+		return judgeIosPermissionPush()
+	} else if (permissionID == "contact") {
+		return judgeIosPermissionContact()
+	} else if (permissionID == "calendar") {
+		return judgeIosPermissionCalendar()
+	} else if (permissionID == "memo") {
+		return judgeIosPermissionMemo()
+	}
+	return false;
+}
+
+// 跳转到**应用**的权限页面
+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 {
+		// console.log(plus.device.vendor);
+		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);
+	}
+}
+
+// 检查系统的设备服务是否开启
+// var checkSystemEnableLocation = async function () {
+function checkSystemEnableLocation() {
+	if (isIos) {
+		var result = false;
+		var cllocationManger = plus.ios.import("CLLocationManager");
+		var result = cllocationManger.locationServicesEnabled();
+		console.log("系统定位开启:" + result);
+		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);
+		console.log("系统定位开启:" + result);
+		return result
+	}
+}
+
+module.exports = {
+	judgeIosPermission: judgeIosPermission,
+	requestAndroidPermission: requestAndroidPermission,
+	checkSystemEnableLocation: checkSystemEnableLocation,
+	gotoAppPermissionSetting: gotoAppPermissionSetting
+}

+ 83 - 0
utils/lib/request/adapters/index.js

@@ -0,0 +1,83 @@
+import buildURL from '../helpers/buildURL'
+import buildFullPath from '../core/buildFullPath'
+import settle from '../core/settle'
+
+/**
+ * 返回可选值存在的配置
+ * @param {Array} keys - 可选值数组
+ * @param {Object} config2 - 配置
+ * @return {{}} - 存在的配置项
+ */
+const mergeKeys = (keys, config2) => {
+  let config = {}
+  keys.forEach(prop => {
+    if (typeof config2[prop] !== 'undefined') {
+      config[prop] = config2[prop]
+    }
+  })
+  return config
+}
+export default (config) => {
+  return new Promise((resolve, reject) => {
+    const _config = {
+      url: buildURL(buildFullPath(config.baseURL, config.url), config.params),
+      header: config.header,
+      complete: (response) => {
+        response.config = config
+        try {
+          // 对可能字符串不是json 的情况容错
+          if (typeof response.data === 'string') {
+            response.data = JSON.parse(response.data)
+          }
+          // eslint-disable-next-line no-empty
+        } catch (e) {
+        }
+        settle(resolve, reject, response)
+      }
+    }
+    let requestTask
+    if (config.method === 'UPLOAD') {
+      let otherConfig = {
+        // #ifdef MP-ALIPAY
+        fileType: config.fileType,
+        // #endif
+        filePath: config.filePath,
+        name: config.name
+      }
+      const optionalKeys = [
+        // #ifdef APP-PLUS || H5
+        'files',
+        // #endif
+        // #ifdef H5
+        'file',
+        // #endif
+        'formData'
+      ]
+      requestTask = uni.uploadFile({..._config, ...otherConfig, ...mergeKeys(optionalKeys, config)})
+    } else if (config.method === 'DOWNLOAD') {
+      requestTask = uni.downloadFile(_config)
+    } else {
+      const optionalKeys = [
+        'data',
+        'method',
+        // #ifdef MP-ALIPAY || MP-WEIXIN
+        'timeout',
+        // #endif
+        'dataType',
+        // #ifndef MP-ALIPAY || APP-PLUS
+        'responseType',
+        // #endif
+        // #ifdef APP-PLUS
+        'sslVerify',
+        // #endif
+        // #ifdef H5
+        'withCredentials'
+        // #endif
+      ]
+      requestTask = uni.request({..._config,...mergeKeys(optionalKeys, config)})
+    }
+    if (config.getTask) {
+      config.getTask(requestTask, config)
+    }
+  })
+}

+ 51 - 0
utils/lib/request/core/InterceptorManager.js

@@ -0,0 +1,51 @@
+'use strict'
+
+
+function InterceptorManager() {
+  this.handlers = []
+}
+
+/**
+ * Add a new interceptor to the stack
+ *
+ * @param {Function} fulfilled The function to handle `then` for a `Promise`
+ * @param {Function} rejected The function to handle `reject` for a `Promise`
+ *
+ * @return {Number} An ID used to remove interceptor later
+ */
+InterceptorManager.prototype.use = function use(fulfilled, rejected) {
+  this.handlers.push({
+    fulfilled: fulfilled,
+    rejected: rejected
+  })
+  return this.handlers.length - 1
+}
+
+/**
+ * Remove an interceptor from the stack
+ *
+ * @param {Number} id The ID that was returned by `use`
+ */
+InterceptorManager.prototype.eject = function eject(id) {
+  if (this.handlers[id]) {
+    this.handlers[id] = null
+  }
+}
+
+/**
+ * Iterate over all the registered interceptors
+ *
+ * This method is particularly useful for skipping over any
+ * interceptors that may have become `null` calling `eject`.
+ *
+ * @param {Function} fn The function to call for each interceptor
+ */
+InterceptorManager.prototype.forEach = function forEach(fn) {
+  this.handlers.forEach(h => {
+    if (h !== null) {
+      fn(h)
+    }
+  })
+}
+
+export default InterceptorManager

+ 198 - 0
utils/lib/request/core/Request.js

@@ -0,0 +1,198 @@
+/**
+ * @Class Request
+ * @description luch-request http请求插件
+ * @version 3.0.2
+ * @Author lu-ch
+ * @Date 2020-06-04
+ * @Email webwork.s@qq.com
+ * 文档: https://quanzhan.co/luch-request/
+ * github: https://github.com/lei-mu/luch-request
+ * DCloud: http://ext.dcloud.net.cn/plugin?id=392
+ * HBuilderX: 2.7.9
+ */
+
+
+import dispatchRequest from './dispatchRequest'
+import InterceptorManager from './InterceptorManager'
+import mergeConfig from './mergeConfig'
+import defaults from './defaults'
+import { isPlainObject } from '../utils'
+
+export default class Request {
+  /**
+   * @param {Object} arg - 全局配置
+   * @param {String} arg.baseURL - 全局根路径
+   * @param {Object} arg.header - 全局header
+   * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
+   * @param {String} arg.dataType = [json] - 全局默认的dataType
+   * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。App和支付宝小程序不支持
+   * @param {Object} arg.custom - 全局默认的自定义参数
+   * @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认30000。仅微信小程序(2.10.0)、支付宝小程序支持
+   * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+)
+   * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+)
+   * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300
+   */
+  constructor(arg = {}) {
+    if (!isPlainObject(arg)) {
+      arg = {}
+      //console.warn('设置全局参数必须接收一个Object')
+    }
+    this.config = {...defaults, ...arg}
+    this.interceptors = {
+      request: new InterceptorManager(),
+      response: new InterceptorManager()
+    }
+  }
+
+  /**
+   * @Function
+   * @param {Request~setConfigCallback} f - 设置全局默认配置
+   */
+  setConfig(f) {
+    this.config = f(this.config)
+  }
+
+  _middleware(config) {
+    config = mergeConfig(this.config, config)
+    let chain = [dispatchRequest, undefined]
+    let promise = Promise.resolve(config)
+
+    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
+      chain.unshift(interceptor.fulfilled, interceptor.rejected)
+    })
+
+    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
+      chain.push(interceptor.fulfilled, interceptor.rejected)
+    })
+
+    while (chain.length) {
+      promise = promise.then(chain.shift(), chain.shift())
+    }
+
+    return promise
+  }
+
+  /**
+   * @Function
+   * @param {Object} config - 请求配置项
+   * @prop {String} options.url - 请求路径
+   * @prop {Object} options.data - 请求参数
+   * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
+   * @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse
+   * @prop {Object} [options.header = config.header] - 请求header
+   * @prop {Object} [options.method = config.method] - 请求方法
+   * @returns {Promise<unknown>}
+   */
+  request(config = {}) {
+    return this._middleware(config)
+  }
+
+  get(url, options = {}) {
+    return this.request({
+      url,
+      method: 'GET',
+      ...options
+    })
+  }
+
+  post(url, data, options = {}) {
+    return this.request({
+      url,
+      data,
+      method: 'POST',
+      ...options
+    })
+  }
+
+  // #ifndef MP-ALIPAY
+  put(url, data, options = {}) {
+    return this.request({
+      url,
+      data,
+      method: 'PUT',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+  delete(url, data, options = {}) {
+    return this.request({
+      url,
+      data,
+      method: 'DELETE',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef APP-PLUS || H5 || MP-WEIXIN
+  connect(url, data, options = {}) {
+    return this.request({
+      url,
+      data,
+      method: 'CONNECT',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+  head(url, data, options = {}) {
+    return this.request({
+      url,
+      data,
+      method: 'HEAD',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+  options(url, data, options = {}) {
+    return this.request({
+      url,
+      data,
+      method: 'OPTIONS',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef APP-PLUS || H5 || MP-WEIXIN
+  trace(url, data, options = {}) {
+    return this.request({
+      url,
+      data,
+      method: 'TRACE',
+      ...options
+    })
+  }
+
+  // #endif
+
+  upload(url, config = {}) {
+    config.url = url
+    config.method = 'UPLOAD'
+    return this._middleware(config)
+  }
+
+  download(url, config = {}) {
+    config.url = url
+    config.method = 'DOWNLOAD'
+    return this._middleware(config)
+  }
+}
+
+
+/**
+ * setConfig回调
+ * @return {Object} - 返回操作后的config
+ * @callback Request~setConfigCallback
+ * @param {Object} config - 全局默认config
+ */

+ 20 - 0
utils/lib/request/core/buildFullPath.js

@@ -0,0 +1,20 @@
+'use strict'
+
+import isAbsoluteURL from '../helpers/isAbsoluteURL'
+import combineURLs from '../helpers/combineURLs'
+
+/**
+ * Creates a new URL by combining the baseURL with the requestedURL,
+ * only when the requestedURL is not already an absolute URL.
+ * If the requestURL is absolute, this function returns the requestedURL untouched.
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} requestedURL Absolute or relative URL to combine
+ * @returns {string} The combined full path
+ */
+export default function buildFullPath(baseURL, requestedURL) {
+  if (baseURL && !isAbsoluteURL(requestedURL)) {
+    return combineURLs(baseURL, requestedURL)
+  }
+  return requestedURL
+}

+ 27 - 0
utils/lib/request/core/defaults.js

@@ -0,0 +1,27 @@
+/**
+ * 默认的全局配置
+ */
+
+
+export default {
+  baseURL: '',
+  header: {},
+  method: 'GET',
+  dataType: 'json',
+  // #ifndef MP-ALIPAY || APP-PLUS
+  responseType: 'text',
+  // #endif
+  custom: {},
+  // #ifdef MP-ALIPAY || MP-WEIXIN
+  timeout: 30000,
+  // #endif
+  // #ifdef APP-PLUS
+  sslVerify: true,
+  // #endif
+  // #ifdef H5
+  withCredentials: false,
+  // #endif
+  validateStatus: function validateStatus(status) {
+    return status >= 200 && status < 300
+  }
+}

+ 7 - 0
utils/lib/request/core/dispatchRequest.js

@@ -0,0 +1,7 @@
+import adapter from '../adapters/index'
+
+
+export default (config) => {
+  config.header = config.header || {}
+  return adapter(config)
+}

+ 97 - 0
utils/lib/request/core/mergeConfig.js

@@ -0,0 +1,97 @@
+import {deepMerge, isObject} from '../utils'
+
+/**
+ * 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局
+ * @param {Array} keys - 配置项
+ * @param {Object} globalsConfig - 当前的全局配置
+ * @param {Object} config2 - 局部配置
+ * @return {{}}
+ */
+const mergeKeys = (keys, globalsConfig, config2) => {
+  let config = {}
+  keys.forEach(prop => {
+    if (typeof config2[prop] !== 'undefined') {
+      config[prop] = config2[prop]
+    } else if (typeof globalsConfig[prop] !== 'undefined') {
+      config[prop] = globalsConfig[prop]
+    }
+  })
+  return config
+}
+/**
+ *
+ * @param globalsConfig - 当前实例的全局配置
+ * @param config2 - 当前的局部配置
+ * @return - 合并后的配置
+ */
+export default (globalsConfig, config2 = {}) => {
+  const method = config2.method || globalsConfig.method || 'GET'
+  let config = {
+    baseURL: globalsConfig.baseURL || '',
+    method: method,
+    url: config2.url || ''
+  }
+  const mergeDeepPropertiesKeys = ['header', 'params', 'custom']
+  const defaultToConfig2Keys = ['getTask', 'validateStatus']
+  mergeDeepPropertiesKeys.forEach(prop => {
+    if (isObject(config2[prop])) {
+      config[prop] = deepMerge(globalsConfig[prop], config2[prop])
+    } else if (typeof config2[prop] !== 'undefined') {
+      config[prop] = config2[prop]
+    } else if (isObject(globalsConfig[prop])) {
+      config[prop] = deepMerge(globalsConfig[prop])
+    } else if (typeof globalsConfig[prop] !== 'undefined') {
+      config[prop] = globalsConfig[prop]
+    }
+  })
+  config = {...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2)}
+
+  // eslint-disable-next-line no-empty
+  if (method === 'DOWNLOAD') {
+
+  } else if (method === 'UPLOAD') {
+    if (isObject(config.header)) {
+      delete config.header['content-type']
+      delete config.header['Content-Type']
+    }
+    const uploadKeys = [
+      // #ifdef APP-PLUS || H5
+      'files',
+      // #endif
+      // #ifdef MP-ALIPAY
+      'fileType',
+      // #endif
+      // #ifdef H5
+      'file',
+      // #endif
+      'filePath',
+      'name',
+      'formData',
+    ]
+    uploadKeys.forEach(prop => {
+      if (typeof config2[prop] !== 'undefined') {
+        config[prop] = config2[prop]
+      }
+    })
+  } else {
+    const defaultsKeys = [
+      'data',
+      // #ifdef MP-ALIPAY || MP-WEIXIN
+      'timeout',
+      // #endif
+      'dataType',
+      // #ifndef MP-ALIPAY || APP-PLUS
+      'responseType',
+      // #endif
+      // #ifdef APP-PLUS
+      'sslVerify',
+      // #endif
+      // #ifdef H5
+      'withCredentials'
+      // #endif
+    ]
+    config = {...config, ...mergeKeys(defaultsKeys, globalsConfig, config2)}
+  }
+
+  return config
+}

+ 16 - 0
utils/lib/request/core/settle.js

@@ -0,0 +1,16 @@
+/**
+ * Resolve or reject a Promise based on response status.
+ *
+ * @param {Function} resolve A function that resolves the promise.
+ * @param {Function} reject A function that rejects the promise.
+ * @param {object} response The response.
+ */
+export default function settle(resolve, reject, response) {
+  const validateStatus = response.config.validateStatus
+  const status = response.statusCode
+  if (status && (!validateStatus || validateStatus(status))) {
+    resolve(response)
+  } else {
+    reject(response)
+  }
+}

+ 69 - 0
utils/lib/request/helpers/buildURL.js

@@ -0,0 +1,69 @@
+'use strict'
+
+import * as utils from './../utils'
+
+function encode(val) {
+  return encodeURIComponent(val).
+    replace(/%40/gi, '@').
+    replace(/%3A/gi, ':').
+    replace(/%24/g, '$').
+    replace(/%2C/gi, ',').
+    replace(/%20/g, '+').
+    replace(/%5B/gi, '[').
+    replace(/%5D/gi, ']')
+}
+
+/**
+ * Build a URL by appending params to the end
+ *
+ * @param {string} url The base of the url (e.g., http://www.google.com)
+ * @param {object} [params] The params to be appended
+ * @returns {string} The formatted url
+ */
+export default function buildURL(url, params) {
+  /*eslint no-param-reassign:0*/
+  if (!params) {
+    return url
+  }
+
+  var serializedParams
+  if (utils.isURLSearchParams(params)) {
+    serializedParams = params.toString()
+  } else {
+    var parts = []
+
+    utils.forEach(params, function serialize(val, key) {
+      if (val === null || typeof val === 'undefined') {
+        return
+      }
+
+      if (utils.isArray(val)) {
+        key = key + '[]'
+      } else {
+        val = [val]
+      }
+
+      utils.forEach(val, function parseValue(v) {
+        if (utils.isDate(v)) {
+          v = v.toISOString()
+        } else if (utils.isObject(v)) {
+          v = JSON.stringify(v)
+        }
+        parts.push(encode(key) + '=' + encode(v))
+      })
+    })
+
+    serializedParams = parts.join('&')
+  }
+
+  if (serializedParams) {
+    var hashmarkIndex = url.indexOf('#')
+    if (hashmarkIndex !== -1) {
+      url = url.slice(0, hashmarkIndex)
+    }
+
+    url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
+  }
+
+  return url
+}

+ 14 - 0
utils/lib/request/helpers/combineURLs.js

@@ -0,0 +1,14 @@
+'use strict'
+
+/**
+ * Creates a new URL by combining the specified URLs
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} relativeURL The relative URL
+ * @returns {string} The combined URL
+ */
+export default function combineURLs(baseURL, relativeURL) {
+  return relativeURL
+    ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
+    : baseURL
+}

+ 14 - 0
utils/lib/request/helpers/isAbsoluteURL.js

@@ -0,0 +1,14 @@
+'use strict'
+
+/**
+ * Determines whether the specified URL is absolute
+ *
+ * @param {string} url The URL to test
+ * @returns {boolean} True if the specified URL is absolute, otherwise false
+ */
+export default function isAbsoluteURL(url) {
+  // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
+  // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
+  // by any combination of letters, digits, plus, period, or hyphen.
+  return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
+}

+ 2 - 0
utils/lib/request/index.js

@@ -0,0 +1,2 @@
+import Request from './core/Request'
+export default Request

+ 131 - 0
utils/lib/request/utils.js

@@ -0,0 +1,131 @@
+'use strict'
+
+// utils is a library of generic helper functions non-specific to axios
+
+var toString = Object.prototype.toString
+
+/**
+ * Determine if a value is an Array
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Array, otherwise false
+ */
+export function isArray (val) {
+  return toString.call(val) === '[object Array]'
+}
+
+
+/**
+ * Determine if a value is an Object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Object, otherwise false
+ */
+export function isObject (val) {
+  return val !== null && typeof val === 'object'
+}
+
+/**
+ * Determine if a value is a Date
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a Date, otherwise false
+ */
+export function isDate (val) {
+  return toString.call(val) === '[object Date]'
+}
+
+/**
+ * Determine if a value is a URLSearchParams object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a URLSearchParams object, otherwise false
+ */
+export function isURLSearchParams (val) {
+  return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams
+}
+
+
+/**
+ * Iterate over an Array or an Object invoking a function for each item.
+ *
+ * If `obj` is an Array callback will be called passing
+ * the value, index, and complete array for each item.
+ *
+ * If 'obj' is an Object callback will be called passing
+ * the value, key, and complete object for each property.
+ *
+ * @param {Object|Array} obj The object to iterate
+ * @param {Function} fn The callback to invoke for each item
+ */
+export function forEach (obj, fn) {
+  // Don't bother if no value provided
+  if (obj === null || typeof obj === 'undefined') {
+    return
+  }
+
+  // Force an array if not already something iterable
+  if (typeof obj !== 'object') {
+    /*eslint no-param-reassign:0*/
+    obj = [obj]
+  }
+
+  if (isArray(obj)) {
+    // Iterate over array values
+    for (var i = 0, l = obj.length; i < l; i++) {
+      fn.call(null, obj[i], i, obj)
+    }
+  } else {
+    // Iterate over object keys
+    for (var key in obj) {
+      if (Object.prototype.hasOwnProperty.call(obj, key)) {
+        fn.call(null, obj[key], key, obj)
+      }
+    }
+  }
+}
+
+/**
+ * 是否为boolean 值
+ * @param val
+ * @returns {boolean}
+ */
+export function isBoolean(val) {
+  return typeof val === 'boolean'
+}
+
+/**
+ * 是否为真正的对象{} new Object
+ * @param {any} obj - 检测的对象
+ * @returns {boolean}
+ */
+export function isPlainObject(obj) {
+  return Object.prototype.toString.call(obj) === '[object Object]'
+}
+
+
+
+/**
+ * Function equal to merge with the difference being that no reference
+ * to original objects is kept.
+ *
+ * @see merge
+ * @param {Object} obj1 Object to merge
+ * @returns {Object} Result of all merge properties
+ */
+export function deepMerge(/* obj1, obj2, obj3, ... */) {
+  let result = {}
+  function assignValue(val, key) {
+    if (typeof result[key] === 'object' && typeof val === 'object') {
+      result[key] = deepMerge(result[key], val)
+    } else if (typeof val === 'object') {
+      result[key] = deepMerge({}, val)
+    } else {
+      result[key] = val
+    }
+  }
+  for (let i = 0, l = arguments.length; i < l; i++) {
+    forEach(arguments[i], assignValue)
+  }
+  return result
+}

+ 219 - 0
utils/md5.js

@@ -0,0 +1,219 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 1.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Code also contributed by Greg Holt
+ * See http://pajhome.org.uk/site/legal.html for details.
+ */
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y) {
+	var lsw = (x & 0xFFFF) + (y & 0xFFFF)
+	var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
+	return (msw << 16) | (lsw & 0xFFFF)
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function rol(num, cnt) {
+	return (num << cnt) | (num >>> (32 - cnt))
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function cmn(q, a, b, x, s, t) {
+	return safe_add(rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b)
+}
+
+function ff(a, b, c, d, x, s, t) {
+	return cmn((b & c) | ((~b) & d), a, b, x, s, t)
+}
+
+function gg(a, b, c, d, x, s, t) {
+	return cmn((b & d) | (c & (~d)), a, b, x, s, t)
+}
+
+function hh(a, b, c, d, x, s, t) {
+	return cmn(b ^ c ^ d, a, b, x, s, t)
+}
+
+function ii(a, b, c, d, x, s, t) {
+	return cmn(c ^ (b | (~d)), a, b, x, s, t)
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, producing an array
+ * of little-endian words.
+ */
+function coreMD5(x) {
+	var a = 1732584193
+	var b = -271733879
+	var c = -1732584194
+	var d = 271733878
+
+	for (var i = 0; i < x.length; i += 16) {
+		var olda = a
+		var oldb = b
+		var oldc = c
+		var oldd = d
+
+		a = ff(a, b, c, d, x[i + 0], 7, -680876936)
+		d = ff(d, a, b, c, x[i + 1], 12, -389564586)
+		c = ff(c, d, a, b, x[i + 2], 17, 606105819)
+		b = ff(b, c, d, a, x[i + 3], 22, -1044525330)
+		a = ff(a, b, c, d, x[i + 4], 7, -176418897)
+		d = ff(d, a, b, c, x[i + 5], 12, 1200080426)
+		c = ff(c, d, a, b, x[i + 6], 17, -1473231341)
+		b = ff(b, c, d, a, x[i + 7], 22, -45705983)
+		a = ff(a, b, c, d, x[i + 8], 7, 1770035416)
+		d = ff(d, a, b, c, x[i + 9], 12, -1958414417)
+		c = ff(c, d, a, b, x[i + 10], 17, -42063)
+		b = ff(b, c, d, a, x[i + 11], 22, -1990404162)
+		a = ff(a, b, c, d, x[i + 12], 7, 1804603682)
+		d = ff(d, a, b, c, x[i + 13], 12, -40341101)
+		c = ff(c, d, a, b, x[i + 14], 17, -1502002290)
+		b = ff(b, c, d, a, x[i + 15], 22, 1236535329)
+
+		a = gg(a, b, c, d, x[i + 1], 5, -165796510)
+		d = gg(d, a, b, c, x[i + 6], 9, -1069501632)
+		c = gg(c, d, a, b, x[i + 11], 14, 643717713)
+		b = gg(b, c, d, a, x[i + 0], 20, -373897302)
+		a = gg(a, b, c, d, x[i + 5], 5, -701558691)
+		d = gg(d, a, b, c, x[i + 10], 9, 38016083)
+		c = gg(c, d, a, b, x[i + 15], 14, -660478335)
+		b = gg(b, c, d, a, x[i + 4], 20, -405537848)
+		a = gg(a, b, c, d, x[i + 9], 5, 568446438)
+		d = gg(d, a, b, c, x[i + 14], 9, -1019803690)
+		c = gg(c, d, a, b, x[i + 3], 14, -187363961)
+		b = gg(b, c, d, a, x[i + 8], 20, 1163531501)
+		a = gg(a, b, c, d, x[i + 13], 5, -1444681467)
+		d = gg(d, a, b, c, x[i + 2], 9, -51403784)
+		c = gg(c, d, a, b, x[i + 7], 14, 1735328473)
+		b = gg(b, c, d, a, x[i + 12], 20, -1926607734)
+
+		a = hh(a, b, c, d, x[i + 5], 4, -378558)
+		d = hh(d, a, b, c, x[i + 8], 11, -2022574463)
+		c = hh(c, d, a, b, x[i + 11], 16, 1839030562)
+		b = hh(b, c, d, a, x[i + 14], 23, -35309556)
+		a = hh(a, b, c, d, x[i + 1], 4, -1530992060)
+		d = hh(d, a, b, c, x[i + 4], 11, 1272893353)
+		c = hh(c, d, a, b, x[i + 7], 16, -155497632)
+		b = hh(b, c, d, a, x[i + 10], 23, -1094730640)
+		a = hh(a, b, c, d, x[i + 13], 4, 681279174)
+		d = hh(d, a, b, c, x[i + 0], 11, -358537222)
+		c = hh(c, d, a, b, x[i + 3], 16, -722521979)
+		b = hh(b, c, d, a, x[i + 6], 23, 76029189)
+		a = hh(a, b, c, d, x[i + 9], 4, -640364487)
+		d = hh(d, a, b, c, x[i + 12], 11, -421815835)
+		c = hh(c, d, a, b, x[i + 15], 16, 530742520)
+		b = hh(b, c, d, a, x[i + 2], 23, -995338651)
+
+		a = ii(a, b, c, d, x[i + 0], 6, -198630844)
+		d = ii(d, a, b, c, x[i + 7], 10, 1126891415)
+		c = ii(c, d, a, b, x[i + 14], 15, -1416354905)
+		b = ii(b, c, d, a, x[i + 5], 21, -57434055)
+		a = ii(a, b, c, d, x[i + 12], 6, 1700485571)
+		d = ii(d, a, b, c, x[i + 3], 10, -1894986606)
+		c = ii(c, d, a, b, x[i + 10], 15, -1051523)
+		b = ii(b, c, d, a, x[i + 1], 21, -2054922799)
+		a = ii(a, b, c, d, x[i + 8], 6, 1873313359)
+		d = ii(d, a, b, c, x[i + 15], 10, -30611744)
+		c = ii(c, d, a, b, x[i + 6], 15, -1560198380)
+		b = ii(b, c, d, a, x[i + 13], 21, 1309151649)
+		a = ii(a, b, c, d, x[i + 4], 6, -145523070)
+		d = ii(d, a, b, c, x[i + 11], 10, -1120210379)
+		c = ii(c, d, a, b, x[i + 2], 15, 718787259)
+		b = ii(b, c, d, a, x[i + 9], 21, -343485551)
+
+		a = safe_add(a, olda)
+		b = safe_add(b, oldb)
+		c = safe_add(c, oldc)
+		d = safe_add(d, oldd)
+	}
+	return [a, b, c, d]
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray) {
+	var hex_tab = "0123456789abcdef"
+	var str = ""
+	for (var i = 0; i < binarray.length * 4; i++) {
+		str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +
+			hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF)
+	}
+	return str
+}
+
+/*
+ * Convert an array of little-endian words to a base64 encoded string.
+ */
+function binl2b64(binarray) {
+	var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+	var str = ""
+	for (var i = 0; i < binarray.length * 32; i += 6) {
+		str += tab.charAt(((binarray[i >> 5] << (i % 32)) & 0x3F) |
+			((binarray[i >> 5 + 1] >> (32 - i % 32)) & 0x3F))
+	}
+	return str
+}
+
+/*
+ * Convert an 8-bit character string to a sequence of 16-word blocks, stored
+ * as an array, and append appropriate padding for MD4/5 calculation.
+ * If any of the characters are >255, the high byte is silently ignored.
+ */
+function str2binl(str) {
+	var nblk = ((str.length + 8) >> 6) + 1 // number of 16-word blocks
+	var blks = new Array(nblk * 16)
+	for (var i = 0; i < nblk * 16; i++) blks[i] = 0
+	for (var i = 0; i < str.length; i++)
+		blks[i >> 2] |= (str.charCodeAt(i) & 0xFF) << ((i % 4) * 8)
+	blks[i >> 2] |= 0x80 << ((i % 4) * 8)
+	blks[nblk * 16 - 2] = str.length * 8
+	return blks
+}
+
+/*
+ * Convert a wide-character string to a sequence of 16-word blocks, stored as
+ * an array, and append appropriate padding for MD4/5 calculation.
+ */
+function strw2binl(str) {
+	var nblk = ((str.length + 4) >> 5) + 1 // number of 16-word blocks
+	var blks = new Array(nblk * 16)
+	for (var i = 0; i < nblk * 16; i++) blks[i] = 0
+	for (var i = 0; i < str.length; i++)
+		blks[i >> 1] |= str.charCodeAt(i) << ((i % 2) * 16)
+	blks[i >> 1] |= 0x80 << ((i % 2) * 16)
+	blks[nblk * 16 - 2] = str.length * 16
+	return blks
+}
+
+/*
+ * External interface
+ */
+export default function hexMD5(str) {
+	return binl2hex(coreMD5(str2binl(str)))
+}
+
+function hexMD5w(str) {
+	return binl2hex(coreMD5(strw2binl(str)))
+}
+
+function b64MD5(str) {
+	return binl2b64(coreMD5(str2binl(str)))
+}
+
+function b64MD5w(str) {
+	return binl2b64(coreMD5(strw2binl(str)))
+}
+/* Backward compatibility */
+function calcMD5(str) {
+	return binl2hex(coreMD5(str2binl(str)))
+}

+ 211 - 0
utils/request.js

@@ -0,0 +1,211 @@
+import {
+	refreshTokenFn
+} from "@/api/services/login.js";
+import api from "@/config/api.js";
+import Request from "@/utils/lib/request/index.js";
+import Foundation from "@/utils/Foundation.js";
+import md5 from "@/utils/md5.js";
+import storage from "@/utils/storage.js";
+
+import jwt from '@/utils/js_sdk/t-jwt/jwt.js';
+import uuid from "@/utils/uuid.modified.js";
+import store from "../store";
+
+
+let isNavigateTo = false
+
+function cleanStorage() {
+	uni.showToast({
+		title: "你的登录状态已过期,请重新登录",
+		icon: "none",
+		duration: 1500,
+	});
+	if (uni.showLoading()) {
+		uni.hideLoading();
+	}
+
+	storage.setHasLogin(false);
+	storage.setAccessToken("");
+	storage.setRefreshToken("");
+	console.log("清空token");
+	storage.setUuid("");
+	storage.setUserInfo({});
+
+
+	if (!isNavigateTo) {
+		isNavigateTo = true
+		// 防抖处理跳转
+		// #ifdef MP-WEIXIN
+		uni.navigateTo({
+			url: "/pages/passport/wechatMPLogin",
+		});
+		// #endif
+		// #ifndef MP-WEIXIN
+		uni.navigateTo({
+			url: "/pages/passport/login",
+		});
+		//  #endif
+	}
+}
+
+let http = new Request();
+
+
+/**
+ * 创建uuid方法
+ */
+const createUuid = () => {
+	if (!storage.getUuid()) {
+		storage.setUuid(uuid.v4());
+		console.log("uuid", storage.getUuid());
+	}
+}
+
+
+http.setConfig((config) => {
+	createUuid();
+	/* 设置全局配置 */
+	config.baseURL = api.buyer;
+	config.header = {
+		...config.header,
+	};
+	config.validateStatus = (statusCode) => {
+		// 不论什么状态,统一在正确中处理
+		return true;
+	};
+	return config;
+});
+
+http.interceptors.request.use(
+	(config) => {
+		/* 请求之前拦截器。可以使用async await 做异步操作 */
+		let accessToken = storage.getAccessToken();
+		if(storage.getInviter()){
+			config.header.inviter = storage.getInviter();
+		}
+		if (accessToken) {
+			/**
+			 * 使用JWT解析
+			 * 小于当前时间将当前token清除
+			 */
+			const decodeJwt = jwt(accessToken);
+			const timing = new Date().getTime() / 1000
+			if (decodeJwt.exp <= timing) {
+				accessToken = ""
+				storage.setAccessToken('')
+			}
+			
+
+			const nonce = Foundation.randomString(6);
+			const timestamp = parseInt(new Date().getTime() / 1000);
+			const sign = md5(nonce + timestamp + accessToken);
+			const _params = {
+				nonce,
+				timestamp,
+				sign,
+			};
+			let params = config.params || {};
+			params = {
+				...params,
+				..._params
+			};
+
+			config.params = params;
+			// config.header.accessToken = accessToken;
+			config.header.Authorization  = 'Bearer '+accessToken;
+
+
+		}
+		createUuid();
+		config.header = {
+			...config.header,
+			uuid: storage.getUuid()
+		};
+		return config;
+	},
+	(config) => {
+		return Promise.reject(config);
+	}
+);
+
+
+// 是否正在刷新的标记
+let isRefreshing = false;
+//重试队列
+let requests = [];
+// 必须使用异步函数,注意
+http.interceptors.response.use(
+	async (response) => {
+		isNavigateTo = false;
+		uni.showLoading() ? uni.hideLoading() : '';
+		let token = storage.getAccessToken();
+		console.log("请求后的处理",response);
+		// token存在但401,说明过期了
+		if ((token && response.statusCode === 401) || response.data.status === 401) {
+			console.log('token过期或无效,跳转登录', token);
+			cleanStorage(); // 清理缓存中的token、用户信息等
+			uni.showToast({
+				title: '登录已过期,请重新登录',
+				icon: 'none',
+				duration: 2000,
+				complete: () => {
+					setTimeout(() => {
+						uni.reLaunch({ url: '/pages/login/index' }); // 跳转登录页
+					}, 2000);
+				}
+			});
+			return Promise.reject('登录状态过期');
+		}
+
+		// 未登录也跳转
+		if ((!token && !storage.getAccessToken() && response.statusCode === 401) || response.data.code === 401) {
+			console.log('无token,跳转登录');
+			cleanStorage();
+			uni.showToast({
+				title: '请先登录',
+				icon: 'none',
+				duration: 2000,
+				complete: () => {
+					setTimeout(() => {
+						uni.reLaunch({ url: '/pages/login/index' });
+					}, 2000);
+				}
+			});
+			return Promise.reject('未登录');
+		}
+
+		// 请求成功但业务失败
+		if (
+			(response.statusCode === 200 && !response.data.success) ||
+			response.statusCode === 400
+		) {
+			if (response.data.message) {
+				uni.showToast({
+					title: response.data.message,
+					icon: "none",
+					duration: 1500,
+					success: () => { store.state.isShowToast = true; },
+					fail: () => { store.state.isShowToast = false; },
+					complete: () => { store.state.isShowToast = false; }
+				});
+			}
+		}
+
+		return response;
+	},
+	(error) => {
+		return Promise.reject(error);
+	}
+);
+
+
+export {
+	http
+};
+
+export const Method = {
+	GET: "GET",
+	POST: "POST",
+	PUT: "PUT",
+	DELETE: "DELETE",
+};

Някои файлове не бяха показани, защото твърде много файлове са промени